refactor(i18n): make the English text the message id
What & why
Follows #7091 (merged). Moves every surface to Lingui's default mode, where the English text is the message id, and deletes the machinery explicit ids required.
With explicit ids, editing English under a stable id left every translation in place and non-empty, so compile --strict saw nothing wrong and the old wording shipped. A custom stale-translation audit (plus an exemptions file and a full-history checkout in CI) existed only to catch that. With the text as the id, an English edit creates a new entry that is empty in every locale and the strict compile catches it natively. Identical English also collapses into one entry: 6,883 catalog entries become 4,894.
What changed:
- Codemod over 971 source files.
<Trans id="…">→<Trans>;t({ id, message })andmsg({ id, message })drop the id; runtimei18n._({ id, message })becomesi18n._(msg({ message })), withvaluesspread into the descriptor where present, since Lingui 6 types_as taking a descriptor alone. Three hand-built descriptor objects that were never extracted becomemsg()calls and are translated for the first time. Descriptors whose message is a variable keep their runtime id; they were never in the catalog. - Catalogs re-keyed by English text. A carry-over script mapped every old id to its text and voted where several ids shared one text: 78,096 translations carried over, 703 votes had disagreeing old translations (mostly the same word translated inconsistently, e.g. German "Home" as "Start" under five ids and "Home" under one), ties preferring a translation that is not just the English. 13 strings that first entered the catalog here were translated by hand across all 16 locales.
- Contexts where one English word meant two things. "View" (app menu vs. button), "Plan" (billing vs. agent mode), "Exploring" (roadmap status vs. agent activity), "Open" (pull-request status vs. action), "Home" (directory vs. navigation), and "Unknown" (a person vs. a branch or task, which French genders differently) carry a
contextand keep their separate translations. - Every build runs the macro. The renderer, the Next apps, and mobile already did. The Electron main process gets
linguiMacroPlugininapps/desktop/vite/helpers.ts; the bun-compiled CLI, host-service, and desktopbundle:cliget a Bun bundler plugin exported from@superset/i18n/bun-plugin(forwarded through a newpluginsoption in cli-framework); the API's Next config gets the same SWC plugin its siblings use.apps/desktop/scripts/verify-single-flight.tsmocks the macro alongside its electron mocks. - Deleted:
check-stale-translations.tsand its test,locales/en-only-changes.txt, thefetch-depth: 0checkout in CI's Lint job.checkis now extract + strict compile. - One test shim for
@lingui/core/macro.bun testruns uncompiled source, where the macro entry point throws.packages/i18n/test-setup.tsderives ids with Lingui's owngenerateMessageId, so tests that load a real catalog get real translations; shared, cli, marketing, and mobile preload it by relative path. Desktop and ui keep their existing React-macro shims.@lingui/coreis declared where the macro is now imported (packages/shared,apps/docs). - Catalog integrity test.
packages/i18n/test/catalog-integrity.test.tschecks every translation keeps its{placeholders},<0>…</0>tag markers, and ICU plural/select shape. Strict compile only checks that an entry exists; this is the validation the deleted translator used to do for machine fills, now applied to hand-written ones. It found two messages whose Japanese, Chinese, and French translations had lost their second line on main (the merge confirmation's "This merges into {2}…" and the sign-in legal text's Terms and Privacy links); those are fixed here, plus the German versions that were still English. - Docs:
AGENTS.mdandpackages/i18n/README.mddescribe the text-as-id workflow and when to add acontext.
The 120k-line catalog diff is the re-keying itself; the compiled messages.ts files keep the same message texts under hashed ids.
How I tested it
bun run check:i18n: strict compile passes with zero missing entries across 16 locales, and a second run leaves the catalogs unchanged.bun run typecheckacross the monorepo: 0 errors.bun run lint: clean.bun run test(turbo): all packages pass, including desktop (3,286), shared (920), ui (60), i18n (100).- The main-process plugin run directly over
src/main/lib/menu.tsemits 21 hashed ids and leaves nomsg()calls. - German spot checks after carry-over: plain "View" → Ansehen, menu "View" → Ansicht; plain "Plan" → Plan, billing "Plan" → Tarif; "Restart" → Neu starten (the tie-break no longer picks the untranslated copy).
Checklist
- PR title follows conventional commits (
type(scope): subject) -
bun run lintandbun run typecheckpass (CI fails on lint warnings too) - "Allow edits from maintainers" is checked on fork PRs