docs(i18n): record the two blind spots the Agents and Storage migrations hit (#2205)
Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+69
-1
@@ -282,7 +282,19 @@ That is why module-scope config objects survived the sweep, and why the
|
||||
new-code ratchet inherits the same hole. `check-module-scope-t.mjs` catches the
|
||||
adjacent case (a module-scope `t()` call, frozen at the boot locale) but not
|
||||
module-scope literal copy. Reviewing a new `const TABLE = [...]` of labels by eye
|
||||
is still required.
|
||||
is still required — and note the shape one step out from a config table is just
|
||||
as invisible: **a plain function returning object literals**, e.g. a
|
||||
`buildRows()` that returns `[{ label: "Disk usage" }]`, is never inspected either.
|
||||
|
||||
**Walk the hooks your routes call, not just the components.** A closure derived
|
||||
from component imports misses them, and they hold no JSX so the string count
|
||||
reports them as zero. Settings → Agents shipped that way and needed a follow-up
|
||||
for a thrown `Error` and a `setError` fallback in two `hooks/domains/settings/`
|
||||
modules; Settings → System → Storage caught ten toast titles in
|
||||
`use-storage-maintenance.ts` only because it re-derived its closure instead of
|
||||
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.
|
||||
|
||||
`externalize-strings.mjs`
|
||||
covers those shapes — it walks `.ts` as well as `.tsx` and handles template
|
||||
@@ -391,11 +403,67 @@ through `t()` — that is the acceptance check for a migrated screen, and the on
|
||||
one that sees the guard's blind spots. Interpolation placeholders and `<n>` tag
|
||||
markers are preserved by the generator.
|
||||
|
||||
Two qualifications, both below: an **interpolated API or network payload** stays
|
||||
English by design, so it is not a miss even though it renders unaccented; and
|
||||
plain English is not a *complete* signal, because a payload inside a translated
|
||||
frame renders accented and reads as done.
|
||||
|
||||
It has one blind spot of its own: **module-scope `t()`**. That copy *is*
|
||||
translated, just frozen at the boot locale, so it renders accented under pseudo
|
||||
and looks correct while never responding to a switch. `check-module-scope-t.mjs`
|
||||
is what catches it.
|
||||
|
||||
### …and it is weakest at an interpolated value
|
||||
|
||||
Plain-English-on-screen is not a complete signal. When an un-migrated value is
|
||||
interpolated into a **migrated** frame, the frame renders accented and the
|
||||
payload reads as part of the sentence:
|
||||
|
||||
```text
|
||||
agents:unableToStartUpdate = "Unable to start update: {{error}}"
|
||||
→ Ữńàƀĺē ţō śţàŕţ ūþďàţē: Agent installation is already in progress.
|
||||
```
|
||||
|
||||
That scans as done. Both instances found this way survived a twelve-screen pseudo
|
||||
walk of the migrated surface before anyone noticed them.
|
||||
|
||||
So when a message interpolates a value, ask **where the value originates**. That
|
||||
distinction is the whole check, and it is a two-minute grep over the paths you
|
||||
migrated:
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
grep -rnE 't\("[^"]+", \{ (error|message)' components app hooks | grep -v '\.test\.'
|
||||
```
|
||||
|
||||
- **An API or network error payload stays English by design.** `String(err)` from
|
||||
a failed request, or a `result.error` the backend returned, is a diagnostic —
|
||||
see [Backend](#what-the-backend-deliberately-does-not-translate) for why the
|
||||
~1,100 server error strings are out of scope. Around 37 catalog keys already
|
||||
interpolate one; none of them is a miss. Where such a call needs a fallback for
|
||||
a missing payload, the *fallback* is copy and is translated
|
||||
(`t("linear:testFailed", { error: result.error || t("linear:unknownError") })`)
|
||||
while the payload itself is not.
|
||||
- **A client-side literal reaching a translated frame is a miss.** A string
|
||||
thrown, `setError`'d, or assigned in one of your own hooks or helpers is copy
|
||||
you wrote, and neither the lint rule nor the pseudo-locale can see it.
|
||||
|
||||
**A translated toast followed by an English `throw` is correct, not a miss.**
|
||||
This shape recurs across the migrated GitHub and Jira settings and should not be
|
||||
churned:
|
||||
|
||||
```tsx
|
||||
} catch {
|
||||
toast({ description: t("github:failedToSaveQuickActions"), variant: "error" });
|
||||
// Signals failure to the settings save coordinator, which only records the
|
||||
// contributor id — this message never reaches a user, so it is not copy.
|
||||
throw new Error("Failed to save quick actions");
|
||||
}
|
||||
```
|
||||
|
||||
The user already saw translated copy; the throw is control flow for
|
||||
`useSettingsSaveContributor`. Translating it would change nothing on screen.
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user