Skip to content

refactor: consolidate localization into shared .resx + duplication quick-wins#26

Merged
HandyS11 merged 13 commits into
developfrom
feat/localization-resx
Jun 24, 2026
Merged

refactor: consolidate localization into shared .resx + duplication quick-wins#26
HandyS11 merged 13 commits into
developfrom
feat/localization-resx

Conversation

@HandyS11

Copy link
Copy Markdown
Owner

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. CommandLocalizationCatalog was 94% duplicated). A .resx model 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)

  • Deleted 6 × *LocalizationCatalog, 6 × *Localizer, 6 × I*Localizer marker interfaces, and DictionaryLocalizer.
  • Added one flat shared resource set — Strings.resx (English/neutral) + Strings.fr.resx — holding all 193 keys (prefix-namespaced, globally unique across the old catalogs), in RustPlusBot.Localization.
  • ResxLocalizer : ILocalizer resolves per-call culture via ResourceManager.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 no Thread.CurrentCulture.
  • All ~44 injection sites now inject the single RustPlusBot.Localization.ILocalizer; one idempotent AddRustPlusBotLocalization() (TryAddSingleton) replaces 6 DI registrations.
  • The resx values were generated programmatically from the old catalogs by reflection, so emoji (⚡ ⭘ ⚠️ 🚨), accented French, and {0} placeholders are byte-exact. An en/fr parity test + key-count guard prevent future drift.

Quick-win

  • MarkerReply.For(…) shared by the cargo/heli/chinook command handlers (they differed only in MarkerKind + key prefix), mirroring the existing RigReply pattern.

Pre-task: editorconfig hardening fallout

The .editorconfig hardening (style rules suggestionwarning) surfaced 5 latent violations under TreatWarningsAsErrors. 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 a CancellationTokenSource).

Deliberately not done

  • A shared base for CommandsHostedService + PlayersHostedService was skipped: their only common project is the dependency-free RustPlusBot.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.
  • Switch/Alarm device symmetry left untouched (separate features; abstracting only to satisfy a metric would add coupling).

Verification

  • Build: 0 errors, 0 warnings.
  • Tests: 514 passing across 13 assemblies (down from 531 only because 23 now-redundant catalog/localizer test classes were removed; the shared ResxLocalizerTests + parity tests replace them).
  • dotnet jb cleanupcode --profile=ReformatAndReorder leaves zero diff.
  • Behavioral equivalence (old catalog values vs new resx) verified across all 6 features in both cultures — 0 mismatches.

Note: the authoritative duplication-% drop will only appear after merge to develop (the Sonar workflow runs single-branch on push to develop).

🤖 Generated with Claude Code

HandyS11 and others added 13 commits June 24, 2026 13:45
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>
@HandyS11 HandyS11 merged commit 4418988 into develop Jun 24, 2026
3 checks passed
@HandyS11 HandyS11 deleted the feat/localization-resx branch June 24, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant