Skip to content

feat: add i18n framework with Chinese (zh-CN) localization#3184

Open
jiaminghua wants to merge 3 commits intowavetermdev:mainfrom
jiaminghua:feat/i18n-chinese-localization
Open

feat: add i18n framework with Chinese (zh-CN) localization#3184
jiaminghua wants to merge 3 commits intowavetermdev:mainfrom
jiaminghua:feat/i18n-chinese-localization

Conversation

@jiaminghua
Copy link
Copy Markdown

Summary

This PR adds internationalization (i18n) support to Wave Terminal with full Simplified Chinese localization, addressing long-standing community requests (#2765, #2981).

Changes

Framework

  • Integrated react-i18next + i18next for internationalization
  • Created i18n initialization with default language zh-CN and English fallback
  • Exposed global t() function via window.__waveI18n for non-React contexts (event handlers, menu builders)

Localized Components (7 core UI components, 73 translation keys)

Component What's Localized
Connection Modal Local/Remote sections, connect, disconnect, reconnect, new connection, Git Bash
Tab Context Menu Rename, close, flag tab, backgrounds, tab bar position
Block Frame Header Split horizontally/vertically, magnify, settings, close, add to layout
AI Panel Header Widget context toggle, ON/OFF, more options
AI Panel Context Menu New chat, max output tokens, configure modes, hide
Settings/Config Page Config files, visual/raw JSON tabs, save/saving, unsaved changes, loading, config errors
About Modal Title, description, version, update channel, GitHub/website/sponsor links
Modal Footer OK/Cancel buttons

Files Added

  • frontend/app/i18n/index.ts — i18n configuration
  • frontend/app/i18n/locales/en.json — English translations (73 keys)
  • frontend/app/i18n/locales/zh-CN.json — Simplified Chinese translations (73 keys)

Design Decisions

  • Used window.__waveI18n.t for non-React contexts (menu builders) to avoid passing t through function parameters
  • Translation keys follow a flat app.xxx namespace for simplicity
  • No functional changes — pure localization layer, zero impact on existing behavior

Closes #2765
Closes #2981

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 4, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

Walkthrough

Adds i18n support using i18next/react-i18next: new dependencies in package.json, a new initializer at frontend/app/i18n/index.ts (exports configured i18n and assigns window.__waveI18n), and two locale files (en.json, zh-CN.json). Multiple UI components, context menus, and modals were updated to replace hardcoded English strings with translation calls (t(...) or useTranslation()), and a top-level side-effect import of the i18n module was added to app initialization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding i18n framework with Chinese localization support, which matches the core objective of this changeset.
Description check ✅ Passed The PR description provides comprehensive context about the i18n implementation, localized components, design decisions, and linked issues, all directly related to the changeset.
Linked Issues check ✅ Passed The PR successfully addresses both linked issues (#2765, #2981): it implements multi-language support with full Simplified Chinese localization, provides JSON translation files, detects system language with localStorage persistence, and localizes 7 core UI components with 73 translation keys.
Out of Scope Changes check ✅ Passed All changes are within scope: i18n framework setup, translations for 7 UI components, translation files, and dependency updates directly support the objectives of adding i18n and Chinese localization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Add react-i18next + i18next for internationalization support
- Create English and Simplified Chinese translation files (73 keys)
- Localize core UI components:
  - Connection modal (connect, disconnect, local/remote sections)
  - Tab context menu (rename, close, flag, backgrounds, tab bar position)
  - Block frame header (split, magnify, settings, close)
  - AI panel header and context menu (widget context, new chat, configure modes)
  - Settings/config page (config files, visual/raw JSON, save, errors)
  - About modal (description, version, links)
  - Modal footer (OK/Cancel buttons)
- Expose global t() function via window.__waveI18n for non-React contexts
- Default language set to zh-CN with English fallback
@jiaminghua jiaminghua force-pushed the feat/i18n-chinese-localization branch from 7ad9c45 to 78dcab4 Compare April 4, 2026 15:30
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/app/view/waveconfig/waveconfig.tsx (1)

69-90: ⚠️ Potential issue | 🟡 Minor

Two visible strings still bypass i18n.

"deprecated" and Save (${model.saveShortcut}) are still hardcoded English, so this view is not fully localized.

🛠️ Suggested fix
-    const saveTooltip = `Save (${model.saveShortcut})`;
+    const saveTooltip = t("app.saveWithShortcut", { shortcut: model.saveShortcut });
...
-                                    deprecated
+                                    {t("app.deprecated")}

Also add locale keys in both files:

// frontend/app/i18n/locales/en.json
"app.deprecated": "Deprecated",
"app.saveWithShortcut": "Save ({{shortcut}})"
// frontend/app/i18n/locales/zh-CN.json
"app.deprecated": "已弃用",
"app.saveWithShortcut": "保存 ({{shortcut}})"

Also applies to: 167-167

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/waveconfig/waveconfig.tsx` around lines 69 - 90, Replace
the hardcoded strings with i18n lookups: change the literal "deprecated" label
shown in the deprecatedConfigFiles.map render and the "Save
(${model.saveShortcut})" text (where model.saveShortcut is used) to use the
app's translation function (e.g., t('app.deprecated') and
t('app.saveWithShortcut', { shortcut: model.saveShortcut })) in the WaveConfig
component rendering logic (the block using deprecatedConfigFiles.map,
selectedFile, handleFileSelect, and model.saveShortcut); then add the
corresponding keys "app.deprecated" and "app.saveWithShortcut" with the
{{shortcut}} placeholder to both locales files (en.json and zh-CN.json).
frontend/app/modals/modal.tsx (1)

96-97: ⚠️ Potential issue | 🟠 Major

Fix label fallback logic: current implementation blocks i18n defaults and overrides intentional empty labels.

At Line 96 and Line 97, hardcoded defaults ("Cancel", "Ok") prevent translated fallbacks from ever being used for undefined props.
At Line 106 and Line 111, || also turns intentional empty strings into fallback text.

Proposed fix
 const ModalFooter = ({
     onCancel,
     onOk,
-    cancelLabel = "Cancel",
-    okLabel = "Ok",
+    cancelLabel,
+    okLabel,
     okDisabled,
     cancelDisabled,
 }: ModalFooterProps) => {
     const { t } = useTranslation();
     return (
         <footer className="modal-footer">
             {onCancel && (
                 <Button className="grey ghost" onClick={onCancel} disabled={cancelDisabled}>
-                    {cancelLabel || t("app.cancel")}
+                    {cancelLabel ?? t("app.cancel")}
                 </Button>
             )}
             {onOk && (
                 <Button onClick={onOk} disabled={okDisabled}>
-                    {okLabel || t("app.ok")}
+                    {okLabel ?? t("app.ok")}
                 </Button>
             )}
         </footer>
     );
 };

Also applies to: 106-111

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/modals/modal.tsx` around lines 96 - 97, The hardcoded default
labels cancelLabel = "Cancel" and okLabel = "Ok" prevent i18n defaults and the
use of || at the render sites forces empty strings to be replaced; change the
prop defaults to undefined (remove the "Cancel"/"Ok" defaults) and replace any
uses of logical OR (e.g., cancelLabel ||, okLabel ||) with the nullish
coalescing operator (cancelLabel ?? fallback, okLabel ?? fallback) or explicit
undefined checks so that undefined falls back to translations but intentional
empty strings are preserved; adjust references in modal component where
cancelLabel and okLabel are read (and the lines using || around 106-111)
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/i18n/index.ts`:
- Around line 7-13: The i18n init currently hardcodes lng: "zh-CN" which forces
Chinese for all users; update the i18n initialization (the
i18n.use(initReactI18next).init call) to compute the initial language by
checking a persisted user preference (e.g., localStorage key like "i18nLng"),
then falling back to navigator.language (or navigator.languages[0]) normalized
to one of the available resources keys ("en" or "zh-CN"), and finally to "en" if
none match; ensure you call i18n.changeLanguage with the chosen language so the
runtime language is applied and keep the resources object (en, zhCN) intact.

In `@frontend/app/i18n/locales/en.json`:
- Line 56: The translation entry "app.ok" currently uses "Ok" — update the
locale value for the "app.ok" key in frontend/app/i18n/locales/en.json to use
the standard button capitalization "OK" so UI text is consistent across the app.

In `@frontend/app/tab/tabcontextmenu.ts`:
- Around line 23-40: The tab context menu contains hardcoded English labels;
update all menu item labels to use the translation helper t(...) instead of
literal strings (e.g., replace "Rename Tab", "Copy TabId", "Flag Tab",
"Backgrounds", "Close Tab" with t("app.renameTab"), t("app.copyTabId"),
t("app.flagTab"), t("app.backgrounds"), t("app.closeTab") respectively), making
changes in buildTabBarContextMenu and the other menu-building logic in
tabcontextmenu.ts (the remaining menu items around lines 42-123) and add
corresponding keys to the localization files so the menu renders fully
translated.

---

Outside diff comments:
In `@frontend/app/modals/modal.tsx`:
- Around line 96-97: The hardcoded default labels cancelLabel = "Cancel" and
okLabel = "Ok" prevent i18n defaults and the use of || at the render sites
forces empty strings to be replaced; change the prop defaults to undefined
(remove the "Cancel"/"Ok" defaults) and replace any uses of logical OR (e.g.,
cancelLabel ||, okLabel ||) with the nullish coalescing operator (cancelLabel ??
fallback, okLabel ?? fallback) or explicit undefined checks so that undefined
falls back to translations but intentional empty strings are preserved; adjust
references in modal component where cancelLabel and okLabel are read (and the
lines using || around 106-111) accordingly.

In `@frontend/app/view/waveconfig/waveconfig.tsx`:
- Around line 69-90: Replace the hardcoded strings with i18n lookups: change the
literal "deprecated" label shown in the deprecatedConfigFiles.map render and the
"Save (${model.saveShortcut})" text (where model.saveShortcut is used) to use
the app's translation function (e.g., t('app.deprecated') and
t('app.saveWithShortcut', { shortcut: model.saveShortcut })) in the WaveConfig
component rendering logic (the block using deprecatedConfigFiles.map,
selectedFile, handleFileSelect, and model.saveShortcut); then add the
corresponding keys "app.deprecated" and "app.saveWithShortcut" with the
{{shortcut}} placeholder to both locales files (en.json and zh-CN.json).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 69d8cf5a-fe66-4f7c-9e08-65a4e4e1b7c8

📥 Commits

Reviewing files that changed from the base of the PR and between 388b4c9 and 7ad9c45.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • frontend/app/aipanel/aipanel-contextmenu.ts
  • frontend/app/aipanel/aipanelheader.tsx
  • frontend/app/app.tsx
  • frontend/app/block/blockframe-header.tsx
  • frontend/app/i18n/index.ts
  • frontend/app/i18n/locales/en.json
  • frontend/app/i18n/locales/zh-CN.json
  • frontend/app/modals/about.tsx
  • frontend/app/modals/conntypeahead.tsx
  • frontend/app/modals/modal.tsx
  • frontend/app/tab/tabcontextmenu.ts
  • frontend/app/view/waveconfig/waveconfig.tsx
  • package.json

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
frontend/app/modals/conntypeahead.tsx (1)

247-248: Consider using empty string or constant for value for consistency.

Other items with custom onSelect handlers (e.g., reconnectSuggestionItem, disconnectSuggestionItem, newConnectionSuggestion) use value: "" since the value isn't used by their handlers. Using the translated string here is inconsistent and could cause issues if any future code matches against value.

♻️ Suggested fix for consistency
     const connectionsEditItem: SuggestionConnectionItem = {
         status: "disconnected",
         icon: "gear",
         iconColor: "var(--grey-text-color)",
-        value: t("app.editConnections"),
+        value: "",
         label: t("app.editConnections"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/modals/conntypeahead.tsx` around lines 247 - 248, Change the
suggestion item's value from the translated string to an empty string (or a
shared constant) to match the other custom onSelect-handled items; leave label
as t("app.editConnections") and ensure the item's onSelect handler still uses
the value-agnostic logic (as in reconnectSuggestionItem,
disconnectSuggestionItem, newConnectionSuggestion) so no behavior changes occur.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/app/modals/conntypeahead.tsx`:
- Around line 247-248: Change the suggestion item's value from the translated
string to an empty string (or a shared constant) to match the other custom
onSelect-handled items; leave label as t("app.editConnections") and ensure the
item's onSelect handler still uses the value-agnostic logic (as in
reconnectSuggestionItem, disconnectSuggestionItem, newConnectionSuggestion) so
no behavior changes occur.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6f8ece89-b9a4-4a3c-a99c-b1630f8720de

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad9c45 and 78dcab4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • frontend/app/aipanel/aipanel-contextmenu.ts
  • frontend/app/aipanel/aipanelheader.tsx
  • frontend/app/app.tsx
  • frontend/app/block/blockframe-header.tsx
  • frontend/app/i18n/index.ts
  • frontend/app/i18n/locales/en.json
  • frontend/app/i18n/locales/zh-CN.json
  • frontend/app/modals/about.tsx
  • frontend/app/modals/conntypeahead.tsx
  • frontend/app/modals/modal.tsx
  • frontend/app/tab/tabcontextmenu.ts
  • frontend/app/view/waveconfig/waveconfig.tsx
  • package.json
✅ Files skipped from review due to trivial changes (7)
  • frontend/app/app.tsx
  • package.json
  • frontend/app/i18n/locales/en.json
  • frontend/app/view/waveconfig/waveconfig.tsx
  • frontend/app/i18n/locales/zh-CN.json
  • frontend/app/tab/tabcontextmenu.ts
  • frontend/app/aipanel/aipanel-contextmenu.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • frontend/app/modals/about.tsx
  • frontend/app/block/blockframe-header.tsx
  • frontend/app/modals/modal.tsx
  • frontend/app/aipanel/aipanelheader.tsx
  • frontend/app/i18n/index.ts

jiaminghua added 2 commits April 4, 2026 23:40
- Complete tab context menu localization (rename, copy ID, flag tab,
  backgrounds, close tab, flag colors)
- Fix 'Ok' → 'OK' capitalization in en.json
- Auto-detect system language instead of hardcoding zh-CN
- Persist language preference to localStorage
- Support language switching via i18n.changeLanguage()
- Translate 'deprecated' label in config sidebar
- Translate 'Save (shortcut)' tooltip with i18n interpolation
- Fix modal footer default label logic (null check instead of ||)
- Fix connectionsEditItem value to empty string for consistency
- Add app.deprecated and app.saveWithShortcut translation keys
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/app/modals/conntypeahead.tsx (1)

162-169: ⚠️ Potential issue | 🟡 Minor

Match Git Bash filtering against the localized label, not hardcoded English.

Line 162 filters with "Git Bash" while Line 168 renders t("app.gitBash"). In localized UIs, users may type the translated label and get no match.

Suggested fix
-    if (hasGitBash && "Git Bash".toLowerCase().includes(connSelected.toLowerCase())) {
+    const gitBashLabel = t("app.gitBash");
+    if (hasGitBash && gitBashLabel.toLowerCase().includes(connSelected.toLowerCase())) {
         gitBashItems.push({
@@
-            label: t("app.gitBash"),
+            label: gitBashLabel,
             current: connection === "local:gitbash",
         });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/modals/conntypeahead.tsx` around lines 162 - 169, The filter for
Git Bash currently matches the hardcoded English string "Git Bash" against
connSelected; change it to match the localized label by using the translation
used for rendering (t("app.gitBash")) when deciding whether to push into
gitBashItems, so update the conditional in the block that references hasGitBash
and connSelected to compare t("app.gitBash").toLowerCase() (or a locale-aware
comparison) with connSelected.toLowerCase() while keeping the rest of the
gitBashItems object (status, icon, value "local:gitbash", label
t("app.gitBash"), current check against connection) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@frontend/app/modals/conntypeahead.tsx`:
- Around line 162-169: The filter for Git Bash currently matches the hardcoded
English string "Git Bash" against connSelected; change it to match the localized
label by using the translation used for rendering (t("app.gitBash")) when
deciding whether to push into gitBashItems, so update the conditional in the
block that references hasGitBash and connSelected to compare
t("app.gitBash").toLowerCase() (or a locale-aware comparison) with
connSelected.toLowerCase() while keeping the rest of the gitBashItems object
(status, icon, value "local:gitbash", label t("app.gitBash"), current check
against connection) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 510f1090-5938-4c87-89a7-180cf9254107

📥 Commits

Reviewing files that changed from the base of the PR and between 177c749 and f79aa62.

📒 Files selected for processing (5)
  • frontend/app/i18n/locales/en.json
  • frontend/app/i18n/locales/zh-CN.json
  • frontend/app/modals/conntypeahead.tsx
  • frontend/app/modals/modal.tsx
  • frontend/app/view/waveconfig/waveconfig.tsx
✅ Files skipped from review due to trivial changes (3)
  • frontend/app/view/waveconfig/waveconfig.tsx
  • frontend/app/i18n/locales/zh-CN.json
  • frontend/app/i18n/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/app/modals/modal.tsx

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.

[Feature]: When will Chinese be supported? Feature Request: Multi-language support (especially Chinese) for Wave Terminal

2 participants