Summary
Completes bilingual (en/zh-CN) UI support for #567. This PR is the third phase of the i18n work (previous phases landed in #584-era commits; full phase 1+2 content is in the first commit of this branch).
Phase 1+2 (checkpoint 1654d66)
- 21 i18n namespaces, en + zh-CN locale files
- React Intl integration (i18next + react-i18next), automatic language detection
- Language switching in Settings → Display & Sound (persisted)
Phase 3 (this PR, d218bb0)
- Explicit Intl locales —
Intl.RelativeTimeFormat/NumberFormat/DateTimeFormatcalls insrc/utils/time.tsandsrc/features/home/homeFormatters.tsnow passi18n.languageinstead of relying on the system locale. This fixes 6 pre-existing test failures on machines where Node's default locale is zh-CN (e.g. Chinese Windows), without touching assertions. - Rust-side verification —
src-tauri/src/i18n.rsgains 6 unit tests asserting the tray menu labels (File/View/Settings) render correctly in both en and zh-CN. - Remaining string scan cleanup —
threadStatus.tsnow returns i18n keys (workspaces:statusRunning/statusReviewing/statusIdle), rendered viat()inWorkspaceHomeHistory.tsx;platformPaths.tstranslates its own file-manager action strings (files:revealInFileManager/showInFileManager/openInFileManagerwith{{fileManager}}interpolation; "Finder" / "File Explorer" stay literal by design). - i18next-parser integration —
i18next-parser@^9.0.2+i18next-parser.config.cjs+npm run extract:i18nscript. Locale files normalized: BOM stripped,\uXXXXescapes inlined, 16 plural base keys backfilled from_onevalues (i18next v24/v4 plural rules). Extraction is idempotent. - Docs —
docs/i18n-phase3-plan.md(extraction workflow, known false positives to clean, leftovers, PR handoff) + README Localization section.
Verification
- Full suite: 1032/1032 passed (baseline had 6 failures, all fixed by item 1)
- i18n parity tests: 31/31 passed
npm run typecheck: cleanvite build: ok (pre-existing chunk-size warning only)
Known limitations
- Rust side:
cargo check/cargo testis currently blocked upstream by #599 (whisper-rs build failure on Windows); the new Rust tests were reviewed manually and will be runnable once #599 is resolved. Runtime language switching for the native tray menu stays as designed (menu text is fixed at startup; restart to apply a language change). - i18next-parser is deprecated upstream in favor of i18next-cli (v9.0.2 still works); flagged for a future upgrade.
optionKeyLabel/metaKeyLabelare false-positive keys the parser regenerates from local variable names inuseSettingsViewOrchestration.ts; they must be deleted after eachextract:i18nrun (documented).
Activity
30Addressed in 9bb3302: settingsViewConstants.ts no longer evaluates i18n.t() at module load. DICTATION_MODELS now stores labelKey/noteKey, composer presets and section labels store key strings only; each consumer (SettingsDictationSection, SettingsComposerSection, SettingsView) renders through t() from useTranslation, so these labels now update immediately when the Display language is switched. Verified: 1032/1032 tests pass, typecheck clean.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bb3302af1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Both review points addressed in 9682f3d:
1. v4 plural forms — added _one/_other variants for all 11 {{count}} keys missing them (files: countMatches/countModified/countFiles, git: openCount, home: windowHours/windowMinutes/statRunsInLast7Days/statAcrossRuns/statActiveDaysInLast7, prompts: metaHasPrompts, workspaces: moreErrors) in both en and zh-CN, and removed the 5 legacy v3 _plural keys. A full-suite scan confirms zero remaining v3 _plural keys and zero {{count}} base keys without _other in either locale. English plural forms are distinct where the language requires (e.g. "{{count}} matches"); zh-CN uses identical _one/_other values as Chinese does not inflect.
2. Live docs — replaced the three historical phase notes (i18n-phase1/2/3-plan.md) with a single live-state guide at docs/localization.md covering the current architecture (21 namespaces, en/zh-CN), v4 plural rules, the extract:i18n workflow including the optionKeyLabel/metaKeyLabel false-positive cleanup step, file format conventions (BOM warning), the module-scope i18n.t() ban, and current limitations. No dangling references (verified: only the phase docs referenced each other).
Verification: 1032/1032 tests pass, i18n parity 31/31.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9682f3df02
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd687d75d9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
All three addressed in 1d24232:
-
Native menu/tray (src-tauri/src/lib.rs + i18n.rs): the menu and the macOS tray now resolve the persisted language through the new
i18n::resolve_ui_language, which uses the saved preference when present and otherwise falls back to the host locale via thesys-localecrate (zh* -> zh-CN, else en). This matches the frontenddetectInitialLanguagesemantics, so on first launch on a zh-CN host the native menu/tray are Chinese instead of English. A saved preference still wins afterwards. -
Composer follow-up hint (Composer.tsx + composer.json):
behaviornow interpolates the translatedcomposer:steer/composer:queuelabels, andactionuses two new localized phrasescomposer:followUpActionSteer("steer" / "引导") andfollowUpActionQueue("queue" / "排队"). The zh-CNfollowUpDefaulttemplate drops the awkward "以" so it reads naturally with Chinese verbs. No mixed-language hint anymore. -
Rust test assertion (src-tauri/src/types.rs):
app_settings_defaults_from_empty_jsonnow expects""for the no-preference default, consistent with the new semantics.
Verified: 1032/1032 tests pass, typecheck clean. Rust changes parse cleanly via cargo fmt; cargo check remains blocked by the pre-existing upstream whisper-rs build failure (#599). sys-locale 0.3 is a pure-Rust, dependency-free crate (Windows uses GetUserDefaultUILanguage, macOS CoreFoundation, Unix env vars), consistent with the host-locale detection approach.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d24232294
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
All four addressed in 31db2d4:
-
P1 i18n.t binding (4 files):
const { t } = i18nmodule-level destructuring inusageLabels.ts,homeFormatters.ts,homeUsageViewModel.tsandComposerAttachments.tsxdropped the i18next instance'sthis, so the firstt(...)call at render threw. All four now usei18n.t.bind(i18n); since they're plain functions evaluated per call (no memoized results), language switching still applies immediately. (Test suite didn't catch it because these modules aren't exercised through their real render paths.) While in this file I also localized the home usage card's hardcodedUpdatedprefix via the newhome:updatedLabelkey. -
Tray rebuild paths (src-tauri/src/tray.rs): both macOS rebuild entry points — recent-threads update (
set_tray_recent_threads) and session usage update (set_tray_session_usage) — now run the saved language throughi18n::resolve_ui_languagebeforeupdate_tray_menu, so an empty saved value no longer snaps the tray back to English after the initial Chinese tray was built at startup. The about-window title path inmenu.rs::handle_menu_eventgot the same treatment (including the lock-failure fallback). -
Trans placeholder (SettingsGitSection.tsx):
settings:git_commit_message_prompt_helpcontains{{diff}}, so<Trans>treated it as a missing interpolation and dropped it. It now passesvalues={{ diff: "{diff}" }}, rendering the literal{diff}placeholder again in both locales. -
docs/i18n-glossary.md: decision #5 rewritten to describe the current state — Rust menu/tray/about-title are localized via
t_rs/t_rs_fmtwith host-locale fallback throughresolve_ui_language— instead of the stale "not in Phase 1 scope" note.
Verified: 1032/1032 tests pass, typecheck clean, cargo fmt --check clean on the touched Rust files. cargo check remains blocked by the pre-existing upstream whisper-rs failure (#599).
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 31db2d4502
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
All three addressed in 4c57a49:
-
Cargo.lock: committed with the
sys-locale v0.3.2entry (its only dependency,libc, is already in the lock), so locked/reproducible Rust builds are consistent again. Note:sys-locale 0.3.2itself compiles cleanly — the remainingcargo checkerrors are all the pre-existing upstream whisper-rs breakage (#599). -
zh-CN server_remote_use: now keeps the
{{name}}interpolation —"使用 {{name}}"— matching the en resource, so inactive remote buttons and theiraria-labels retain the remote-specific context instead of all reading the bare "使用" label. -
GitDiffPanel: the
pushSyncConflictMessagememo now includestin its dependency array, so the localized conflict text refreshes on language change even when the push error payload is unchanged.
Verified: 1032/1032 tests pass, typecheck clean.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c57a49db6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:
d218bb0ed5ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".