feat(web-gui): full i18n support (react-i18next, EN/ZH-CN)#2115
Conversation
- Add i18next + react-i18next dependencies - Create src/i18n/ module: config, types, resolve, I18nProvider - Add en/zh-CN translation resource skeletons - Add language selector in Settings > General tab - Add key completeness and resolveLanguage tests - Wrap App with I18nProvider in main.tsx All 118 tests pass, typecheck clean, build succeeds.
The lock file was slightly out of sync with package.json after adding i18next/react-i18next dependencies. Running npm install with a clean node_modules reconciled the lock file (removed stale peer flag on use-sync-external-store, re-resolved transitive deps).
…tibility The previous npm install (npm 11.6.2/node v25) stripped @emnapi/core and @emnapi/runtime peer dependency entries from the lock file, causing npm ci to fail on npm 11.12.0/node v23 with 'Missing: @emnapi/core from lock file'. Regenerated with matching npm version to preserve peer entries.
Phase 0 only set up the i18n framework and language selector without connecting actual UI strings. This commit converts all page UI text to use t() translation calls: - App.tsx: navigation labels, dashboard, connection switcher, bootstrapping/missing-agent pages, create-agent modal - DashboardPage, SearchPage, SkillsPage, TemplatesPage, AgentPage, SettingsPage, ActivityInspectorPanel - Right panel components: AgentOverviewPanel, RightSidePanel, FileBrowserPanel - Resource files: expanded en.ts/zh-CN.ts from ~18 to ~300+ keys covering all page namespaces Verified: tsc passes, 118/118 tests pass, vite build succeeds, browser test confirms language switch from English to Chinese changes all converted UI text in real-time.
…RightPanel strings Replace all remaining hardcoded English UI strings with t() calls: - SettingsPage: header, runtime overview, model defaults, search section, provider status, OAuth section, advanced/raw config, diagnostics - AgentOverviewPanel: empty states, worktree label, task detail Kind - Add ~35 new translation keys to en.ts and zh-CN.ts Verified: tsc --noEmit zero errors, vitest 118/118 passed
Phase 2: Agent conversation page (AgentPage.tsx) - Timeline turn labels, composer aria/placeholder, thinking controls - Model picker strings, message actions, activity trail, working indicator Phase 3: Right panel deep coverage (AgentOverviewPanel, RightSidePanel) - Agent overview: lifecycle, worktree/path, skills/tasks/work-items cards - Skill manager panel: search, enable/disable, catalog refresh - Work item detail, task detail, file browser panels Phase 4: Search, Skills, Inspector - SearchPage: preview labels via i18next for module-level functions - SkillsPage: scope labels (Global/Workspace/Agent) - ActivityInspectorPanel: detail labels, timeline, raw event sections Phase 5: Dashboard + common components - DashboardPage: aria-labels - All sub-components now have useTranslation() or i18next.t() for module-level functions Added ~80 new i18n keys across en/zh-CN (agent, inspector, dashboard, searchPage, rightPanel namespaces). Updated SearchPage.test.ts to initialize i18next for module-level t(). tsc --noEmit: zero errors. vitest: 118/118 passing.
- zh-CN.ts: 所有 "Agent" → "智能体"(AgentTemplate 类型名保留) - AgentOverviewPanel.tsx: 修复 workspaceCount/effectiveCount i18n、 manageSkillsDesc JSX 缺失花括号、"Refreshing…" 硬编码、 WorkItemCard 缺少 useTranslation、compactMeta "current" 未翻译 - ActivityInspectorPanel.tsx: 52 处 labelledText 标签全部接入 i18next.t() - SettingsPage.tsx: "API Key for" 硬编码 → t() 插值 - en.ts/zh-CN.ts: 新增 ~55 个 inspector.* keys + apiKeyFor key - ActivityInspectorPanel.test.ts: 添加 i18next 初始化
- zh-CN: all standalone 'Agent' values → '智能体' (keep 'AgentTemplate' type name) - Add agent.model/currentWork/scheduling keys (were missing, showed raw 'AGENT.*') - Dashboard metric labels: use i18n keys instead of hardcoded English (Agents/Needs attention/Active tasks/Current work) - Update runtime-store.ts, client.ts, fixtures.ts to store key paths - DashboardPage.tsx: render metric.label via t() Verified: tsc zero errors, vitest 118/118, browser ZH-CN switch confirmed all dashboard + right panel labels translate correctly.
StatusBadge/AgentStateBadge had hardcoded English labels (Ready, Running, Waiting, etc.) and raw scope/state values (workspace, agent, open, completed). Added badge namespace to en.ts and zh-CN.ts, made StatusChip.tsx i18n-aware.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
jolestar
left a comment
There was a problem hiding this comment.
Review Summary
Architecture: Clean i18n implementation with well-separated concerns — config.ts (init), I18nProvider.tsx (context + persistence), resolve.ts (language detection), types.ts (type-safe modes), and resources/ (translation files). Good use of react-i18next patterns.
Positives:
- Type-safe
LanguageMode/ResolvedLanguagewith clear extension points - Resource key parity test (
resources.test.ts) ensures en/zh-CN stay in sync resolveBrowserLanguagetest covers zh variants, en variants, fallback, and case insensitivity- Dashboard metrics converted cleanly: data layer stores i18n keys, component resolves via
t() escapeValue: falsecorrect for React;useSuspense: falseavoids Suspense boundary requirement- localStorage persistence with graceful fallback for SSR/unavailable environments
<em>Saving…</em>patterns properly converted tot("common.saving")- Test files properly initialize i18next in
beforeAll
Non-blocking findings:
-
Incomplete "Save" button translations — Several SettingsPage buttons translate "Saving…" via
t("settings.saving")but leave "Save" / "Save Vision" hardcoded:{runtimeConfigSaving ? t("settings.saving") : "Save"}→ should uset("settings.save"){runtimeConfigSaving ? t("settings.saving") : "Save Vision"}→ should uset("settings.saveVision")- Dynamic labels like
`Save ${definition.label}`and`Save ${providerId}`could use i18next interpolation:t("settings.saveProvider", { name }). The translation keys already exist in resources.
-
Missed hint string —
DuckDuckGo and native search do not need API keys. Add keys only for API-backed providers below.in SettingsPage remains hardcoded English. -
Minor duplication —
readStoredLanguageMode()inconfig.tsandreadStoredMode()inI18nProvider.tsxdo identical work; could share one function.
None of these are blocking — the architecture is sound and the untranslated strings can be addressed in a follow-up. The PR description claim of "zero remaining hardcoded UI strings" is slightly overstated but the core i18n infrastructure is production-ready.
There was a problem hiding this comment.
Review Summary
This PR adds a comprehensive i18n layer (react-i18next + i18next) to the web-gui, covering most user-facing strings across Dashboard, Agent, Settings, Search, Skills, and Templates pages. The migration introduces a language selector (system / English / Chinese Simplified) and passes TFunction through utility helpers for consistent translation. Overall the architecture is clean — well-separated concerns with namespace-based resource files and a sensible fallback pattern in statusLabel().
Findings
1. Hardcoded English strings remain alongside translated counterparts (warn)
Several strings were missed during the migration pass:
"Save"and"Save Vision"buttons still use raw English while the loading variant correctly usest("settings.saving")."Disabled"appears raw while the adjacent enabled state usest("settings.enabled")— the keyt("settings.disabled")already exists.
2. Mixed useTranslation() vs direct i18next.t() in non-component code (warn)
groupTimelineTurns() and workingActivityLabel() in AgentPage.tsx use i18next.t() directly instead of the React hook. This works at call time but bypasses React's re-render cycle, so these labels won't update when the user switches languages without a full page re-render.
3. statusLabel() fallback robustness (nit)
The badgeKey !== translated check relies on i18next's default behavior of returning the key when no translation exists. Consider using t(badgeKey, { defaultValue: prettify(value) }) for explicit fallback safety.
What looks good
- Language selector with system detection and manual override is a nice UX touch.
- The
describeStatus/statusLabelrefactor to acceptTFunctionis pragmatic and avoids unnecessary React context dependency in pure functions. - Translation resource files are well-organized by namespace.
- The fallback
prettify()path instatusLabelhandles unknown badge values gracefully.
Holon Run Report
|
Summary
Add maintainable multi-language support to
web-gui, enabling UI text to switch between English and Chinese (with clear extension points for additional languages).Implementation
react-i18next+i18next(Provider + hook pattern, namespace support, language detection, fallback)src/i18n/— config, I18nProvider, resource loading, type-safe key resolutionsrc/i18n/resources/en.ts&zh-CN.ts— centralized translation resources (740+ keys each)Verification
tsc --noEmit— zero errorsvitest run— 118/118 passingChanged Files
29 files changed, +2639 / -708 lines across 9 commits (Phase 0–5 + incremental fixes).