docs(i18n): consumer sweeps, destructuring defaults, and unowned shared copy (#2204)
Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -296,6 +296,23 @@ trusting the count. Install/update flows and any maintenance action are the
|
||||
toast-heavy cases. A count from `pnpm run lint:i18n` is a floor on the visible
|
||||
half, never a work-list.
|
||||
|
||||
**Default values in a destructuring pattern are a third invisible shape, and the
|
||||
fix is not the obvious one.** `ActionButtonContent` carried three:
|
||||
|
||||
```tsx
|
||||
export function ActionButtonContent({
|
||||
pendingLabel = "Running...", // NOT flagged — not a literal in JSX
|
||||
successLabel = "Done",
|
||||
errorLabel = "Failed",
|
||||
}: Props) {
|
||||
```
|
||||
|
||||
These are evaluated in the parameter list, which runs *before* the component
|
||||
body, so a `const { t } = useTranslation()` in the body is not in scope for them —
|
||||
you cannot fix this in place. Resolve the fallback inside the body instead
|
||||
(`const pending = pendingLabel ?? t("system:jobRunning")`). Prop defaults on a
|
||||
shared presentational component are a common home for this.
|
||||
|
||||
`externalize-strings.mjs`
|
||||
covers those shapes — it walks `.ts` as well as `.tsx` and handles template
|
||||
literals and dialog/toast arguments — but **a zero from `pnpm lint` does not by
|
||||
@@ -490,6 +507,50 @@ While the migration is in flight, an unmigrated screen is expected to be full of
|
||||
plain English — pseudo only proves anything about screens whose components are on
|
||||
the `i18nGuardFiles` allowlist.
|
||||
|
||||
### Walk it by consumer, not by route
|
||||
|
||||
Which screens you walk matters as much as what you look for. A component's copy
|
||||
is verified by walking **every route that mounts it** — not the routes your
|
||||
migration owns.
|
||||
|
||||
The natural method fails here. Walking the import closure *from your own pages*
|
||||
traverses the graph in the wrong direction: it finds what your routes render, and
|
||||
by construction never finds a route that renders *you*. One migration hit it in
|
||||
both directions — the cards it converted mount only on a sibling migration's
|
||||
route, and a card that sibling owned mounts on its own. Neither could have been
|
||||
verified from its own pages alone.
|
||||
|
||||
`job-progress-indicator.tsx` is the worked example. It reported **0** findings,
|
||||
because its `Queued` / `Running` / `Done` / `Failed` come out of a plain `switch`
|
||||
in a non-JSX function, and it has four consumers spanning two directories
|
||||
(`database-stats-card`, `disk-usage-card`, `backups-table`, and Storage's
|
||||
`storage-quarantine-card`). Three separate migrations rendered it, and each left
|
||||
it — a component every one of them touched, and none of them owned. The System
|
||||
migration finally took it and walked Storage's already-merged route to confirm.
|
||||
|
||||
Per component you touch:
|
||||
|
||||
```bash
|
||||
grep -rn '<JobProgressIndicator' apps/web/app apps/web/components
|
||||
```
|
||||
|
||||
then walk each of those routes under pseudo, whoever owns them. If a consumer
|
||||
belongs to an unmigrated directory, note it in the guard comment rather than
|
||||
silently leaving it — the next migration inherits the answer instead of
|
||||
rediscovering the question.
|
||||
|
||||
### Copy that belongs to no directory
|
||||
|
||||
A file shared by *every* area has no natural owner and so gets skipped by all of
|
||||
them. `src/settings-routes.tsx` holds the live title and description for every
|
||||
settings route in one `SETTINGS_ROUTES` map — a SCREAMING_CASE identifier the
|
||||
guard skips entirely, in a 650-line file no single migration's closure claims.
|
||||
|
||||
The rule: a migration converts the entries it owns but must **not** add such a
|
||||
file to `i18nGuardFiles`, because allowlisting it claims a completeness nobody
|
||||
has. The last area to migrate adds it. Say so in your guard comment, naming which
|
||||
entries are still English and who owns them.
|
||||
|
||||
## Adding a real language
|
||||
|
||||
1. Add the locale to `SUPPORTED_LOCALES` + `LOCALE_LABELS` in
|
||||
|
||||
Reference in New Issue
Block a user