Skip to content

Localization: framework + full string sweep (2.3.0) - #12

Open
chrisdfennell wants to merge 7 commits into
feature/gear-audit-depthfrom
feature/localization
Open

Localization: framework + full string sweep (2.3.0)#12
chrisdfennell wants to merge 7 commits into
feature/gear-audit-depthfrom
feature/localization

Conversation

@chrisdfennell

@chrisdfennell chrisdfennell commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Stacked on #11 (base is the gear branch). Retarget to main after #11 merges.

Adds full localization — the addon had none; every user-facing string was an inline English literal.

Framework

  • Locale.lua creates SP.L; its __index returns 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.md documents 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. Because SP.L falls 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

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Locale.lua
-- 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

chrisdfennell and others added 4 commits July 23, 2026 14:30
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>
@chrisdfennell chrisdfennell changed the title Localization framework + first strings (2.3.0, WIP) Localization: framework + full string sweep (2.3.0) Jul 24, 2026
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.
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