feat(settings): add Chinese (zh-CN) i18n support#935
Conversation
📝 WalkthroughWalkthroughThis PR introduces a persisted i18n layer (Zustand store, ChangesInternationalization implementation
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/settings/sections/ModelsSection.tsx (1)
743-747: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win"not connected" is translated 30 lines below but hardcoded here.
Line 745's dropdown badge still reads a literal
not connected, while the near-identical concept at line 776 now goes throught("models.notConnected"). Same string, two different fates — zh-CN users will see mixed English/Chinese in the same dropdown.Same localization pass also leaves "Connected" badges, "Docs", "Test", and "Save" buttons hardcoded elsewhere in this file — worth a follow-up sweep for full zh-CN coverage.🌐 Proposed fix
{!pConfigured ? ( <span className="ml-auto text-[9.5px] normal-case tracking-normal text-muted-foreground/70"> - not connected + {t("models.notConnected")} </span> ) : null}Also applies to: 774-777
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/settings/sections/ModelsSection.tsx` around lines 743 - 747, The ModelsSection dropdown still hardcodes the “not connected” badge in one branch while the same label already uses t("models.notConnected") elsewhere; update the conditional badge rendering in ModelsSection to use the translation helper consistently so zh-CN users don’t see mixed language. While you’re there, sweep the nearby badge/button labels in the same component (such as the Connected, Docs, Test, and Save text) and route them through the existing i18n keys or add matching keys where needed.
🧹 Nitpick comments (5)
src/i18n/index.ts (2)
65-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate resolution logic between
useTranslationandtranslate.Both functions repeat the same "lookup then fall back to English then key" sequence. Worth extracting a shared
resolve(messages, key)helper to keep the two paths from drifting.Also applies to: 78-84
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/i18n/index.ts` around lines 65 - 72, Both useTranslation and translate duplicate the same lookup/fallback flow, so extract a shared resolve(messages, key) helper in the i18n module and have both functions delegate to it. Keep the logic centralized around getNestedValue, locales.en fallback, and final key fallback so the two paths stay consistent and don’t drift.
14-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
TranslationKeyis exported but never enforced.
useTranslation()'s returned function andtranslate()both accept a plainkey: string, so the whole point ofNestedKeyOf<typeof en>(compile-time key safety, catching typos like"agents.hanlde") is lost. Every call site in the sections is currently free to pass an invalid key that silently falls back to itself (line 70/83).Type the key parameter
- return (key: string): string => { + return (key: TranslationKey): string => { const value = getNestedValue(messages, key); if (value !== undefined) return value; // Fallback to English const enValue = getNestedValue(locales.en, key); return enValue ?? key; }; } ... -export function translate(key: string, locale?: Locale): string { +export function translate(key: TranslationKey, locale?: Locale): string {Also applies to: 61-84
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/i18n/index.ts` around lines 14 - 24, `TranslationKey` is defined but not actually used, so invalid translation keys still compile. Update the key parameter types in `useTranslation()` and `translate()` from plain `string` to `TranslationKey` (or a generic constrained to it), and propagate that type through the returned translator function so call sites in the sections get compile-time key safety and typo detection.src/settings/SettingsApp.tsx (1)
23-45: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueType
labelKeyasTranslationKeyhere.useTranslation()falls back to the raw key when a translation is missing, so leaving this asstringlets a typo inTABSreach the UI with no build-time signal.src/settings/SettingsApp.tsx:38-45🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/settings/SettingsApp.tsx` around lines 23 - 45, The TABS metadata is using an untyped string for labelKey, which allows invalid i18n keys to slip into the UI; update the SettingsApp.tsx TABS definition so labelKey is typed as TranslationKey instead of string. Use the existing useTranslation/TranslationKey types in this file to keep the tab labels checked at compile time and prevent typos in the tab entries like general, themes, shortcuts, models, agents, and about.src/settings/sections/AgentsSection.tsx (1)
543-545: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDrop the commented-out
savedTickJSX.Dead, disabled markup sitting in a hunk you just touched. Per repo convention, default to no comments and no leftover code.
As per coding guidelines, "Default to no comments; if a comment is genuinely needed, keep it to 1-2 lines explaining why, not what, and avoid AI-generic filler."🧹 Proposed fix
- {/* {savedTick > 0 ? ( - <span className="text-[10px] text-muted-foreground">Saved</span> - ) : null} */} {draft && (🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/settings/sections/AgentsSection.tsx` around lines 543 - 545, Remove the commented-out savedTick JSX from AgentsSection so there is no leftover disabled markup in the touched block; keep the render logic clean and ensure the component only contains the active JSX around savedTick and the surrounding settings UI.Source: Coding guidelines
src/settings/sections/ThemesSection.tsx (1)
193-193: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
tparam in.map((t) => ...)shadows the translation function.Not a bug today (nothing inside the loop calls
t(...)), but the newconst t = useTranslation()at the top now collides with this pre-existing loop variable naming. Anyone adding a translated string inside the theme card render later will get confusing type errors or silently reference the wrongt. Renaming the loop var (e.g.theme) removes the trap.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/settings/sections/ThemesSection.tsx` at line 193, The theme list render in ThemesSection has a naming collision because the .map callback uses t, which shadows the translation function returned by useTranslation(). Rename the loop variable in the themes.map callback to a non-conflicting name such as theme, and update all references inside that render block so future calls to t(...) remain unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/i18n/locales/en.json`:
- Around line 127-137: The models locale object contains a duplicate "test" key,
which should be defined only once. Remove the redundant "test" entry in the same
translation block and make sure the remaining key is the single source of truth;
apply the same cleanup to the corresponding models object in zh-CN.json so both
locale files stay consistent.
In `@src/settings/sections/AboutSection.tsx`:
- Around line 46-49: The AboutSection status label still hardcodes English in
the available/manualAvailable branches, so update the ternary in
AboutSection.tsx to route those cases through translation like the other
branches. Add new i18n keys for the action prefixes used by the status text
(about.installVersion and about.updateToVersion) in both en.json and zh-CN.json,
then compose the localized prefix with the version value in the AboutSection
rendering logic.
In `@src/settings/sections/AgentsSection.tsx`:
- Line 411: The three save buttons in AgentsSection are using the ModelsSection
translation key instead of an agents-scoped/shared key. Update the save label in
AgentEditorDialog, SnippetEditorDialog, and CustomInstructionsBlock to use a new
agents.save or common.save translation key, and add that key to both locale
files so these components no longer depend on models.save.
In `@src/settings/sections/ModelsSection.tsx`:
- Line 776: The rendered text in ModelsSection is using a literal em dash in the
not-connected message, which violates the repo’s punctuation guideline. Update
the JSX in the ModelsSection rendering path so the separator between
t("models.notConnected") and getProvider(provider).label uses a non-em-dash
alternative, and make sure no other nearby UI strings in this component
introduce em dashes.
In `@src/settings/SettingsApp.tsx`:
- Around line 126-153: The language switcher button in SettingsApp still uses a
hardcoded English tooltip, so update the DropdownMenuTrigger button’s title to
use the existing translation function instead of the literal “Language”. Locate
the language selector block in SettingsApp and route that label through t()
consistently with the rest of the file so the tooltip is localized.
---
Outside diff comments:
In `@src/settings/sections/ModelsSection.tsx`:
- Around line 743-747: The ModelsSection dropdown still hardcodes the “not
connected” badge in one branch while the same label already uses
t("models.notConnected") elsewhere; update the conditional badge rendering in
ModelsSection to use the translation helper consistently so zh-CN users don’t
see mixed language. While you’re there, sweep the nearby badge/button labels in
the same component (such as the Connected, Docs, Test, and Save text) and route
them through the existing i18n keys or add matching keys where needed.
---
Nitpick comments:
In `@src/i18n/index.ts`:
- Around line 65-72: Both useTranslation and translate duplicate the same
lookup/fallback flow, so extract a shared resolve(messages, key) helper in the
i18n module and have both functions delegate to it. Keep the logic centralized
around getNestedValue, locales.en fallback, and final key fallback so the two
paths stay consistent and don’t drift.
- Around line 14-24: `TranslationKey` is defined but not actually used, so
invalid translation keys still compile. Update the key parameter types in
`useTranslation()` and `translate()` from plain `string` to `TranslationKey` (or
a generic constrained to it), and propagate that type through the returned
translator function so call sites in the sections get compile-time key safety
and typo detection.
In `@src/settings/sections/AgentsSection.tsx`:
- Around line 543-545: Remove the commented-out savedTick JSX from AgentsSection
so there is no leftover disabled markup in the touched block; keep the render
logic clean and ensure the component only contains the active JSX around
savedTick and the surrounding settings UI.
In `@src/settings/sections/ThemesSection.tsx`:
- Line 193: The theme list render in ThemesSection has a naming collision
because the .map callback uses t, which shadows the translation function
returned by useTranslation(). Rename the loop variable in the themes.map
callback to a non-conflicting name such as theme, and update all references
inside that render block so future calls to t(...) remain unambiguous.
In `@src/settings/SettingsApp.tsx`:
- Around line 23-45: The TABS metadata is using an untyped string for labelKey,
which allows invalid i18n keys to slip into the UI; update the SettingsApp.tsx
TABS definition so labelKey is typed as TranslationKey instead of string. Use
the existing useTranslation/TranslationKey types in this file to keep the tab
labels checked at compile time and prevent typos in the tab entries like
general, themes, shortcuts, models, agents, and about.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: adbf2f55-a41d-4f18-8cf8-54ad6ac5b62f
📒 Files selected for processing (10)
src/i18n/index.tssrc/i18n/locales/en.jsonsrc/i18n/locales/zh-CN.jsonsrc/settings/SettingsApp.tsxsrc/settings/sections/AboutSection.tsxsrc/settings/sections/AgentsSection.tsxsrc/settings/sections/GeneralSection.tsxsrc/settings/sections/ModelsSection.tsxsrc/settings/sections/ShortcutsSection.tsxsrc/settings/sections/ThemesSection.tsx
| "test": "Test", | ||
| "modelId": "Model ID", | ||
| "context": "Context", | ||
| "apiKey": "API key", | ||
| "optional": "Optional — leave empty for unauthenticated endpoints", | ||
| "save": "Save", | ||
| "removeKey": "Remove key", | ||
| "reachable": "Reachable — server responded.", | ||
| "notReachable": "Could not reach the server.", | ||
| "test": "Test", | ||
| "testing": "Testing…", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Duplicate "test" key in the models object.
"test": "Test" is defined twice (Line 127 and Line 136). The JSON parser silently keeps the last one; both happen to hold the same value now, but it's a latent trap if a future edit touches only one copy (e.g. localization tooling regenerating one entry). Same duplication exists in zh-CN.json.
Fix
"baseURL": "Base URL",
- "test": "Test",
"modelId": "Model ID",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "test": "Test", | |
| "modelId": "Model ID", | |
| "context": "Context", | |
| "apiKey": "API key", | |
| "optional": "Optional — leave empty for unauthenticated endpoints", | |
| "save": "Save", | |
| "removeKey": "Remove key", | |
| "reachable": "Reachable — server responded.", | |
| "notReachable": "Could not reach the server.", | |
| "test": "Test", | |
| "testing": "Testing…", | |
| "baseURL": "Base URL", | |
| "modelId": "Model ID", | |
| "context": "Context", | |
| "apiKey": "API key", | |
| "optional": "Optional — leave empty for unauthenticated endpoints", | |
| "save": "Save", | |
| "removeKey": "Remove key", | |
| "reachable": "Reachable — server responded.", | |
| "notReachable": "Could not reach the server.", | |
| "test": "Test", | |
| "testing": "Testing…", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/i18n/locales/en.json` around lines 127 - 137, The models locale object
contains a duplicate "test" key, which should be defined only once. Remove the
redundant "test" entry in the same translation block and make sure the remaining
key is the single source of truth; apply the same cleanup to the corresponding
models object in zh-CN.json so both locale files stay consistent.
| : available | ||
| ? `Install v${status.update.version}` | ||
| : manualAvailable | ||
| ? `Update to v${status.info.version}` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Install/update version strings never got translated.
Every other branch of this ternary routes through t(...), but the available/manualAvailable branches still hardcode English: `Install v${status.update.version}` and `Update to v${status.info.version}`. On zh-CN this button will silently show English text for the two most action-relevant states (an update is actually ready to install). The AI summary claims "install/update version messaging" was localized too, so this looks like an oversight rather than intent.
Since useTranslation() has no interpolation support, the cheapest fix is splitting into a translated prefix + raw version:
🌐 Proposed fix
: available
- ? `Install v${status.update.version}`
+ ? `${t("about.installVersion")} v${status.update.version}`
: manualAvailable
- ? `Update to v${status.info.version}`
+ ? `${t("about.updateToVersion")} v${status.info.version}`
: t("about.checkForUpdates");New keys (about.installVersion = "Install", about.updateToVersion = "Update to") need to be added to both en.json and zh-CN.json.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/settings/sections/AboutSection.tsx` around lines 46 - 49, The
AboutSection status label still hardcodes English in the
available/manualAvailable branches, so update the ternary in AboutSection.tsx to
route those cases through translation like the other branches. Add new i18n keys
for the action prefixes used by the status text (about.installVersion and
about.updateToVersion) in both en.json and zh-CN.json, then compose the
localized prefix with the version value in the AboutSection rendering logic.
| onClick={() => onSave({ ...draft, builtIn: false })} | ||
| > | ||
| Save | ||
| {t("models.save")} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Save buttons borrow models.save instead of an agents-scoped key.
Three separate save buttons in this file (AgentEditorDialog, SnippetEditorDialog, CustomInstructionsBlock) all call t("models.save"). Every other string in this file is properly namespaced under agents.*; reaching into ModelsSection's namespace couples these two unrelated modules together — if ModelsSection ever renames or removes that key, three buttons here break silently. Worth introducing a shared common.save key (or agents.save) instead.
♻️ Proposed fix
- {t("models.save")}
+ {t("common.save")}(apply to all three occurrences, plus add common.save to both locale files)
Also applies to: 519-519, 548-548
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/settings/sections/AgentsSection.tsx` at line 411, The three save buttons
in AgentsSection are using the ModelsSection translation key instead of an
agents-scoped/shared key. Update the save label in AgentEditorDialog,
SnippetEditorDialog, and CustomInstructionsBlock to use a new agents.save or
common.save translation key, and add that key to both locale files so these
components no longer depend on models.save.
| {enabled && !hasKey ? ( | ||
| <p className="pl-19 text-[10.5px] text-muted-foreground"> | ||
| {getProvider(provider).label} isn't connected — add it below. | ||
| {t("models.notConnected")} — {getProvider(provider).label}. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Em dash slipped into rendered UI text.
{t("models.notConnected")} — {getProvider(provider).label}. uses a literal em dash. Repo guideline bans em dashes everywhere, including in code/strings.
✏️ Proposed fix
- {t("models.notConnected")} — {getProvider(provider).label}.
+ {t("models.notConnected")}: {getProvider(provider).label}.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {t("models.notConnected")} — {getProvider(provider).label}. | |
| {t("models.notConnected")}: {getProvider(provider).label}. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/settings/sections/ModelsSection.tsx` at line 776, The rendered text in
ModelsSection is using a literal em dash in the not-connected message, which
violates the repo’s punctuation guideline. Update the JSX in the ModelsSection
rendering path so the separator between t("models.notConnected") and
getProvider(provider).label uses a non-em-dash alternative, and make sure no
other nearby UI strings in this component introduce em dashes.
Source: Coding guidelines
| {/* Language Selector */} | ||
| <DropdownMenu> | ||
| <DropdownMenuTrigger asChild> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className="mr-2 size-7 text-muted-foreground hover:text-foreground" | ||
| title="Language" | ||
| > | ||
| <HugeiconsIcon icon={Globe02Icon} size={14} strokeWidth={1.75} /> | ||
| </Button> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent align="end" className="min-w-32 p-1"> | ||
| {AVAILABLE_LOCALES.map((locale) => ( | ||
| <DropdownMenuItem | ||
| key={locale.value} | ||
| onSelect={() => setLocale(locale.value as Locale)} | ||
| className="flex items-center justify-between gap-2 text-[12px]" | ||
| > | ||
| <span>{locale.label}</span> | ||
| {currentLocale === locale.value && ( | ||
| <span className="text-[10px] text-muted-foreground">✓</span> | ||
| )} | ||
| </DropdownMenuItem> | ||
| ))} | ||
| </DropdownMenuContent> | ||
| </DropdownMenu> | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Untranslated tooltip on the language switcher itself.
title="Language" at Line 133 is hardcoded English — the one string in this feature that ships un-localized is, fittingly, the language selector's own label. Should route through t() like everything else in this file.
diff
<Button
variant="ghost"
size="icon"
className="mr-2 size-7 text-muted-foreground hover:text-foreground"
- title="Language"
+ title={t("settings.language")}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {/* Language Selector */} | |
| <DropdownMenu> | |
| <DropdownMenuTrigger asChild> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| className="mr-2 size-7 text-muted-foreground hover:text-foreground" | |
| title="Language" | |
| > | |
| <HugeiconsIcon icon={Globe02Icon} size={14} strokeWidth={1.75} /> | |
| </Button> | |
| </DropdownMenuTrigger> | |
| <DropdownMenuContent align="end" className="min-w-32 p-1"> | |
| {AVAILABLE_LOCALES.map((locale) => ( | |
| <DropdownMenuItem | |
| key={locale.value} | |
| onSelect={() => setLocale(locale.value as Locale)} | |
| className="flex items-center justify-between gap-2 text-[12px]" | |
| > | |
| <span>{locale.label}</span> | |
| {currentLocale === locale.value && ( | |
| <span className="text-[10px] text-muted-foreground">✓</span> | |
| )} | |
| </DropdownMenuItem> | |
| ))} | |
| </DropdownMenuContent> | |
| </DropdownMenu> | |
| {/* Language Selector */} | |
| <DropdownMenu> | |
| <DropdownMenuTrigger asChild> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| className="mr-2 size-7 text-muted-foreground hover:text-foreground" | |
| title={t("settings.language")} | |
| > | |
| <HugeiconsIcon icon={Globe02Icon} size={14} strokeWidth={1.75} /> | |
| </Button> | |
| </DropdownMenuTrigger> | |
| <DropdownMenuContent align="end" className="min-w-32 p-1"> | |
| {AVAILABLE_LOCALES.map((locale) => ( | |
| <DropdownMenuItem | |
| key={locale.value} | |
| onSelect={() => setLocale(locale.value as Locale)} | |
| className="flex items-center justify-between gap-2 text-[12px]" | |
| > | |
| <span>{locale.label}</span> | |
| {currentLocale === locale.value && ( | |
| <span className="text-[10px] text-muted-foreground">✓</span> | |
| )} | |
| </DropdownMenuItem> | |
| ))} | |
| </DropdownMenuContent> | |
| </DropdownMenu> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/settings/SettingsApp.tsx` around lines 126 - 153, The language switcher
button in SettingsApp still uses a hardcoded English tooltip, so update the
DropdownMenuTrigger button’s title to use the existing translation function
instead of the literal “Language”. Locate the language selector block in
SettingsApp and route that label through t() consistently with the rest of the
file so the tooltip is localized.
What
Why
How
Testing
pnpm exec tsc --noEmitcleansrc-tauri/)cargo test --lockedandcargo clippy --all-targets --locked -- -D warningsclean#[tauri::command]signature) called out below so the FE caller can be updated in locksteppnpm tauri devScreenshots / GIFs
Notes for reviewer
Summary by CodeRabbit
New Features
Bug Fixes