localization: the 'what exists today' table described a world three phases ago
Every row was wrong — bare en.json, 46 keys, three components, 5% deployed — and
it is the first thing whoever picks up phase 3 reads. platform-59 flagged it and
left it alone rather than colliding with me mid-edit, which was the right call.
Kept as a before/now table rather than replaced. The shape of the gap is the
argument for the phases, and deleting the 'before' column would leave them looking
arbitrary.
Also records two things the plan did not anticipate, both found by doing the work:
TranslationKeys walked into a plural object so t('key', {count}) — the whole point
of D7 — would not compile while the workaround did, which no test could have caught
because phase 1 had no plurals. And the typed wrappers rebuilt t every render,
which is the trap CLAUDE.md already documents against useClient().
And the shape this catalogue format cannot express: a sentence with an inline link
splits into a key whose Portuguese value is '.'. Flagged rather than solved, since
a node-level interpolation helper is a design decision.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+34
-25
@@ -25,35 +25,44 @@ the server does instead, which is the one decision that reaches outside this bou
|
|||||||
|
|
||||||
## What exists today, and what it cannot do
|
## What exists today, and what it cannot do
|
||||||
|
|
||||||
There is already an i18n system. It is small, hand-rolled, has no npm dependency, and it works. It is
|
**Updated 2026-08-23, after phases 0, 1 and 2.** This section described the pre-phase-0 world for long
|
||||||
also about **5% deployed**, and that gap is the whole job.
|
enough that the next reader would have started from a false picture — bare `en.json`, 46 keys, three
|
||||||
|
components. All of that is now history and is kept below only as the "before" column, because the shape of
|
||||||
|
the gap is the argument for the plan and deleting it would leave the phases looking arbitrary.
|
||||||
|
|
||||||
| | |
|
| | before phase 0 | now |
|
||||||
| ----------------------------------------- | -------------------------------------------------------------------------- |
|
| -------------------------- | ---------------------- | --------------------------------------------- |
|
||||||
| `src/workspaces/i18n/` | the package: `I18nProvider`, `useTranslation`, `resolveKey`, `interpolate` |
|
| catalogs | `en.json`, `pt.json` | `en-GB.json`, `pt-PT.json` per workspace |
|
||||||
| `src/apps/officer-web/lib/i18n.ts` | a typed wrapper binding keys to `en.json` |
|
| keys | 46 | **581** — 538 `app.*`, 43 `ui.*` |
|
||||||
| `src/apps/officer-web/lib/I18nBridge.tsx` | reads `settings.languages.default`, feeds the provider |
|
| workspaces with a catalog | 1 (`officer-web`) | 3 (`officer-web`, `components`, `officerdev`) |
|
||||||
| `src/apps/officer-web/locales/` | `en.json` and `pt.json` — **46 keys each, in sync** |
|
| components using it | 3 | all of `officer-web` and `components` |
|
||||||
| components actually using it | **3** — `UserMenu`, `Languages`, `ChangePassword` |
|
| plural rules | none | `Intl.PluralRules`, per locale |
|
||||||
|
| dates and numbers | browser locale | `useFormat()`, the account's locale |
|
||||||
|
| a missing key | rendered its dot-path | falls back to English, warns in dev |
|
||||||
|
| locales offered vs shipped | ~20 offered, 2 existed | only shipped locales are offered |
|
||||||
|
| parity enforcement | none | **a gap fails the build** |
|
||||||
|
|
||||||
What it already does well, and which this plan keeps: dot-path lookup, `{{param}}` interpolation,
|
Still standing from the original description, and still true: the package is hand-rolled, has no npm
|
||||||
fallback to `en`, and **type-safe keys** via `TranslationKeys<typeof en>` — a wrong key is a
|
dependency, and gives type-safe keys — a wrong key is a typecheck failure, which is worth more than it
|
||||||
typecheck failure, which is worth more than it sounds when one agent is editing 400 files.
|
sounds when one agent is editing hundreds of files.
|
||||||
|
|
||||||
**What it cannot do yet**, each of which is a task below:
|
**What remains:** `src/workspaces/officerdev` — the panels — and then translation. `panel.*` is scaffolded,
|
||||||
|
wired into the bridge, and empty.
|
||||||
|
|
||||||
1. **No plural rules.** `1 file` / `2 files` has no expression. `Intl.PluralRules` is built into the
|
Two things learned doing phases 1 and 2 that the plan did not anticipate:
|
||||||
runtime and unused.
|
|
||||||
2. **No locale-aware dates or numbers.** ~23 `toLocaleDateString/TimeString/String()` call sites use
|
- **`TranslationKeys` walked into a plural object**, so `t('key', { count })` — the entire point of D7 —
|
||||||
the _browser's_ locale, not the user's chosen one, so a Portuguese UI shows American dates.
|
did not compile while `t('key.one')` did. The documented feature was uncompilable and the workaround was
|
||||||
3. **No fallback chain.** `resolveKey` falls back to `en` and nothing else, so `pt-BR` cannot inherit
|
not, which no test would ever have noticed because phase 1 contained no plurals. Fixed with type-level
|
||||||
from `pt`.
|
assertions.
|
||||||
4. **One catalog, one workspace.** `locales/` lives inside `officer-web`. The 213 panel files in
|
- **The typed wrappers rebuilt `t` on every render**, which is the trap `CLAUDE.md` already records against
|
||||||
`officerdev` and the 91 in `components` have no catalog of their own.
|
`useClient()` and the Jellyfin player. It matters the moment `t` lands in a `useMemo` dependency list,
|
||||||
5. **Silent, ugly misses.** A missing key renders its own dot-path (`settings.profile.foo`) to the
|
which phase 2 needed immediately. Memoised.
|
||||||
user. No warning, no test.
|
|
||||||
6. **Twenty languages offered, two that exist.** The picker in `Languages.tsx` lists ~20 codes
|
**A shape this format cannot express:** a sentence with an inline link. Splitting around the anchor gives a
|
||||||
including Hebrew and Greek. Choosing any of the eighteen silently yields English.
|
key whose Portuguese value is `"."`. The cheap fix is rephrasing so the link falls at a clause boundary;
|
||||||
|
there is no node-level interpolation helper and adding one is a design decision, not a patch. Worth
|
||||||
|
raising if a phase turns out to be full of them rather than absorbing it forty times.
|
||||||
|
|
||||||
### The size of the remaining work
|
### The size of the remaining work
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user