Skip to content

feat(web): internationalize the UI (en/zh-Hans/zh-Hant/ja/ko)#91

Merged
cnjack merged 3 commits into
mainfrom
feat/web-i18n
Jun 21, 2026
Merged

feat(web): internationalize the UI (en/zh-Hans/zh-Hant/ja/ko)#91
cnjack merged 3 commits into
mainfrom
feat/web-i18n

Conversation

@cnjack

@cnjack cnjack commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Full vue-i18n (v11, Composition mode) internationalization across the entire web frontend, with five locale catalogs and an endonym-based language picker.

  • Infra: src/i18n/index.tscreateI18n setup with SUPPORTED_LOCALES/LOCALE_LABELS/HTML_LANG, browser-language detection (zh-TW → zh-Hant, etc.), localStorage persistence (jcode_locale), lazy setLocale().
  • Catalogs: en (canonical), zh-Hans, zh-Hant, ja, ko — all mirroring the same key structure; ~50 new keys for the Settings tabs.
  • Language picker wired into SettingsDialog (General → Preferences).
  • Migration: every .vue component + stores/composables (chat, project, toolInfo, useBranch) now read strings via t().

Type fixes (pre-existing WIP issues that blocked pnpm build):

  • CommandPalette: rename .map(t =>) callback — shadowed the i18n t
  • GoalBanner: drop dead 'in_progress'/'completed' switch arms (GoalStatus is active|complete|blocked per the Go backend)
  • i18n/index.ts + SettingsDialog: cast locale reads/writes for vue-i18n v11's type narrowing

