Skip to content

Localization: client-supplied stat names, German translation, locale linter - #13

Merged
chrisdfennell merged 9 commits into
mainfrom
feature/localization
Jul 27, 2026
Merged

chrisdfennell merged 9 commits into
mainfrom
feature/localization

Conversation

@chrisdfennell

Copy link
Copy Markdown
Owner

Continues the locale framework from the earlier commits on this branch, closing the gaps that only surface once someone actually translates the addon.

Stat and slot names come from the client

New SP.Global(globalName, englishKey) in Locale.lua prefers Blizzard's own GlobalStrings and falls back to SP.L when a global is missing. The 16 gear slots (HEADSLOT, FINGER0SLOT, …) and 14 stat names (STAT_HASTE, SPELL_STAT1_NAME, …) are already correct in all twelve client locales with no translator effort.

Primary and Armor DR stay in SP.L — neither is a Blizzard concept. So do the abbreviated priority-chain labels (Crit, Mast, Vers), since the Blizzard names are the full ones and the point of that line is that it fits.

Strings the first pass missed

The font-outline dropdown, the live-preview window, the Gear page's slot list and summary, and the LDB broker text. The last three reach the UI through string.format and assignment rather than SetText, which is why the original sweep didn't find them — the broker one is visible in Titan Panel and ElvUI datatexts.

Sentences assembled with .. are now format strings so translations can reorder them.

German translation

Locales/deDE.lua, complete at 452/452 keys. Terminology checked against the German client's own wording; a native-speaker pass on tone would still be welcome.

Locale linter

SP.L never fails at runtime, which is right — but it means the two mistakes translators actually make are silent. A mistyped key renders English forever and nobody notices; a dropped or reordered format specifier only errors at the far-away :format() call.

tools/locale-lint.ps1 reports both, plus duplicate keys, empty translations, a locale file whose SP.Locale argument doesn't match its filename, and one missing from the TOC. -Export <locale> emits a ready-to-fill stub. CI runs it on every PR.

Verified by seeding a throwaway locale file with six deliberate faults: all six fired, plus the TOC check, exit 1 — and the positional %1$d case correctly did not error.

Fixes the CI TOC check

It anchored on [A-Za-z0-9_/-]+\.lua, which stops dead at the backslash in Locales\deDE.lua. Every subdirectory entry was invisible to it, so a broken locale path would have passed green. Separators are normalized before matching and the check now walks subdirectories.

Also in this release

## Notes-<locale> headers for the ten translated locales, read by the in-game addon list and the CurseForge/Wago pages.

Known, not addressed here

  • Fonts\FRIZQT__.TTF is hardcoded in ten places. Blizzard ships locale-specific fonts for zhCN/koKR/ruRU, so a CJK translation would likely render tofu boxes. Unverified without those clients — worth resolving before adding those locales.
  • string.format("%.2f") always emits 10.65; German convention is 10,65.
  • %-12s in the gear report pads by bytes, not characters, so multi-byte slot names skew that column.

chrisdfennell and others added 9 commits July 23, 2026 11:31
The audit checked enchants, sockets and item level. Add three more
of the questions worth asking before a raid, all from unprotected
item data:

- Tier set count via C_Item.GetSetBonusesForSpecializationByItemID
  across the five tier slots -- there is no "N of 5" API, so we tally
  the slots whose item grants a set bonus for the current spec, which
  also counts Catalyst-made pieces.
- Upgrade track and progress per slot ("Champion 6/8"). No API
  returns this for an equipped item, so it is scanned from the item
  tooltip via C_TooltipInfo; best-effort and English-only by nature,
  guarded so a bad read never takes the audit down, and simply
  omitted when it doesn't match.
- Below-Epic gems, flagged like a missing enchant.

Surfaced in /sp gear, the Gear options page, and (tier count) the
chat announce. Enchant rank and embellishment were left out: both
need a per-season table or fragile matching and would rot silently.

API surface verified against warcraft.wiki.gg and a reference addon
before building. Not verified in-game -- the tooltip scan for the
upgrade track is the part most worth confirming live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
#	StatPanel.toc
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>
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.

@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: 5d1863c048

ℹ️ 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 tools/locale-lint.ps1
Comment on lines +180 to +182
if ($positional) {
# %1$s style: the translator has explicitly reordered, which is
# the supported way to do it. Only the count matters.

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 Reject unsupported positional format specifiers

When a translator follows the documented reordering advice and writes a value such as %2$d ... %1$s, this branch accepts it, but the addon's Lua 5.1 string.format implementation does not support the $ positional syntax and raises an invalid-format-option error at the affected UI call. The linter should reject these specifiers, or argument reordering must be implemented outside string.format, rather than certifying a translation that will fail at runtime.

Useful? React with 👍 / 👎.

Comment thread Announce.lua
Comment on lines 98 to +99
local priority = SP:GetCurrentPriority()
parts[#parts + 1] = "priority " .. table.concat(priority, " > ")
parts[#parts + 1] = L["priority %s"]:format(table.concat(priority, " > "))

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 Localize priority entries before joining them

On a German client with includePriority enabled, this produces a mixed-language announcement such as Priorität Haste > Crit > Mastery > Versatility: GetCurrentPriority() returns the canonical English identifiers, while only the surrounding format string is translated. Convert each entry through the localized stat display names before concatenating it; the same raw-list formatting also occurs in the pasted-priority confirmation in Options.lua.

Useful? React with 👍 / 👎.

@chrisdfennell
chrisdfennell merged commit f0a5b0f into main Jul 27, 2026
6 checks passed
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