refactor: consolidate localization into shared .resx + duplication quick-wins#26
Merged
Conversation
Promote dotnet/csharp style + naming rules from :suggestion to :warning so redundant style smells fail the build instead of accumulating silently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Promoting style rules to :warning under TreatWarningsAsErrors surfaced 5 latent violations. Two clean fixes (IDE0130 test namespace, IDE0028 collection expressions); IDE0060 folded into the existing MapIcons RCS1163 suppression. IDE0045 (AlarmEmbedRenderer) and IDE0290 (PairingSupervisor.Handle) are suppressed at the line with documented justification: their "fixes" conflict with RCS1238/S3358 (nested ternary) and CA2000 (CTS on a primary ctor). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…talogs
193 keys, globally unique across all 6 feature catalogs, generated by reflection
from the compiled *LocalizationCatalog.Default values so emoji/accents/{0} are
byte-exact. NeutralResourcesLanguage("en") makes the neutral set the English
fallback; verified ResourceManager round-trip resolves en/fr, falls back de->en,
and walks fr-FR->fr.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… extension ResxLocalizer resolves per-call culture via ResourceManager.GetString over the shared Strings resource set, preserving the ILocalizer contract: en fallback, fr-FR->fr normalization, key returned when missing, per-culture format provider. AddRustPlusBotLocalization registers it idempotently (TryAddSingleton). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enumerates the neutral (en, invariant-keyed) and fr resource sets directly so a dropped or renamed key fails the build. Asserts 193 keys with matching sets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… catalog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 6 features now resolve the shared ILocalizer via ResxLocalizer, so the dictionary-backed implementation and its tests are dead code. Behavioral equivalence with the old per-feature catalogs spot-checked across feature prefixes in both cultures (emoji, accents, placeholders byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/chinook The cargo/heli/chinook handlers differed only in MarkerKind and key prefix. Extract MarkerReply.For(state, context, kind, prefix, localizer, clock), mirroring the existing RigReply helper; each handler now delegates in one call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Cut the SonarQube duplication that remained after PR #25. Live baseline at branch start: 4.4% duplication / 754 dup-lines across 20 files, of which ~51% was localization. SonarQube flagged each per-feature catalog because its
["en"] = {…}block and["fr"] = {…}block share an identical key sequence (e.g.CommandLocalizationCatalogwas 94% duplicated). A.resxmodel removes that twinning — each language is its own file with no repeated C# dictionary syntax — and makes adding a language a one-file drop.What changed
Localization (the main event)
*LocalizationCatalog, 6 ×*Localizer, 6 ×I*Localizermarker interfaces, andDictionaryLocalizer.Strings.resx(English/neutral) +Strings.fr.resx— holding all 193 keys (prefix-namespaced, globally unique across the old catalogs), inRustPlusBot.Localization.ResxLocalizer : ILocalizerresolves per-call culture viaResourceManager.GetString(key, culture)— preserving the exact prior contract: requested culture → English fallback → key itself if missing; the format overload uses the requested culture's format provider. Culture stays a per-call parameter (this bot serves many Discord servers, each with its own language), so noThread.CurrentCulture.RustPlusBot.Localization.ILocalizer; one idempotentAddRustPlusBotLocalization()(TryAddSingleton) replaces 6 DI registrations.{0}placeholders are byte-exact. Anen/frparity test + key-count guard prevent future drift.Quick-win
MarkerReply.For(…)shared by thecargo/heli/chinookcommand handlers (they differed only inMarkerKind+ key prefix), mirroring the existingRigReplypattern.Pre-task: editorconfig hardening fallout
The
.editorconfighardening (style rulessuggestion→warning) surfaced 5 latent violations underTreatWarningsAsErrors. Fixed 3 cleanly (IDE0028, IDE0130, IDE0060); the other 2 are documented line-level suppressions because their "fixes" conflict with other enabled analyzers (IDE0045 ↔ RCS1238/S3358 nested-ternary; IDE0290 ↔ CA2000 on aCancellationTokenSource).Deliberately not done
CommandsHostedService+PlayersHostedServicewas skipped: their only common project is the dependency-freeRustPlusBot.Abstractions, and a base there would force hosting/logging deps onto the foundational project — not worth the coupling for ~30 boilerplate lines, and the two services differ in real per-event behavior.Verification
ResxLocalizerTests+ parity tests replace them).dotnet jb cleanupcode --profile=ReformatAndReorderleaves zero diff.🤖 Generated with Claude Code