Note: this branch is based on chore/web-ui-p0-p1 (PR #90) and includes that commit. Merge PR #90 first; once main advances, this PR will show only the i18n commit.

Verification

  • `pnpm lint` ✅
  • `pnpm build` ✅ (vue-tsc + vite; all 5 locale chunks emitted)
  • new keys + translations confirmed in the built bundles
  • `go build ./...` / `go vet ./...` / `go test ./...` ✅

P0 — design tokens:
- Add code-block (--code-bg/--code-border) and on-primary/on-destructive
  foreground tokens, plus a full ANSI terminal palette
  (--term-bg/fg/cursor/selection + 16 colors) in tokens.css
- TerminalInstance.vue resolves terminal colors from tokens at runtime via
  getComputedStyle (xterm needs color strings, not var()) and guards the
  ws handlers against a torn-down term
- Replace hardcoded colors in style.css (.prose-chat pre, .xterm,
  .diff-bar-empty) with the new tokens

P1 — icon system:
- Migrate the entire web/ frontend from lucide-vue-next to
  @heroicons/vue/24/outline (drop lucide-vue-next, add @heroicons/vue ^2.2.0)
- Replace inline <svg> blocks across all components with heroicons;
  non-1:1 icons use the semantically-closest outline icon
- Document the convention (heroicons-only, w-N h-N sizing, color tokens,
  terminal token resolution) under Frontend in AGENTS.md

P1 — UX polish (no layout changes):
- Add hover tooltips (with shortcuts where relevant)
- Simplify the welcome-screen copy (drop the "Pick a workspace" prompt)
- ToolCallCard.truncate() is now code-point aware ([...text])
- ApprovalBanner: two-step confirm on "Allow all", unified icons
- SettingsDialog: fix translate-x-4.5/translate-x-0.76 toggle values
  (-> translate-x-[20px]/translate-x-[2px]); iconFor returns components
- App.vue: flex-anchor the scroll-to-bottom button (remove bottom-40)
- GoalBanner: Clear button hover; ChatInput: send-btn disabled opacity
  0.3 -> 0.45, composer shadow uses --shadow-xl

Alongside (pre-existing in-progress work, bundled here):
- BLE status channel: GET/POST /api/channel/ble backend (channel.go,
  server.go) + api.ts client
- Persist approval mode as the startup default (server.go)
- Stamp turn durationMs on the final assistant message; expose
  setAutoApprove; drop retryFromMessage (chat.ts, types/api.ts)
- Favicon swap icon.svg -> icon.png (index.html, notifications.ts,
  web/public/) and regenerated desktop Tauri icons

Verification: pnpm lint OK, pnpm build OK (vue-tsc + vite), make build OK.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@cnjack, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 59 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94b94a76-6def-49ff-8c22-e84bd6b20b4a

📥 Commits

Reviewing files that changed from the base of the PR and between bf9d745 and 426888d.

⛔ Files ignored due to path filters (52)
  • desktop/src-tauri/icons/128x128.png is excluded by !**/*.png
  • desktop/src-tauri/icons/128x128@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/32x32.png is excluded by !**/*.png
  • desktop/src-tauri/icons/64x64.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square107x107Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square142x142Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square150x150Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square284x284Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square30x30Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square310x310Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square44x44Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square71x71Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square89x89Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/StoreLogo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • desktop/src-tauri/icons/icon.ico is excluded by !**/*.ico
  • desktop/src-tauri/icons/icon.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-512@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png is excluded by !**/*.png
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • web/public/icon.png is excluded by !**/*.png
  • web/public/icon.svg is excluded by !**/*.svg
📒 Files selected for processing (45)
  • AGENTS.md
  • desktop/src-tauri/icons/icon.icns
  • internal/web/channel.go
  • internal/web/server.go
  • web/index.html
  • web/package.json
  • web/src/App.vue
  • web/src/components/ApprovalBanner.vue
  • web/src/components/AskUserCard.vue
  • web/src/components/BranchPicker.vue
  • web/src/components/ChatInput.vue
  • web/src/components/ChatMessage.vue
  • web/src/components/CommandPalette.vue
  • web/src/components/DiffViewer.vue
  • web/src/components/FileTreePanel.vue
  • web/src/components/FileViewer.vue
  • web/src/components/GoalBanner.vue
  • web/src/components/ProjectSwitcher.vue
  • web/src/components/RemoteConnectWizard.vue
  • web/src/components/RightPanel.vue
  • web/src/components/SettingsDialog.vue
  • web/src/components/SetupView.vue
  • web/src/components/Sidebar.vue
  • web/src/components/TaskList.vue
  • web/src/components/TerminalInstance.vue
  • web/src/components/TerminalPanel.vue
  • web/src/components/ToolCallCard.vue
  • web/src/components/TopBar.vue
  • web/src/components/WorkspacePicker.vue
  • web/src/composables/api.ts
  • web/src/composables/notifications.ts
  • web/src/composables/toolInfo.ts
  • web/src/composables/useBranch.ts
  • web/src/i18n/index.ts
  • web/src/i18n/locales/en.ts
  • web/src/i18n/locales/ja.ts
  • web/src/i18n/locales/ko.ts
  • web/src/i18n/locales/zh-Hans.ts
  • web/src/i18n/locales/zh-Hant.ts
  • web/src/main.ts
  • web/src/stores/chat.ts
  • web/src/stores/project.ts
  • web/src/style.css
  • web/src/styles/tokens.css
  • web/src/types/api.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-i18n

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.

cnjack added 2 commits June 21, 2026 18:27
handleSetApprovalMode was writing both cfg.DefaultMode (the authoritative
unified field) and cfg.AutoApprove (its deprecated fallback). resolveStartupMode
only reads AutoApprove when DefaultMode is empty, so the AutoApprove write was
dead code — and staticcheck SA1019 flags it. DefaultMode alone covers the
persistence; the fallback is read-only for old configs.
Add full vue-i18n (v11, Composition mode) internationalization across the
entire web frontend, with five locale catalogs and an endonym-based language
picker.

i18n infrastructure:
- src/i18n/index.ts: createI18n setup with SUPPORTED_LOCALES /
  LOCALE_LABELS / HTML_LANG maps, browser-language detection (zh-TW ->
  zh-Hant, etc.), localStorage persistence (jcode_locale), and a lazy
  setLocale() that pre-loads the initial non-en locale to avoid a flash
- Locale catalogs: en (canonical source), zh-Hans, zh-Hant, ja, ko — all
  mirroring the same key structure; ~50 new keys added for the Settings
  tabs (appearance/providers/mcp/skills/ssh/channels)
- Language picker wired into SettingsDialog (General -> Preferences)
- main.ts registers the i18n plugin

Component migration (every .vue now reads strings via t()):
- App, ChatInput, ChatMessage, ApprovalBanner, GoalBanner, Sidebar,
  TopBar, RightPanel, ToolCallCard, CommandPalette, AskUserCard,
  DiffViewer, FileTreePanel, FileViewer, TerminalInstance, TerminalPanel,
  SetupView, RemoteConnectWizard, ProjectSwitcher, WorkspacePicker,
  BranchPicker, SettingsDialog (all tabs)
- Stores/composables: chat, project, toolInfo, useBranch

Type fixes (pre-existing WIP issues that blocked pnpm build):
- CommandPalette: rename .map(t =>) callback to avoid shadowing the i18n t
- GoalBanner: drop dead 'in_progress'/'completed' switch arms (GoalStatus
  is 'active'|'complete'|'blocked' per the Go backend)
- i18n/index.ts + SettingsDialog: cast locale writes/reads for vue-i18n
  v11's type narrowing (locale is typed from the initially-bundled 'en')

Verification: pnpm lint OK, pnpm build OK (vue-tsc + vite, all 5 locale
chunks emitted), new keys + translations confirmed in the built bundles.
@cnjack cnjack merged commit a0b932b into main Jun 21, 2026
3 checks passed
@cnjack cnjack deleted the feat/web-i18n branch June 21, 2026 10:30
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