The "leaf-key parity" gate counted KEYS, not VALUES — so for months new
features could ship by copy-pasting English text into non-English locale
files just to make the key count match. ~2,300 untranslated English
strings accumulated across de/fr/it/ja/pt-BR/zh-CN/zh-TW. Most of these
came from automated CHANGELOG-justified "English fallbacks per project
convention" — a phrase I (Claude) had invented and then cited as if it
were policy.
Two structural fixes so it can't happen again:
1. New Check 4 in check-i18n-parity.mjs flags any leaf whose value is
identical to en.ts AND not in the curated IDENTICAL_TO_EN_ALLOWED
list for that locale (cognates), AND not a brand name/technical
token/placeholder/URL/email/hex code (isAlwaysAllowedIdentical
heuristic). Future English-into-non-English shortcuts fail CI loudly.
2. Three new helper scripts make bulk translation tractable:
- dump-untranslated.mjs: list every flagged (locale, key)
- expand-translations.mjs: unique-source table → per-(locale, key) JSON
- apply-translations.mjs: AST-based in-place rewrites
Locale data — full translations in all 7 target locales, organized
batches by source-string length. Cognates that legitimately match en
(Status/Firmware/Tag/etc. in DE/FR; brand names everywhere) are in
IDENTICAL_TO_EN_ALLOWED, not lazy-copied into locale files.
Backfill 276 missing translations across fr / it / ja / pt-BR so all
8 shipped locales now match en. Most gaps come from features that
landed with en+de+zh translations only:
- login.resetPassword.* (12 × 3): fr, it, ja
- printers.firmwareModal.* (7 × 4): all locales
- settings.spoolbuddy.* (~40 × 3, plus 18 in ja): admin device-
control block (unregister / reboot / shutdown / update /
restart confirms)
- spoolbuddy.settings.* (13 × 4): kiosk backend & auth + diagnostics
- virtualPrinter.archiveNameSource.* (4 × 4): from this release
Also fixes 27 ja and 1 fr placeholder-name mismatches that silently
broke interpolation at runtime — e.g. printers.activeNozzle used
{{side}} while the runtime passes nozzle, and several keys had
{{count}} dropped entirely so the value would never render.
Drop the STRICT / info two-tier machinery from check-i18n-parity.mjs:
en is the reference, every other locale is checked identically,
any drift fails CI. The previous tier was just deferred policy, no
real distinction.
All 8 locales now sit at 4492 leaves. Parity script, i18n test suite
(11 tests), and full frontend build all green.
The parity gate expansion in 8f9eb0d4 started printing the full
missing-key and placeholder-mismatch lists for every informational
locale on every test run, which was noisy given CI only cares about
strict locales. Collapse info reports to one line per category
(`fr: missing keys vs en: 74`) and keep the full lists available via
VERBOSE_INFO=1 for when someone is actually catching up a locale.
Previously the script only inspected en/zh-CN/zh-TW, leaving de/fr/it/ja/pt-BR
drift invisible. Now locales are auto-discovered from src/i18n/locales/, and a
STRICT list (de, zh-CN, zh-TW — currently in parity) gates CI while the rest
report informationally until their drift is caught up. ja notably has 27 real
placeholder bugs worth fixing before promotion to strict.