Skip to content

feat: add English/Chinese language toggle for the manager UI#1264

Open
thynomex wants to merge 3 commits into
BigPizzaV3:mainfrom
thynomex:feature/english-translation-toggle
Open

feat: add English/Chinese language toggle for the manager UI#1264
thynomex wants to merge 3 commits into
BigPizzaV3:mainfrom
thynomex:feature/english-translation-toggle

Conversation

@thynomex

Copy link
Copy Markdown

Summary

Adds an English / Chinese language toggle to the Codex++ manager UI. The app is authored Chinese-first; this PR makes the entire manager frontend translatable without changing any behaviour, and adds a one-click toggle in the top bar (next to the theme toggle).

Chinese remains the default. Nothing changes for existing users unless they flip the toggle.

How it works

A lightweight, source-text-keyed i18n layer (no key renaming, minimal diff noise):

  • src/i18n.ts — runtime. t("中文") returns the English translation when the active language is English, otherwise the original Chinese (zero-overhead default). tf("前缀 {0}", [expr]) handles interpolated strings, preserving argument order.
  • src/i18n-en.ts — the English dictionary (537 plain strings + 39 interpolated templates), keyed by the original Chinese source text.
  • Every user-facing Chinese literal in App.tsx and ProviderPresetSelector.tsx is wrapped with t() / tf().

Why a reload on toggle: many Chinese literals live in module-level constants (route tables, preset/category labels, status maps) that evaluate once at import. Switching language persists the choice to localStorage and reloads the webview, so every literal — module-level and render-level — re-evaluates under the new language. For a local Tauri window this is instant.

Tooling (for maintainability)

To keep the translation layer honest as the UI evolves, two scripts are included under tools/:

  • i18n-codemod.mjs — AST-based (uses the repo's own TypeScript). Wraps every CJK string/template/JSX-text precisely via position-based edits, so formatting is untouched. Re-runnable and idempotent (won't double-wrap).
  • i18n-verify.mjs — scans actual t()/tf() call sites and fails if the dictionary has any missing or stale key. Run it in CI or before a release to guarantee no string is left untranslated or orphaned.
node tools/i18n-verify.mjs   # → "Dictionary matches every t()/tf() call site exactly."

Scope

Frontend UI strings only. Backend (commands.rs) result messages are out of scope for this PR and can follow in a second pass if desired.

Verification

  • npm run check (tsc) — clean
  • npm run vite:build — succeeds (1607 modules)
  • node --test (model-windows unit tests) — 9/9 pass
  • node tools/i18n-verify.mjs — dictionary matches call sites exactly

Notes for reviewers

  • model-windows.ts is intentionally not wrapped: its only Chinese string is a test-only error message, and model-windows.test.ts imports the module in isolation under node --test where an @/i18n import wouldn't resolve.
  • Translations aim for natural, concise English that matches the technical domain (relay/provider/Codex terminology). Happy to adjust any wording.

thynomex added 3 commits June 29, 2026 15:53
The manager app (codex-plus-manager) was Chinese-only. This adds a
source-text-keyed i18n layer so every user-facing string can render in
English, with a topbar toggle to switch languages.

- src/i18n.ts: t()/tf() helpers keyed by the original Chinese text;
  Chinese stays the zero-overhead default. Language is persisted to
  localStorage and the webview reloads on switch so module-level
  literals (route tables, preset labels) re-evaluate under the new lang.
- src/i18n-en.ts: English dictionary (539 plain + 39 interpolated keys).
- App.tsx / ProviderPresetSelector.tsx: every Chinese literal wrapped
  via an AST codemod (tools/i18n-codemod.mjs) — position-based edits
  that preserve formatting; comments and object keys left untouched.
- tools/i18n-verify.mjs: scans real t()/tf() call sites and fails if the
  dictionary has any missing or stale key.
- Added a Languages toggle button beside the theme toggle in the topbar.

Verified: tsc --noEmit clean, vite build succeeds, existing unit tests
pass, dictionary matches every call site.
…tions

showNotice now wraps the message body with t() so Rust backend messages
are translated at the display layer. Adds EN_BACKEND (exact-match) and
EN_BACKEND_PATTERNS (regex) dictionaries covering all commands.rs strings.
…is active

Add update_tray_labels Tauri command that rebuilds the tray context menu
with translated labels and updates the window title. The frontend invokes
it on startup when the language is set to English.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant