Repository navigation
feat(i18n): localize the whole app, with a complete zh-CN translation - #330
Conversation
|
Hey man, thank for the contribution. |
Wire every user-visible string to i18next across all components, stores, lib and hooks, and ship a full zh-CN locale. English output is byte identical to before, so existing e2e assertions hold. - src/lib/i18n.ts: react-i18next with bundled TS locale modules (no network, no Suspense), uiLanguage pref (system/en/zh-CN), live switch - eight namespaces (common/sidebar/settings/dialogs/task/panels/chrome/ backend); parity test pins en/zh-CN key + placeholder sets identical - components: useTranslation per string-rendering component; Trans for embedded markup; t added to memo deps only where recompute is safe - outside React (toasts, notifications, confirm dialogs, tab titles): i18n.t at event time; helpers that render for a component take t - data-testids, raw backend error passthroughs and brand names unchanged See docs/i18n.md for the wiring rules and conventions. Co-Authored-By: Claude Code <noreply@anthropic.com>
Follow-up to the whole-app localization for everything the newer upstream surfaces added: task groups and their menus, the project task filter, the delegated-work marks and legend, the checkout picker, the machine- and project-level YOLO defaults, and the env hints reworked for a second <code> element. Also carries the Clear filter button in the sidebar's empty filter state. Co-Authored-By: Claude Code <noreply@anthropic.com>
The active language defaults to "system", which follows navigator.language, and Node >= 21 exposes a real navigator with the machine's locale. On a zh-CN machine every user-visible string renders as Chinese and assertions matching on English catalog text fail, which reads like an app bug but is pure environment. The suites are written against the English catalog (and CI is an en machine), so pin it here; src/locales/parity.test.ts covers the other language's keys. Co-Authored-By: Claude Code <noreply@anthropic.com>
Two v1.8.3..v1.9.1 additions carried hardcoded English:
- SpawnedFromMark's title and aria-label ("Started by ...").
- The Windows caption buttons in WindowControls (Minimize / Maximize /
Restore / Close), which ship aria-labels and tooltips.
Both wire through the usual namespaces, with en byte-identical to the
upstream strings.
Co-Authored-By: Claude Code <noreply@anthropic.com>
c715d20 to
376e99d
Compare
|
Thanks for the heads-up! Rebased on v1.9.1, including the Windows port and the spawn links work. Two small additions came out of the rebase:
|
simion
left a comment
There was a problem hiding this comment.
Wired correctly across the app and the full suite stays green (2407/2407) with no init race. FileFinderDialog is still entirely untranslated and a handful of strings drifted slightly from the original English, worth a follow-up pass whenever convenient.
Thanks for the contribution!
`useTranslation("panels")` binds `t`, but <Trans> does not inherit it:
with no `t={t}` and no `ns=`, it looks the key up in defaultNS
("common"), misses, and renders the key itself. Thirteen call sites did
that, so the PR card told people "pr.cliMissingBody", and the clone
dialog dropped its whole destination line, since the `<code>` element
lives in the translation and a missed key takes it (and its testid)
with it. Four e2e cases caught two of the thirteen; the other nine were
shipping silently.
The guard is static rather than a render test, so it covers the call
sites no spec opens: every <Trans> key must resolve in the namespace
that element will actually use, plural suffixes included.
Refs #330
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBZ9SY1zTBpPW33QAekZC1
The static guard reads every <Trans> call site; this reads what the app paints. `rawI18nKeysOnScreen` hands each dotted token on screen to i18next's own `exists` across every namespace, so "package.json" is not a key, a real one cannot hide behind a regex nobody updated, and the check needs no list of expected sentences to stay true. Covers the main window, every settings page, the new-task and new-project dialogs (clone mode included, where a missed key took the whole destination line with it) and a zh-CN pass. The first case is the control: it plants "pr.cliMissingBody" on screen and requires the detector to name it. Every other assertion here is "we found nothing", which is also what a detector that stopped seeing anything reports. Needs the live i18next instance on `window.__termic`, e2e build only. Refs #330 Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBZ9SY1zTBpPW33QAekZC1
|
This is live in v1.10.0, released today, and it is the release's headline: Termic now speaks Simplified Chinese, with the picker under Settings, General, Language. |
The Shortcuts settings page and the ⌘/ cheat sheet drew their row labels and hints from English strings in lib/shortcuts.ts, so a Chinese UI showed a Chinese page whose every row was English. The page's own chrome (subtitle, Reset all, the conflict note) had locale entries; the rows under it did not. The copy moves into the locale files (settings:shortcuts.*) and lib/shortcutCopy.ts becomes the id -> key map plus the accessors both surfaces read. One copy is the only way the settings list and the sheet cannot disagree about what a command is called, and a new ShortcutId is a compile error until it has a key there. The registry keeps structure only (ids, groups, bindings, glyphs) and DOUBLE_SHIFT_MODES / CTRL_TAB_MODES become id lists. English text is unchanged, word for word, so the specs that assert on it stay valid. Keys built from a table are invisible to usedKeys.test.ts (literals only) and to parity.test.ts (en against zh-CN only: a row missing from BOTH passes it), so shortcutCopy.test.ts pins them, and i18n.e2e.ts gains a case that opens the page in Chinese and reads the rows back. Follow-up to #330. Co-authored-by: Claude Code <noreply@anthropic.com>
{"body":"This adds i18next + react-i18next and wires every user-visible string in the webview app, shipping a complete Simplified Chinese (zh-CN) locale alongside English.\n\nEnglish output is byte-identical to before, so existing behavior, styling and e2e assertions are unchanged.\n\n### How it is wired\n\n- String-rendering components resolve text through
useTranslation, withTransfor embedded markup.\n- Outside React (toasts, OS notifications, confirm dialogs, tab titles), strings resolve viai18n.tat event time.\n- Helpers that render for a component take the caller'stas a parameter, so there is no second i18n instance.\n- Eight namespaces keep the locale files navigable: common, sidebar, settings, dialogs, task, panels, chrome, backend.\n- A new uiLanguage preference (system / English / 中文) switches live, with no reload.\n- Locales are bundled TS modules: no network fetch, no Suspense boundary, no change to the CSP.\n\n### Guardrails\n\n-src/locales/parity.test.tspins en and zh-CN to identical key and placeholder sets, so a new English string without a Chinese counterpart fails the suite instead of silently reaching Chinese users as English.\n-docs/i18n.mddocuments the wiring rules (hooks vsi18n.t,Trans, plurals, zh-CN conventions) for future strings.\n- A settings e2e spec covers the language switch.\n- Data-testids, raw backend error passthroughs and brand names are deliberately left untranslated.\n\n### Testing\n\nnpm test(2460 tests across 167 files) andtsc -bare green on this branch, which sits on top of v1.9.1.\nManually exercised in the running app by the author: switched the language between system/English/中文 in Settings and walked the sidebar, task view, settings pages and dialogs in both languages, confirming the zh-CN surface renders completely with no raw keys.\n\n🤖 Generated with Claude Code"}