Console i18n: catalogs and the parity tool¶
The console reads web/messages/<locale>.json. en.json is the source of truth and the runtime fallback
(app/lib/i18n.tsx resolves a key in the active locale, then in English). The 29 other locale files are
machine-translated subsets and are not expected to be complete.
Adding strings¶
Put new strings in web/messages/en.json, in a new namespace for a new screen. Do not hand-edit the other
locale files: npm run translate-messages (web/scripts/translate-messages.mjs) asks an LLM to translate the
keys that are missing or stale in each locale.
Parity tool¶
web/scripts/i18n-parity.mjs (npm: i18n:parity, i18n:parity:check).
| Command | Effect |
|---|---|
node scripts/i18n-parity.mjs |
per locale: coverage (share of en.json leaf keys present), missing count, dead keys |
... --prune |
removes locale keys that no longer exist in en.json; namespaces left empty are dropped; formatting and key order are preserved (files are JSON.stringify(_, null, 2) plus a newline) |
... --check |
ratchet against messages/i18n-parity-baseline.json (no workflow under .github/workflows runs it yet; run it before a change that touches messages/) |
... --update |
rewrites the baseline; refuses to loosen it unless --force |
A "dead" key is a leaf in a locale file with no counterpart in en.json (left behind when English keys are
renamed or removed). Arrays count as leaves.
Ratchet rules¶
- Coverage per locale may only go up (tolerance 0.01 percentage points).
- Dead keys per locale may only go down. After renaming or deleting English keys, run
--prune. - Adding English keys lowers every locale's coverage. When that exceeds the tolerance, either translate or
re-baseline deliberately with
--update --forcein the same change, saying so in the commit message.
Numbers (2026-10-15)¶
Before the first prune each locale carried 6,855 to 7,138 dead keys (202,300 in total) against 12,780 English keys; coverage was 53.0% to 55.0%. After: 0 dead keys in all 29 locales, coverage unchanged (the pruned keys were never read), about 202k lines removed from the locale files.
Agent-control screens: every key in every locale¶
The runtime settings, settings catalog copy, approvals queue, prompt registry, turn timeline, eval controls, agent Behavior
tab and "Where do I control...?" screens must never show a raw key in any language. web/scripts/ui-i18n.mjs keeps that
true without waiting for translators:
node scripts/ui-i18n.mjs --checkfails when a locale file lacks a key of those screens (the namespaces inUI_NAMESPACES, plus any key added to another namespace sinceBASELINE_REF).web/app/lib/uiI18n.test.tsruns the same check in vitest.--filladds every missing key to every locale with the English text as the fallback, touching nothing else.--todorewrites ui-translation-todo.md: the keys (with their English text) that still need a human translation, and a per-locale count.
When you add a message to one of those namespaces: add it to en.json, run --fill, then --todo. A setting's copy
(settings.<key>.label|description|help) is also checked against the real catalog by
web/app/admin/runtime-settings/settingsCoverage.test.tsx.