build(i18n): generate compiled Lingui catalogs instead of committing them
Summary
- Stop committing the compiled Lingui catalogs (
packages/i18n/locales/*/messages.ts); generate them from the.posources instead. - Wire
lingui compileinto the build graph so they exist everywhere they're read: turbo^buildfor dependents, a self-dependency for i18n's own typecheck/test, and postinstall for the paths that bypass turbo.
Why / Context
lingui compile emits one minified line per locale — 248 KB on a single line for en, zero newlines. Git's merge is line-based, so a one-line file can never be three-way merged: if two branches both touched it, that's a whole-file conflict, 100% of the time.
The result was that every PR adding any string conflicted with every other PR adding any string, on all 17 compiled catalogs, regardless of what the strings were. #7179 hit it against three unrelated PRs (#6901, #7176, #7175) that had landed while it was open. The resolution is never a hand-merge — it's "take the merged .pos and regenerate" — which is a strong hint the file shouldn't be in git at all.
The .po sources merge cleanly and always did, because packages/i18n/lingui.config.ts sets orderBy: "message" and origins: false, keeping them deterministic, alphabetically-ordered, multi-line text.
Lingui's own CLI docs say not to commit the compiled output:
"The compiled files can be safely ignored by your version control system, since these files must be created each time you deploy to production."
"We recommend you to create the compiled catalogs in CI as part of your deployment process."
How It Works
packages/i18n gains a build script that runs compile only — extract rewrites the .po sources from app code and has no business running inside a build. Three entry points then cover everything that reads the catalogs:
| path | covered by |
|---|---|
turbo typecheck / test / build in any dependent | root pipeline's existing ^build |
@superset/i18n's own typecheck / test | new packages/i18n/turbo.json, self-dependency on build |
turbo dev (incl. Metro/Expo) | root dev gains dependsOn: ["@superset/i18n#build"], scoped to that one package so dev start doesn't drag in every other build |
starting an app directly (cd apps/mobile && bunx expo start) | a predev hook on each app that imports @superset/i18n |
packages/cli build:dist (invoked directly, not via turbo) | a prebuild:dist hook |
bun run --filter=<pkg> typecheck on a fresh clone | scripts/postinstall.sh — local only, CI installs with --ignore-scripts in 26 steps |
The package-level turbo.json also declares inputs (locales/*/messages.po, lingui.config.ts) and outputs (locales/*/messages.ts) so the compile step caches instead of re-running on every task.
postinstall's compile is deliberately non-fatal — a missing translation must fail a real build, not bun install. It prints a pointer to bun run check:i18n and moves on; the strict compile in the build graph is what actually gates.
Note that postinstall covers local clones only: CI installs with --ignore-scripts almost everywhere. Anything CI builds must therefore reach the catalogs through turbo ^build or an explicit pre-hook — which is why build:dist got one after Build CLI caught this on Linux.
Manual QA
Verified by deleting all 17 compiled catalogs and running each entry point cold:
-
./scripts/postinstall.sh→ 17 catalogs regenerated -
turbo run typecheck --filter=@superset/i18n→ runsbuildfirst, then typechecks; 17 regenerated -
turbo run typecheck --filter=@superset/desktop→ 6 tasks successful; 17 regenerated -
turbo run build --filter=@superset/desktopfrom zero catalogs - CI's exact gate —
bun run check:i18n && git diff --exit-code --stat HEAD -- packages/i18n/locales— passes clean, and still tracks all 17.pofiles, so stale-catalog drift is still caught -
git check-ignoreconfirms the compiled catalogs are ignored;git ls-filesshows 0 trackedmessages.ts, 17 trackedmessages.po - Direct dev is not stale: edited
en/messages.po, ranapps/web'spredev, confirmed the new message reached the compiled catalog. The same probe before the predev hooks showed the catalog unchanged — i.e. a direct dev start served the previous compile. - Conflict class is gone: two throwaway branches each inserting a message at a different alphabetical position in
en/messages.po(the wayextractwrites them) now merge with zero conflicts. The identical experiment before this change conflicted on all 17messages.ts.
Testing
bun run lint— cleanbun run --filter=@superset/i18n test— 120 passbun run check:i18n— cleanbun turbo run typecheck --filter=@superset/desktop— cleanbun turbo run build --filter=@superset/desktop— clean from a zero-catalog state
Risks / Rollout
- Risk: a path that reads the catalogs without going through turbo, predev, or postinstall would now see missing files. The four covered entry points above are the ones I could find; the failure mode is a loud
Cannot find module '../locales/en/messages'at typecheck, not a silent runtime bug. - Rollout: no flag, no ordering constraint. First
bun installor any turbo task after merging regenerates locally. - Rollback: revert. The compiled catalogs come back under version control and the conflict class returns with them; nothing is lost, since they're fully derived from the
.pofiles that stay tracked.
Follow-ups
- The other Lingui friction is unaddressed here and worth its own look: the CLI silently no-ops under Node <22.18 and
compile --strictexits 0 under Bun's worker pool (why our scripts pinbunx --bun … --workers 1), andbun testneeds macro shims inapps/desktop/test-setup.tsbecause macros are compile-time.
https://claude.ai/code/session_01DNoj8JhjGwTRs9AoxYYRB9
Summary by CodeRabbit
-
Build & Development
- Translation catalogs are now compiled automatically before development servers, builds, type checks, and tests.
- Localization files are generated during installation to improve setup reliability without blocking installation if generation fails.
- Updated application and command-line workflows ensure translations are available before startup or packaging.
-
Chores
- Generated translation catalog files are now excluded from version control.