docs: correct the web localization status for the finished i18n work (#2400)

Co-authored-by: Kandev Agent <agent@kandev.dev>
This commit is contained in:
João Salavisa
2026-08-07 21:45:12 +02:00
committed by GitHub
parent 4f34dc682c
commit 9ed977a3fe
4 changed files with 42 additions and 19 deletions
+7 -3
View File
@@ -6,9 +6,11 @@ European Portuguese (`pt-pt`) and Simplified Chinese (`zh-cn`) are the shipped
human locales, and a generated **pseudo-locale** is used for QA. Requirements live in
[`docs/specs/platform/i18n.md`](specs/platform/i18n.md).
The migration is **in progress, one directory per PR**. The runtime, the gates,
and Settings → General → Appearance are done; everything else still holds
English literals.
The migration is **complete**. Every routed web UI surface renders through `t()`
/ `<Trans>`, and `i18nGuardFiles` now enumerates the directories that hold them.
Running the guard across `components app hooks lib` reports literals in only the
unrouted QA pages under `app/demo/`. New copy is still guarded everywhere, so a
hardcoded literal is now a regression rather than unfinished migration.
Two enforcement layers, and it matters which is which:
@@ -176,6 +178,8 @@ pnpm run i18n:pseudo # regenerate the pseudo catalog from en
pnpm run lint:i18n <path> # preview the guard on a path that is NOT yet migrated
pnpm run i18n:ratchet # new-code guard + allowlist-only-grows (pre-commit + CI)
pnpm run i18n:removed # did this change rewrite any English? (diff-based, advisory)
pnpm run i18n:parity # per-locale missing/extra keys (report, never a gate)
pnpm run i18n:sweep <path> # copy the jsx-only rule cannot see (report, never a gate)
# Codemods — always inspect the diff; each declines what it cannot prove safe.
node scripts/externalize-strings.mjs --write components app hooks lib
+1 -1
View File
@@ -75,7 +75,7 @@ Released builds can lag this page. Check **Settings > System > About** or `kande
| Language servers | Limited | Desktop Monaco maps TypeScript/JavaScript, Go, Rust, Python, and experimental Kotlin to task-host servers with diagnostics, completion, hover, navigation, signature help, and semantic tokens. Status distinguishes a launched process awaiting `initialize` from server-reported project work; slow Kotlin Gradle imports can take minutes, with no automatic timeout or universal percentage/ETA. Fine-pointer desktop can place active-file status in the editor toolbar or Application status bar; touch tablet falls back to the toolbar, and phone has no LSP control. Auto-start/installation default off; Kotlin is manual-only. Local PC and Local Docker are supported, while SSH, Sprites, remote Docker, and mobile LSP are not. |
| Integrated shell terminals | Dependency-bound | Create, reopen, rename, and terminate are shipped. Shell settings affect new terminals, and executor/task-environment availability controls where the PTY runs. |
| Prompts, keyboard shortcuts, notifications, appearance, and task actions | Supported | These are normal settings surfaces. Browser notification permission, custom command validity, and local shell/editor availability still apply to their effects. |
| Web UI localization | Limited | **Settings > General > Appearance** offers English and 简体中文. The selection persists across reloads, but localization migration is incremental: currently externalized surfaces use the selected language while unmigrated surfaces remain in English. |
| Web UI localization | Limited | **Settings > General > Appearance** offers English, Português (Portugal), and 简体中文. The choice applies immediately, persists per browser through a `kandev_locale` cookie, and is negotiated from `Accept-Language` before defaulting to English when no cookie is set. Externalization is complete: every reachable web UI surface renders through the translation layer, and dates, numbers, and relative timestamps follow the selected locale. Per-language completeness still differs. Portuguese tracks English closely; Simplified Chinese is maintained out of band and lags, most visibly in the task workbench. An untranslated key falls back to English rather than failing. About fifteen shared `@kandev/ui` accessibility labels — mostly screen-reader-only text such as a dialog's **Close** — stay English even where the visible UI is translated. Desktop-shell and CLI surfaces, API/log diagnostics, and agent output were never in scope; user content such as task titles, chat, and diffs is never translated. Development and E2E builds list an extra **Pseudo (QA)** locale that production builds do not ship. |
</details>
+17
View File
@@ -81,6 +81,23 @@ Chat, plan, changes, files, terminal, editor/diff, preview, commit, and pull-req
- a restored dock layout and a narrow mobile route;
- absent optional providers or executor capabilities.
## Localize user-facing copy
Every new user-facing string must go through i18next — `t("namespace:key")` or `<Trans>` — never an inline literal. That covers visible copy, `placeholder`, `aria-label`, `title`, `alt`, toasts, validation and empty-state text, and dialog copy. Externalization of the existing UI is complete, so a hardcoded literal is now a regression rather than unfinished migration.
Catalogs live in `apps/web/src/locales/<locale>/<namespace>.json`. English is the source locale and the only hand-authored catalog; it is bundled into the entry chunk, while each other locale is a lazily fetched chunk loaded before that locale activates. Shipped locales are English, European Portuguese, and Simplified Chinese, plus a generated `pseudo` catalog that only development and E2E builds contain. A missing key falls back to English at runtime.
Three mistakes cause silent bugs: never translate a string compared with `===`, never call `t()` at module scope, and never pass an English plural ending as a value — use `count` with `_one`/`_other` keys.
```bash
pnpm run i18n:check # gate: plurals, module-scope t(), Trans indices
pnpm run i18n:ratchet # gate: no new hardcoded literals in changed lines
pnpm run i18n:parity # advisory report: per-locale missing and extra keys
pnpm run i18n:sweep <path> # advisory report: copy the jsx-only lint rule cannot see
```
The first two run in pre-commit and CI. The last two are reports that always exit zero and are wired into neither, because real-locale translation is maintained out of band and must not fail an English-only change. A clean lint does not prove a file is done: the rule only inspects JSX literals and skips anything assigned to a SCREAMING_CASE identifier, so review config tables by eye and use the pseudo-locale (**Settings > General > Appearance** in development builds) as the completeness check. Shared `@kandev/ui` primitives cannot import the app's i18n runtime, so their accessibility labels are localized at the call site. Full guide: [`docs/i18n.md`](../i18n.md).
## Security, accessibility, and responsiveness
Frontend checks improve the experience; they are not authorization. The backend must enforce identity, workspace/task scope, and destructive operations. Treat provider text, repository content, Markdown/HTML, URLs, and agent output as untrusted. Keep raw HTML rendering paired with the existing sanitizer and never interpolate shell commands or credentials in the browser.
+17 -15
View File
@@ -21,13 +21,13 @@ language and the pseudo-locale as the migration QA oracle.
`alt`, toast/`sonner` messages, form-validation messages, empty-state and
error copy, and confirm/dialog text.
- English SHALL remain the source locale. The shipped human locales SHALL be
`en` and `zh-cn`; copy lives in per-namespace JSON catalogs keyed by stable
ids. The `pseudo` catalog is generated from `en`, never hand-edited.
- The app SHALL expose a **language switcher** in Settings. It lists English and
Simplified Chinese by their fixed endonyms. Selecting a locale re-renders the
UI without a full reload and persists the
choice across restarts. Development and E2E builds additionally list the
**pseudo-locale** (see below).
`en`, `pt-pt`, and `zh-cn`; copy lives in per-namespace JSON catalogs keyed by
stable ids. The `pseudo` catalog is generated from `en`, never hand-edited.
- The app SHALL expose a **language switcher** in Settings. It lists English,
European Portuguese, and Simplified Chinese by their fixed endonyms. Selecting
a locale re-renders the UI without a full reload and persists the choice across
restarts. Development and E2E builds additionally list the **pseudo-locale**
(see below).
- The app SHALL ship a **pseudo-locale** (`pseudo`) that transforms every
extracted message (accents + padding, e.g. `Ĉřéàţé ţàśķ`) so QA can visually
confirm a string was externalized and detect truncation. The pseudo-locale is
@@ -95,12 +95,12 @@ merge. Sequence:
i18n adds no server-side persistent entities. It introduces:
- **Message catalogs** — checked-in JSON under
`apps/web/src/locales/<locale>/<namespace>.json` for `en`, `zh-cn`, and
`pseudo`. `en` is hand-authored (keys are stable ids; the English copy lives
only here); `zh-cn` is a human-maintained translation, maintained out of band,
whose namespace and key sets aim to mirror `en` but may lag it — a gap warns
rather than failing, and i18next falls back to `en` at runtime; `pseudo` is
generated from `en` by
`apps/web/src/locales/<locale>/<namespace>.json` for `en`, `pt-pt`, `zh-cn`,
and `pseudo`. `en` is hand-authored (keys are stable ids; the English copy
lives only here); `pt-pt` and `zh-cn` are human-maintained translations,
maintained out of band, whose namespace and key sets aim to mirror `en` but may
lag it — a gap warns rather than failing, and i18next falls back to `en` at
runtime; `pseudo` is generated from `en` by
`scripts/generate-pseudo-locale.mjs` and is never hand-edited. Catalogs load at
runtime via Vite glob import — no build plugin. Only `en` is bundled into the
initial download: it is the fallback locale, so it has to be in memory the
@@ -177,7 +177,8 @@ Frontend module contract (new):
- **GIVEN** a plural key (`x_one` / `x_other`) rendered with a `count`, **WHEN**
count is 1 vs many, **THEN** i18next selects the matching form.
- **GIVEN** a production build, **WHEN** the language switcher renders, **THEN**
English and 简体中文 are offered and the pseudo-locale is not.
English, Português (Portugal), and 简体中文 are offered and the pseudo-locale
is not.
- **GIVEN** an English namespace or key changes, **WHEN** `i18n:check` runs,
**THEN** every real locale missing or adding a namespace or key is reported as
a warning naming the locale, namespace, and missing or extra entries, **AND**
@@ -210,7 +211,8 @@ Frontend module contract (new):
switching locale is a pure client re-render.
## Out of scope
- Shipping human languages other than English and Simplified Chinese.
- Shipping human languages other than English, European Portuguese, and
Simplified Chinese.
- Translating the CLI launcher, logs, or ACP/agent output, and the ~1,100
diagnostic API error strings (`http.Error` / `gin.H{"error": …}`) — the SPA maps
API failures onto its own translated copy, so those stay English by design.