fix(i18n): make real-locale catalog parity advisory, not a gate (#2272)
Co-authored-by: Kandev Agent <agent@kandev.dev>
This commit is contained in:
+53
-5
@@ -44,8 +44,12 @@ Two enforcement layers, and it matters which is which:
|
||||
`sidebar`, `slack`, `system`, `workflows`, and `workspaces`. Each migrated area
|
||||
can add the namespace it needs (`statusBar`, `kanban`, `task`, …). A string used
|
||||
in two or more areas belongs in `common` so it is translated once. A new
|
||||
namespace needs no registration: `lib/i18n/index.ts` discovers catalogs by glob,
|
||||
but every real locale must add the same namespace in the same change.
|
||||
namespace needs no registration: `lib/i18n/index.ts` discovers catalogs by glob.
|
||||
|
||||
Adding the same namespace to every real locale in the same change is the ideal,
|
||||
but it is **not required and does not gate** — translation is handled out of
|
||||
band, so `i18n:check` reports a real-locale gap as a warning. See
|
||||
[Real locales warn, they do not gate](#real-locales-warn-they-do-not-gate).
|
||||
|
||||
### Interpolation
|
||||
|
||||
@@ -841,9 +845,10 @@ case variants such as `zh-CN`, but cookies and `<html lang>` use the canonical i
|
||||
tests for canonicalization, cookie and `Accept-Language` negotiation, message
|
||||
lookup, exact catalog parity, and the first shell response.
|
||||
5. Run `pnpm run i18n:check` from `apps/web`. It discovers every real locale
|
||||
except `en` and generated `pseudo`, then fails on any missing or extra
|
||||
namespace or key. Regenerate `pseudo` only from English with
|
||||
`pnpm run i18n:pseudo`; never copy a human translation into it.
|
||||
except `en` and generated `pseudo`, and **warns** on any missing or extra
|
||||
namespace or key — advisory, not a build failure; see below. Regenerate
|
||||
`pseudo` only from English with `pnpm run i18n:pseudo`; never copy a human
|
||||
translation into it.
|
||||
6. In a development or E2E build, select the new language under **Settings >
|
||||
General > Appearance**, reload, and switch back to English. Confirm translated
|
||||
copy, the locale cookie, `<html lang>`, interpolation, layout, and then use
|
||||
@@ -853,3 +858,46 @@ Kandev's migration remains incremental. A complete locale catalog guarantees
|
||||
that every currently externalized message has a translation; it does not mean
|
||||
that every screen has been externalized. Unmigrated surfaces continue to render
|
||||
English until their directory joins `i18nGuardFiles`.
|
||||
|
||||
### Real locales warn, they do not gate
|
||||
|
||||
`en` and `pseudo` gate. `en` is authored in the same PR as the code and `pseudo`
|
||||
is **generated** from it, so a mismatch between them is always the author's to
|
||||
fix in the change that caused it. Real-locale parity is a **warning**.
|
||||
|
||||
That is a deliberate policy, not an oversight. Translation happens out of band,
|
||||
on a third party's cadence, so gating on it makes an ordinary English-only PR
|
||||
fail CI for work nobody in the merge path can do. It is not hypothetical: #2243
|
||||
introduced `zh-cn` together with the parity gate, #2261 added 13 English `chat`
|
||||
keys twenty minutes later, and `main` went red — not because either change was
|
||||
wrong, but because the two could not have been ordered to avoid it. The gate
|
||||
fired exactly as designed and blocked the wrong people.
|
||||
|
||||
So: **add the keys to every real locale when you can, and never let a missing
|
||||
one stop you merging.** The warning is addressed to whoever owns the
|
||||
translation.
|
||||
|
||||
#### It is a structural check, not a translation check
|
||||
|
||||
Worth stating plainly, because assuming otherwise has already produced two wrong
|
||||
conclusions: a green run does **not** mean "translated". The pass compares
|
||||
shapes. Measured, one mutation at a time:
|
||||
|
||||
| mutation | result |
|
||||
|---|---|
|
||||
| value identical to English | **not caught** |
|
||||
| whole catalog copy-pasted from `en` | **not caught** |
|
||||
| swapped `_one` / `_other` | **not caught** |
|
||||
| empty / whitespace / non-string value | caught |
|
||||
| missing key, extra key | caught |
|
||||
| missing namespace, extra namespace | caught |
|
||||
| dropped `{{placeholder}}` | caught |
|
||||
| dropped or renumbered `<n>` tag | caught |
|
||||
|
||||
The identical-to-English tolerance is deliberate and cannot be tightened
|
||||
naively: brand nouns and technical literals must stay verbatim (`automations`
|
||||
alone has seven — `GitHub`, `Webhook`, `Webhook URL`, …), so `value === en` would
|
||||
report each of them. Closing it needs a per-key exemption list or a
|
||||
whole-file proportion threshold. Reviewing a translation is a human job;
|
||||
`scripts/check-i18n-keys.test.ts` pins both the caught and the uncaught cases so
|
||||
this stays honest.
|
||||
|
||||
Reference in New Issue
Block a user