Localization: framework + full string sweep (2.3.0) - #12
Conversation
StatPanel had no localization at all -- every user-facing string was an inline English literal. Add a tiny hand-rolled locale table (no embedded library, in keeping with the rest of the addon): - Locale.lua creates SP.L, whose __index returns the key itself, so the English string IS the key and any untranslated or missing string falls back to readable English. A matching non-English client locale file overlays it; other locales' files load into a throwaway table. - Locales/README.md documents how to contribute a translation. - Loaded first in the .toc, before anything that reads SP.L. Route the slash-command output (SPMain) and the right-click menu (Menu) through SP.L as the first tranche, converting the concatenated messages to format strings so translators can reorder. The remaining files -- Options, Config, Presets, Gear, Announce and the rest -- are the next tranches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b738b33ba8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| -- overlays it. Any other locale's assignments land on a throwaway table and are | ||
| -- discarded, so every translation file can load unconditionally. | ||
| function SP.Locale(locale) | ||
| if locale == "enUS" or locale == GetLocale() then |
There was a problem hiding this comment.
Whitelist GetLocale before running luacheck
In the pull_request Lint workflow, luacheck . runs against .luacheckrc, whose read_globals list does not include GetLocale (repo search only finds this new reference). As soon as CI checks this commit, luacheck will report accessing undefined variable GetLocale, so the localization framework blocks the PR until GetLocale is added to the WoW globals list.
Useful? React with 👍 / 👎.
Locale.lua reads GetLocale() to pick the client locale; it wasn't in the declared API list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bulk of the sweep. Every option label, tooltip, header, note, dropdown display name and button caption in Options.lua now looks up SP.L; Config.lua's profile/import error messages and the preset descriptions in Presets.lua do too. Concatenated messages became format strings so translators can reorder. Config keys (path=), dropdown data values (value=), page ids, value-format templates, media/font names, preset names and section titles are deliberately left untouched -- they are identifiers and lookup keys, not display text. Because SP.L falls back to the key itself, every wrap is a no-op on an English client and only takes effect once a translation provides the string. The mechanical wrapping was done by script over the unambiguous patterns (label/tooltip/text/name/UI:Header/UI:Note) and reviewed as a whole; the concatenated and escaped strings were done by hand. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Content-context rule names, the broker tooltip lines, the announce channel names and every announce status/error message now look up SP.L. The "N values left out" plural was simplified to "N value(s)" so it needs no English-specific plural fragment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the sweep: stat display names (STAT_DEFS, PRIMARY_NAME, SHORT_NAME), the panel row tooltip and the Rebuild error in StatPanel; the whole /sp gear report and warning summary in Gear; and the item-level / priority / gear lines the announce assembles. Plural counts became "%d thing(s)" so no language needs English plural rules. Every user-facing string in the addon now looks up SP.L. Document the feature and bump to 2.3.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first localization pass routed strings through SP.L but left several gaps that only show up once someone actually translates. Stat and gear-slot names now come from Blizzard's own GlobalStrings via a new SP.Global helper, which falls back to SP.L when a global is missing. Those read correctly in all twelve locales with no translator effort. The abbreviated priority-chain labels (Crit, Mast, Vers) stay in SP.L, since the Blizzard names are the full ones and would not fit. Strings the first pass missed are routed too: the font-outline dropdown, the live preview window, the Gear page's slot list and summary, and the LDB broker text. The last three were reaching the UI through string.format and assignment rather than SetText, which is why a grep for SetText did not find them. Sentences built with .. become format strings so translations can reorder them, and Locales/deDE.lua is a complete German translation (452 keys). SP.L never fails at runtime, so the two mistakes translators actually make are silent: a mistyped key renders English forever, and a dropped format specifier only errors at the far-away :format() call. tools/locale-lint.ps1 reports both, plus duplicates, empty translations, a mismatched SP.Locale argument and a file missing from the TOC. -Export emits a stub. CI runs it on every pull request. Also fixes the CI TOC check, which anchored on [A-Za-z0-9_/-]+ and so stopped at the backslash in a Windows-style path. Every subdirectory entry was skipped silently, meaning a broken Locales\ path would have passed. Separators are normalized before matching and the check now walks subdirectories.
2.2.0 keeps its 2026-07-23 date: that is when its work landed, and it never had a release of its own -- it ships as part of this one.
Stacked on #11 (base is the gear branch). Retarget to
mainafter #11 merges.Adds full localization — the addon had none; every user-facing string was an inline English literal.
Framework
Locale.luacreatesSP.L; its__indexreturns the key itself, so the English string is the key and any untranslated/missing string falls back to readable English. A non-English client locale file overlays it; other locales load into a throwaway table. No embedded library.Locales/README.mddocuments how to add a translation. Loaded first in the.toc.Every user-facing string is routed through SP.L (~445 across 11 files)
Options window, panel tooltip + stat names, slash commands, right-click menu, gear report, chat announce, profile/import errors, auto-profile context names, broker tooltip. Concatenations became format strings and plurals became
%d thing(s)so no language needs English plural rules.Deliberately NOT wrapped
Config keys (
path=), dropdown data values (value=), page ids, value-format tokens, media/font names, preset names, section titles — they're identifiers/lookup keys, not display text. BecauseSP.Lfalls back to the key, every wrap is a no-op on English and only takes effect once a translation provides the string.Method
Unambiguous patterns (
label/tooltip/text/name/UI:Header/UI:Note) wrapped by script and diff-reviewed; concatenated/escaped strings by hand. Safety-scanned: 0 config keys wrapped, 0 double-wraps. CI luacheck green.Not verified in-game. English behaviour is unchanged by construction (fallback-to-key); the risk surface is syntax, which luacheck covers.
🤖 Generated with Claude Code