Skip to content

feat(i18n): Chinese localization — Simplified + Traditional - #86

Merged
hilash merged 13 commits into
mainfrom
feat/i18n-chinese
May 15, 2026
Merged

feat(i18n): Chinese localization — Simplified + Traditional#86
hilash merged 13 commits into
mainfrom
feat/i18n-chinese

Conversation

@hilash

@hilash hilash commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Closes #79.

Summary

  • Adds 简体中文 (zh-CN) and 繁體中文 (zh-TW) as fully-supported locales
  • ~669 strings translated per locale across 91 namespaces; structural parity with en.json and he.json
  • Two new label keys (chineseSimplified / chineseTraditional) added to en.json and he.json so every locale labels the new options in its own script
  • Brand renders phonetically: 凯比内特 (zh-CN) / 凱比內特 (zh-TW)
  • Domain terms translate natively where natural: 心跳 (heartbeat), 例行任务 / 例行任務 (routine), 技能 (skill), 工作区 / 工作區 (workspace), 智能体 (zh-CN agent) / 智慧代理 (zh-TW agent)
  • zh-TW uses Taiwan-region vocabulary (檔案 / 影片 / 設定 / 伺服器 / 啟動 / 預設) rather than mainland conversions

CJK font fallback

  • New loadCjkFonts(locale) in src/lib/themes.ts pulls Noto Sans SC or Noto Sans TC from Google Fonts when locale is Chinese — same <link> swap pattern as the existing theme font loader
  • CSS rules scoped to html:lang(zh-*) in globals.css append the loaded CJK family + system fallbacks (PingFang SC/TC, Microsoft YaHei/JhengHei) after the theme's Latin font
  • Browsers cascade font-family per character, so Latin keeps the theme's look and CJK glyphs resolve to Noto Sans without touching any of the 15 theme font stacks

What's here vs. what's not

  • ✅ All UI strings extracted to JSON are translated
  • ⚠️ This is a v0 machine translation — native review and refinement welcome via follow-up PRs
  • ⚠️ zh-TW has not yet been reviewed by a native Traditional speaker; the regional vocabulary choices may need tuning
  • ⚠️ Strings hardcoded outside the i18n system (settings tab labels, some sidebar buttons) remain English — they predate this work and are tracked in CONTRIBUTING_I18N.md as the ongoing extraction budget
  • ❌ Onboarding seed pages (resources/getting-started-*) — not translated; covered by P4 of the i18n PRD
  • ❌ Agent locale propagation — already wired for Hebrew, will pick up Chinese automatically once a user selects zh-* (no code change needed here)

Test plan

  • npx tsc --noEmit clean
  • jq validates both JSON files
  • paths(scalars) parity check: zero missing keys vs en.json (only the 2 new label keys are extra)
  • Browser smoke test: switch to zh-CN → <html lang="zh-CN"> set, Noto Sans SC <link> loaded, settings page renders translated (姓名 / 显示名称 / 工作区 / 头像 / etc.), brand renders as 凯比内特 in "新建凯比内特"
  • Native Simplified Chinese review
  • Native Traditional Chinese review

Summary by CodeRabbit

  • New Features

    • Full Simplified (zh‑CN) and Traditional (zh‑TW) Chinese UI translations added and selectable in Settings.
    • Extensive i18n applied across help, onboarding, sidebar, agents, home, status bar, tasks, dialogs, tours, and quick actions.
    • Automatic CJK font fallback/loading to ensure correct Chinese rendering.
  • Chores

    • Applying a locale now triggers CJK font handling to load or remove appropriate fonts.

Review Change Stack

…h-TW)

Closes #79.

Adds two new locales to the picker: 简体中文 (zh-CN) and 繁體中文 (zh-TW),
both with full UI translation across all 91 namespaces (~669 strings each).
Hebrew and English remain in sync; two new label keys (chineseSimplified /
chineseTraditional) added to en.json and he.json so every locale can label
the new options in its own script.

Brand name renders phonetically: 凯比内特 (zh-CN) / 凱比內特 (zh-TW). Domain
terms like 心跳 (heartbeat), 例行任务/例行任務 (routine), 技能 (skill),
工作区/工作區 (workspace) translate natively; references the existing per-
document `dir` model in CONTRIBUTING_I18N.md.

CJK font fallback: a new loadCjkFonts() loader in src/lib/themes.ts pulls
Noto Sans SC or Noto Sans TC from Google Fonts when the active locale is
Chinese, swapped via the same <link>-tag pattern as the theme font loader.
CSS rules in globals.css scoped to html:lang(zh-*) append the loaded CJK
family + PingFang / Microsoft YaHei system fallbacks after the theme's
Latin font so Chinese glyphs render correctly across all 15 themes without
modifying their font stacks.

This is a v0 machine translation — native review and refinement welcome via
follow-up PRs. zh-TW uses Taiwan-region vocabulary (檔案/影片/設定/伺服器/
啟動/etc) rather than mainland conversions, but is not yet reviewed by a
native Traditional speaker.
@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds zh-CN/zh-TW locales and BCP47 mappings; loads Noto Sans SC/TC for Chinese locales; and replaces hardcoded UI strings with i18n lookups across onboarding, help, runtime, composer, task board, schedule picker/view, settings, home, and status UI.

Changes

i18n + CJK integration

Layer / File(s) Summary
i18n core and mappings
src/i18n/formatters.ts, src/i18n/index.ts
Adds BCP47 entries and includes zh-CN/zh-TW in SUPPORTED_LOCALES and i18next resources; removes them from REQUESTABLE_LOCALES.
Locale JSON files
src/i18n/locales/zh-CN.json, src/i18n/locales/zh-TW.json, src/i18n/locales/en.json, src/i18n/locales/he.json
Adds zh-CN/zh-TW translations and expands en/he dictionaries with many new keys used by updated components.
CJK font loader
src/lib/themes.ts, src/app/globals.css
Exports loadCjkFonts(locale) and adds CSS rules to append Noto Sans SC/TC fallbacks for Chinese lang attributes.
use-locale wiring
src/i18n/use-locale.ts
Calls loadCjkFonts(locale) when applying document locale.
Help & visuals
src/components/help/*
Replaces inline titles/descriptions/CTA strings with Trans/useLocale-driven keys and translates help visuals.
Onboarding/tour slides
src/components/onboarding/tour/*
Converts slide copy/scene metadata to use i18n (Trans/useLocale), localizes badges/headlines, and exports SlideTasks.
Composer & start-work
src/components/composer/*, src/components/composer/start-work-dialog.tsx
Localizes runtime/composer labels, start-work modes, mode hints, heartbeat panel, and error messages.
Task runtime & picker
src/components/composer/task-runtime-picker.tsx
Localizes provider/model labels, terminal provider statuses/tooltips, selection banner fallback, and dropdown summaries.
Tasks board & schedule
src/components/tasks/board/*, src/components/mission-control/schedule-picker.tsx
Localizes Kanban lane labels/hints, view toggle, agent filter text, schedule picker mode/weekdays, and schedule view month labels.
Home, Settings, Layout, StatusBar
src/components/home/*, src/components/settings/*, src/components/layout/*
Adds quick-action labelKey usage, includes zh-CN/zh-TW in settings language list, localizes sidebar groups, status/git/update/help labels, narrow viewport hint, and whats-new card text.
Misc components
src/components/*
Many small localized replacements across agents, depth-dropdown, skill dialogs, tree nodes, feedback popup, and others using useLocale() and t(...).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hilash/cabinet#84: Overlaps i18n locale initialization/document-locale wiring.
  • hilash/cabinet#74: Heartbeat UI changes that may interact with localized heartbeat labels/tooltips.

Poem

🐰 A rabbit’s note on small changes:
Keys replace the English thread,
Fonts arrive for texts in red,
Tours and help now speak like home—
小兔子: hop, translate, and roam.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.67% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(i18n): Chinese localization — Simplified + Traditional' clearly describes the main change: adding Chinese i18n support with both simplified and traditional locales.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from #79: i18n framework integration, Chinese locale files (zh-CN/zh-TW), CJK font fallback system, and hardcoded UI string extraction across components.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing Chinese localization and i18n support. No unrelated modifications or scope creep detected in the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/i18n-chinese

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/components/settings/settings-page.tsx (1)

239-244: ⚡ Quick win

Generate picker options from the locale registry to prevent drift.

This local hardcoded list duplicates supported-locale data. Deriving options from the central registry avoids future misses when adding locales.

♻️ Suggested refactor
-import { REQUESTABLE_LOCALES, type Locale } from "@/i18n";
+import { REQUESTABLE_LOCALES, SUPPORTED_LOCALES, type Locale } from "@/i18n";

+const LOCALE_LABEL_KEY: Record<Locale, string> = {
+  en: "settings:language.english",
+  he: "settings:language.hebrew",
+  "zh-CN": "settings:language.chineseSimplified",
+  "zh-TW": "settings:language.chineseTraditional",
+};
+
 function LanguageSection() {
   const { locale, setLocale, t } = useLocale();
@@
-  const supported: { value: Locale; label: string }[] = [
-    { value: "en", label: t("settings:language.english") },
-    { value: "he", label: t("settings:language.hebrew") },
-    { value: "zh-CN", label: t("settings:language.chineseSimplified") },
-    { value: "zh-TW", label: t("settings:language.chineseTraditional") },
-  ];
+  const supported: { value: Locale; label: string }[] = SUPPORTED_LOCALES.map((value) => ({
+    value,
+    label: t(LOCALE_LABEL_KEY[value]),
+  }));
🤖 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/components/settings/settings-page.tsx` around lines 239 - 244, Replace
the hardcoded supported array with values derived from the central locale
registry: import the registry (e.g., localeRegistry or supportedLocales) and map
its entries to the picker shape used by the component (value: Locale, label:
string) inside settings-page.tsx, using the existing t(...) translator to
produce labels (e.g., registry.map(entry => ({ value: entry.locale as Locale,
label: t(`settings:language.${entry.key}`) }))). Ensure the new code replaces
the const supported declaration and preserves the Locale type and existing usage
of t so adding/removing locales in the registry automatically updates the
picker.
src/i18n/use-locale.ts (1)

12-12: ⚡ Quick win

Decouple locale plumbing from the theme registry module.

use-locale only needs CJK font loading, but importing from @/lib/themes couples i18n flow to a large theme module. Moving loadCjkFonts into a small dedicated module keeps boundaries cleaner and lowers risk of accidental baseline bundle growth later.

🤖 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/use-locale.ts` at line 12, The import of loadCjkFonts from
"@/lib/themes" couples i18n to the large theme module; extract loadCjkFonts into
a small dedicated module (e.g., src/lib/cjk-fonts.ts) and move only the minimal
implementation there, then update the import in use-locale.ts to import {
loadCjkFonts } from that new module; ensure the new module exports the same
function name/signature, update any other callers if present, and run
build/tests to confirm no missing exports.
🤖 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/zh-TW.json`:
- Line 725: Update the zh-TW locale entry for the key "chineseSimplified" to use
Traditional Chinese characters instead of Simplified: replace the value "简体中文"
with "簡體中文" in the zh-TW JSON so the language picker displays the Simplified
Chinese label in Traditional script; ensure the change is made for the
"chineseSimplified" key in src/i18n/locales/zh-TW.json and that the JSON remains
valid.

---

Nitpick comments:
In `@src/components/settings/settings-page.tsx`:
- Around line 239-244: Replace the hardcoded supported array with values derived
from the central locale registry: import the registry (e.g., localeRegistry or
supportedLocales) and map its entries to the picker shape used by the component
(value: Locale, label: string) inside settings-page.tsx, using the existing
t(...) translator to produce labels (e.g., registry.map(entry => ({ value:
entry.locale as Locale, label: t(`settings:language.${entry.key}`) }))). Ensure
the new code replaces the const supported declaration and preserves the Locale
type and existing usage of t so adding/removing locales in the registry
automatically updates the picker.

In `@src/i18n/use-locale.ts`:
- Line 12: The import of loadCjkFonts from "@/lib/themes" couples i18n to the
large theme module; extract loadCjkFonts into a small dedicated module (e.g.,
src/lib/cjk-fonts.ts) and move only the minimal implementation there, then
update the import in use-locale.ts to import { loadCjkFonts } from that new
module; ensure the new module exports the same function name/signature, update
any other callers if present, and run build/tests to confirm no missing exports.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a7f7f59-40f5-44e9-a23d-e1b163aa9230

📥 Commits

Reviewing files that changed from the base of the PR and between 77c456b and cbf7f0e.

📒 Files selected for processing (10)
  • src/app/globals.css
  • src/components/settings/settings-page.tsx
  • src/i18n/formatters.ts
  • src/i18n/index.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/he.json
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/zh-TW.json
  • src/i18n/use-locale.ts
  • src/lib/themes.ts

"description": "變更語言和閱讀方向。既有頁面會保留各自的文件方向。",
"english": "English",
"hebrew": "עברית",
"chineseSimplified": "简体中文",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use Traditional script for the Simplified Chinese label in zh-TW locale.

At Line 725, chineseSimplified is currently 简体中文; in zh-TW it should be 簡體中文 to keep script consistency in the language picker.

Proposed fix
-      "chineseSimplified": "简体中文",
+      "chineseSimplified": "簡體中文",
📝 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.

Suggested change
"chineseSimplified": "简体中文",
"chineseSimplified": "簡體中文",
🤖 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/zh-TW.json` at line 725, Update the zh-TW locale entry for
the key "chineseSimplified" to use Traditional Chinese characters instead of
Simplified: replace the value "简体中文" with "簡體中文" in the zh-TW JSON so the
language picker displays the Simplified Chinese label in Traditional script;
ensure the change is made for the "chineseSimplified" key in
src/i18n/locales/zh-TW.json and that the JSON remains valid.

hilash added 6 commits May 15, 2026 02:14
Adds ~60 new translation keys across en/he/zh-CN/zh-TW so the Help page
(the entire feature catalog, 13 cards with title/description/cta + chrome
+ Discord footer), the inline help-visuals mockups (kanban Todo/Doing/Done,
cabinet hierarchy labels, conversation chat bubbles + Pending action /
Approve / Decline, theme picker, providers list), and several high-impact
UI strings (Refresh / Edit heartbeat / New Routine / Configure heartbeat /
Open org chart / Dismiss / Search the library / agent-stopped tooltips /
"Shared across all cabinets" / "Saving to") all render in the active locale.

help-page.tsx switches to `<Trans>` so each item title keeps its accent
color span via `<accent>...</accent>` markup inside the translation string —
no per-language span surgery, single key per item.

Brand "Cabinet" continues to render phonetically in Chinese (凯比内特 / 凱比內特)
and as Latin script in Hebrew per CONTRIBUTING_I18N.md.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (5)
src/components/agents/v2/routines-tab.tsx (1)

93-149: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Incomplete localization: only one tooltip is translated while the entire tab remains in English.

Line 273 localizes the locked-routine tooltip, but the rest of the RoutinesTab component remains hardcoded in English:

  • ExplainerCard content (lines 93-103)
  • Stats labels (lines 108-115): "firing", "off", "locked"
  • Search placeholder (line 120)
  • Filter options (lines 131-137): "All states", "Firing", "Off", "Locked (agent stopped)"
  • Empty state messages (lines 142-149)
  • Fallback text (line 244): "(untitled routine)"

This creates the same inconsistent mixed-language experience as in the other tab components.

Consider localizing all three tab components (agents, routines, heartbeats) together to provide a cohesive user experience, or defer these files until complete localization is ready.

Also applies to: 244-244

🤖 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/components/agents/v2/routines-tab.tsx` around lines 93 - 149, The
RoutinesTab component contains many hardcoded English strings (ExplainerCard
content, stats labels in the stats fragment, searchPlaceholder, FilterChip
option labels, empty state titles/hints, and the fallback "(untitled routine)")
that must be localized; replace each literal with calls to your i18n helper
(e.g., t('...')) and use consistent keys for: the ExplainerCard text, stats
labels ("firing", "off", "locked"), the ExplainerIcon aria label,
searchPlaceholder, each FilterChip option ("All states", "Firing", "Off",
"Locked (agent stopped)"), empty state title/hint (dependent on jobs and
filtered), and the fallback "(untitled routine)" so the component (symbols:
ExplainerCard, ExplainerIcon, FilterChip, searchPlaceholder, stats, jobs,
filtered) renders translated strings and matches the localization approach used
by the other tab components.
src/components/agents/v2/heartbeats-tab.tsx (1)

79-143: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Incomplete localization: only one tooltip is translated while the entire tab remains in English.

Line 250 localizes the locked-heartbeat tooltip, but the rest of the HeartbeatsTab component remains hardcoded in English:

  • ExplainerCard content (lines 79-90)
  • Stats labels (lines 95-102): "firing", "off", "locked"
  • Search placeholder (line 107)
  • Filter options (lines 113-116)
  • Button labels (lines 126-130): "Pause all" / "Resume all"
  • Empty state messages (lines 136-143)

Chinese users will see "This agent is stopped…" (localized tooltip) mixed with English UI everywhere else, which is inconsistent and confusing.

Consider localizing the entire tab component in a follow-up, or defer this file until complete localization is ready to avoid the mixed-language UX.

🤖 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/components/agents/v2/heartbeats-tab.tsx` around lines 79 - 143, The
HeartbeatsTab component contains many hardcoded English strings (inside
ExplainerCard text, stats labels rendered from stats.firing/off/locked,
searchPlaceholder, FilterChip options, trailingActions button labels using Pause
and toggleAllHeartbeats, and empty state title/hint) while only the
locked-heartbeat tooltip is localized; update this file to use the
i18n/localization helper used elsewhere (e.g., t(...) or the project's translate
function) for all user-facing strings in ExplainerCard, the stats labels,
searchPlaceholder, FilterChip options, the Pause/Resume button text (in the
trailingActions that calls toggleAllHeartbeats), and the empty.title and
empty.hint so the tab is fully localized and consistent for non-English users.
src/components/agents/v2/new-agent-dialog.tsx (1)

58-59: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Incomplete localization creates mixed-language UX in the dialog.

The dialog title, description, and placeholder are localized (lines 115, 117, 126), but error messages (lines 58-59, 65) and state text (lines 143-149) remain hardcoded in English. Chinese users will see a dialog with Chinese headers but English errors like "Couldn't load the agent library" and "Loading the library…", which is jarring and inconsistent.

Suggested translation keys to add

Extract these strings to translation files:

// Line 58-59
setError(t("agents:dialog.errorLoadLibrary"));

// Line 65
setError(t("agents:dialog.errorLoadLibrary"));

// Line 86
setError(t("agents:dialog.errorAddAgent"));

// Line 143
{t("agents:dialog.loadingLibrary")}

// Lines 147-149
{templates.length === 0
  ? t("agents:dialog.noTemplates")
  : t("agents:dialog.noMatches")}

Also applies to: 65-65, 143-149

🤖 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/components/agents/v2/new-agent-dialog.tsx` around lines 58 - 59, Replace
hardcoded English strings in the dialog with i18n lookups: change the setError
calls that currently pass "Couldn't load the agent library." and the other error
string used in the add-agent flow to use t("agents:dialog.errorLoadLibrary") and
t("agents:dialog.errorAddAgent") respectively (references: setError usages in
this file), replace the visible loading text "Loading the library…" with
t("agents:dialog.loadingLibrary") and the templates state text that currently
uses English with t("agents:dialog.noTemplates") or t("agents:dialog.noMatches")
based on templates.length (reference: templates usage and JSX rendering around
the loading/templates block), and add these keys to the translation resource
files for each locale.
src/components/agents/v2/agents-tab.tsx (1)

79-146: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Incomplete localization: only the org chart button is translated while the rest of the tab remains in English.

Lines 130 and 134 localize the "Org chart" button, but the entire AgentsTab component remains hardcoded in English:

  • ExplainerCard content (lines 79-89)
  • Stats labels (lines 94-99): "active", "departments"
  • Search placeholder (line 104)
  • Filter options (lines 112-122): "All departments", "All", "Active only", "Stopped only"
  • Empty state messages (lines 139-146)

Chinese users will see "组织架构" (org chart button) surrounded by English UI, creating an inconsistent experience.

Consider localizing the entire tab component in a follow-up to provide a consistent user experience.

🤖 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/components/agents/v2/agents-tab.tsx` around lines 79 - 146, The component
contains hardcoded English strings (in ExplainerCard text, stats labels using
activeCount/departmentCount, searchPlaceholder, FilterChip option labels, and
empty state messages around the agents/filtered arrays) while only the org chart
button uses t(); replace those literals with i18n lookups using the existing t
function (e.g. convert the ExplainerCard paragraphs, the "active" and
"departments" labels, searchPlaceholder, each FilterChip option label like "All
departments"/"All"/"Active only"/"Stopped only", and the empty.title/empty.hint
messages) so everything calls t("agents:...") keys, and keep setOrgChartOpen and
the Network button translation unchanged; add missing translation keys to the
locale files accordingly.
src/components/help/help-visuals.tsx (1)

29-36: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused translation hook.

The Stage component declares const { t } = useLocale() but never uses the translation function. This is dead code.

🧹 Proposed fix
 function Stage({ children }: { children: React.ReactNode }) {
-  const { t } = useLocale();
   return (
     <div className="flex h-full w-full items-center justify-center p-6" style={stage}>
       {children}
     </div>
   );
 }
🤖 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/components/help/help-visuals.tsx` around lines 29 - 36, The Stage
component declares an unused translation hook; remove the dead code by deleting
the line that destructures t from useLocale() (const { t } = useLocale();)
inside the Stage function and ensure the component simply returns the JSX using
children; keep the component name Stage and its props signature unchanged.
🧹 Nitpick comments (1)
src/components/onboarding/tour/slide-tasks.tsx (1)

27-27: ⚡ Quick win

Verify that translated typedCommand strings contain @mentions.

The regex split on line 55 expects the translated command to include @word or @word/ patterns for special styling. If Chinese translations don't preserve these patterns, the styling won't apply correctly.

Consider documenting the expected format in translation files (e.g., via comments) or adding a fallback that renders the entire string uniformly if no matches are found.

Also applies to: 55-67

🤖 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/components/onboarding/tour/slide-tasks.tsx` at line 27, The translated
TYPED_COMMAND may not include expected "@mention" patterns, so update the
rendering logic in slide-tasks.tsx where TYPED_COMMAND is split by the
mention-regex: detect whether the split/match returns any mention tokens and, if
none, render the entire TYPED_COMMAND as a single styled element (fallback)
instead of mapping for mentions; alternatively add a short comment in the
translation keys indicating the required "@word" or "@word/" pattern for
translators. Ensure you reference TYPED_COMMAND and the mention-splitting code
so the code path handles both matched and unmatched translations.
🤖 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/components/onboarding/tour/slide-agents.tsx`:
- Around line 178-184: Replace the concatenated t(...) calls with a single
translatable sentence key (e.g. "slideAgents:paragraph") that uses
placeholders/markup for the emphasized words so translators can reorder terms
per locale; keep the emphasized styling (className="font-medium" and style={{
color: P.text }}) but inject those as placeholder elements via your i18n
solution (React-i18next Trans or element interpolation) for the persona,
schedule, and memory placeholders; update the JSX in slide-agents.tsx to render
that single translation with the three styled spans inserted into the
placeholders.

In `@src/components/onboarding/tour/slide-data.tsx`:
- Around line 583-586: Current paragraph composes
t("slideDataCopy:paragraphPrefix") + inline <span className="font-mono" style={{
color: P.accent }}>@</span> + t("slideDataCopy:paragraphSuffix"), which breaks
localization ordering; replace these fragmented keys with one full-sentence
translation key (e.g. "slideDataCopy:paragraph") that includes a
placeholder/markup for the styled "@" token and update the component to render
the single key using your i18n markup/interpolation method (use Trans or t with
interpolation/html rendering) so translators can reorder the styled token
correctly while preserving the <span className="font-mono" style={{ color:
P.accent }}> markup and the P.accent value.

---

Outside diff comments:
In `@src/components/agents/v2/agents-tab.tsx`:
- Around line 79-146: The component contains hardcoded English strings (in
ExplainerCard text, stats labels using activeCount/departmentCount,
searchPlaceholder, FilterChip option labels, and empty state messages around the
agents/filtered arrays) while only the org chart button uses t(); replace those
literals with i18n lookups using the existing t function (e.g. convert the
ExplainerCard paragraphs, the "active" and "departments" labels,
searchPlaceholder, each FilterChip option label like "All
departments"/"All"/"Active only"/"Stopped only", and the empty.title/empty.hint
messages) so everything calls t("agents:...") keys, and keep setOrgChartOpen and
the Network button translation unchanged; add missing translation keys to the
locale files accordingly.

In `@src/components/agents/v2/heartbeats-tab.tsx`:
- Around line 79-143: The HeartbeatsTab component contains many hardcoded
English strings (inside ExplainerCard text, stats labels rendered from
stats.firing/off/locked, searchPlaceholder, FilterChip options, trailingActions
button labels using Pause and toggleAllHeartbeats, and empty state title/hint)
while only the locked-heartbeat tooltip is localized; update this file to use
the i18n/localization helper used elsewhere (e.g., t(...) or the project's
translate function) for all user-facing strings in ExplainerCard, the stats
labels, searchPlaceholder, FilterChip options, the Pause/Resume button text (in
the trailingActions that calls toggleAllHeartbeats), and the empty.title and
empty.hint so the tab is fully localized and consistent for non-English users.

In `@src/components/agents/v2/new-agent-dialog.tsx`:
- Around line 58-59: Replace hardcoded English strings in the dialog with i18n
lookups: change the setError calls that currently pass "Couldn't load the agent
library." and the other error string used in the add-agent flow to use
t("agents:dialog.errorLoadLibrary") and t("agents:dialog.errorAddAgent")
respectively (references: setError usages in this file), replace the visible
loading text "Loading the library…" with t("agents:dialog.loadingLibrary") and
the templates state text that currently uses English with
t("agents:dialog.noTemplates") or t("agents:dialog.noMatches") based on
templates.length (reference: templates usage and JSX rendering around the
loading/templates block), and add these keys to the translation resource files
for each locale.

In `@src/components/agents/v2/routines-tab.tsx`:
- Around line 93-149: The RoutinesTab component contains many hardcoded English
strings (ExplainerCard content, stats labels in the stats fragment,
searchPlaceholder, FilterChip option labels, empty state titles/hints, and the
fallback "(untitled routine)") that must be localized; replace each literal with
calls to your i18n helper (e.g., t('...')) and use consistent keys for: the
ExplainerCard text, stats labels ("firing", "off", "locked"), the ExplainerIcon
aria label, searchPlaceholder, each FilterChip option ("All states", "Firing",
"Off", "Locked (agent stopped)"), empty state title/hint (dependent on jobs and
filtered), and the fallback "(untitled routine)" so the component (symbols:
ExplainerCard, ExplainerIcon, FilterChip, searchPlaceholder, stats, jobs,
filtered) renders translated strings and matches the localization approach used
by the other tab components.

In `@src/components/help/help-visuals.tsx`:
- Around line 29-36: The Stage component declares an unused translation hook;
remove the dead code by deleting the line that destructures t from useLocale()
(const { t } = useLocale();) inside the Stage function and ensure the component
simply returns the JSX using children; keep the component name Stage and its
props signature unchanged.

---

Nitpick comments:
In `@src/components/onboarding/tour/slide-tasks.tsx`:
- Line 27: The translated TYPED_COMMAND may not include expected "@mention"
patterns, so update the rendering logic in slide-tasks.tsx where TYPED_COMMAND
is split by the mention-regex: detect whether the split/match returns any
mention tokens and, if none, render the entire TYPED_COMMAND as a single styled
element (fallback) instead of mapping for mentions; alternatively add a short
comment in the translation keys indicating the required "@word" or "@word/"
pattern for translators. Ensure you reference TYPED_COMMAND and the
mention-splitting code so the code path handles both matched and unmatched
translations.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96b67074-b586-48a9-ae35-86bf1ccf8514

📥 Commits

Reviewing files that changed from the base of the PR and between cbf7f0e and 682605c.

📒 Files selected for processing (24)
  • src/components/agents/agent-detail-v2.tsx
  • src/components/agents/agent-row.tsx
  • src/components/agents/v2/agents-tab.tsx
  • src/components/agents/v2/heartbeats-tab.tsx
  • src/components/agents/v2/new-agent-dialog.tsx
  • src/components/agents/v2/routines-tab.tsx
  • src/components/agents/v2/tab-explainer.tsx
  • src/components/agents/v2/tabs-layout.tsx
  • src/components/cabinets/depth-dropdown.tsx
  • src/components/help/help-page.tsx
  • src/components/help/help-visuals.tsx
  • src/components/home/home-screen.tsx
  • src/components/layout/status-bar.tsx
  • src/components/onboarding/tour/mockup-sidebar.tsx
  • src/components/onboarding/tour/slide-agents.tsx
  • src/components/onboarding/tour/slide-data.tsx
  • src/components/onboarding/tour/slide-intro.tsx
  • src/components/onboarding/tour/slide-tasks.tsx
  • src/components/sidebar/tree-node.tsx
  • src/components/sidebar/tree-view.tsx
  • src/i18n/locales/en.json
  • src/i18n/locales/he.json
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (4)
  • src/components/sidebar/tree-node.tsx
  • src/components/agents/agent-detail-v2.tsx
  • src/i18n/locales/zh-TW.json
  • src/components/sidebar/tree-view.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/i18n/locales/zh-CN.json

Comment on lines +178 to +184
{t("slideAgents:paragraphPrefix")}
<span className="font-medium" style={{ color: P.text }}>{t("slideAgents:personaWord")}</span>
{t("slideAgents:paragraphMiddle")}
<span className="font-medium" style={{ color: P.text }}>{t("slideAgents:scheduleWord")}</span>
{t("slideAgents:paragraphMiddle2")}
<span className="font-medium" style={{ color: P.text }}>{t("slideAgents:memoryWord")}</span>
{t("slideAgents:paragraphSuffix")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a single translation sentence for this paragraph.

Lines 178-184 stitch the sentence from multiple keys plus inline emphasized words. That locks source-language order and can read incorrectly in some locales. Use one translatable sentence key with placeholders/markup for the emphasized terms.

🤖 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/components/onboarding/tour/slide-agents.tsx` around lines 178 - 184,
Replace the concatenated t(...) calls with a single translatable sentence key
(e.g. "slideAgents:paragraph") that uses placeholders/markup for the emphasized
words so translators can reorder terms per locale; keep the emphasized styling
(className="font-medium" and style={{ color: P.text }}) but inject those as
placeholder elements via your i18n solution (React-i18next Trans or element
interpolation) for the persona, schedule, and memory placeholders; update the
JSX in slide-agents.tsx to render that single translation with the three styled
spans inserted into the placeholders.

Comment on lines +583 to 586
{t("slideDataCopy:paragraphPrefix")}
<span className="font-mono" style={{ color: P.accent }}>@</span>
{t("slideDataCopy:paragraphSuffix")}
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid prefix/suffix fragment composition in localized paragraphs.

These paragraphs are built from multiple key fragments plus inline inserts, which can force unnatural ordering in non-English locales. Prefer single full-sentence translation keys with placeholders/markup for the styled segments.

Also applies to: 595-598

🤖 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/components/onboarding/tour/slide-data.tsx` around lines 583 - 586,
Current paragraph composes t("slideDataCopy:paragraphPrefix") + inline <span
className="font-mono" style={{ color: P.accent }}>@</span> +
t("slideDataCopy:paragraphSuffix"), which breaks localization ordering; replace
these fragmented keys with one full-sentence translation key (e.g.
"slideDataCopy:paragraph") that includes a placeholder/markup for the styled "@"
token and update the component to render the single key using your i18n
markup/interpolation method (use Trans or t with interpolation/html rendering)
so translators can reorder the styled token correctly while preserving the <span
className="font-mono" style={{ color: P.accent }}> markup and the P.accent
value.

hilash added 2 commits May 15, 2026 07:26
The model + effort + provider picker is one of the most-visible composer
surfaces. Extracts: Model column header, Auto effort, Ready/Log in/Not
installed provider status, Default/Auto effort fallback, App default /
Select app default action labels, Loading and 'using system default'
trigger titles, 'No providers available' empty state, 'click to launch in
PTY terminal' tooltip.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
src/i18n/locales/en.json (1)

31-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Duplicate orgChart key in agents.workspace causes silent override.

orgChart is defined twice (Line 31 and Line 59). In JSON objects, the later value wins, so the Line 31 string is effectively ignored. Please keep one orgChart key and rename the other intent-specific label (for example, orgChartTitle vs orgChartAction) to avoid accidental overrides.

Proposed fix
-      "orgChart": "Your Team Org Chart",
+      "orgChartTitle": "Your Team Org Chart",
...
       "openOrgChart": "Open org chart",
       "orgChart": "Org chart",

Also applies to: 59-59

🤖 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` at line 31, The en.json contains a duplicate key
"orgChart" under agents.workspace causing the later value to override the
earlier one; remove the duplicate by keeping one "orgChart" and rename the other
to a distinct, intent-specific key (e.g., "orgChartTitle" or "orgChartAction")
in src/i18n/locales/en.json, then update any code references that read the
renamed key (search for usages of agents.workspace.orgChart in the codebase) to
use the new key name so consumers continue to work.
🤖 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.

Outside diff comments:
In `@src/i18n/locales/en.json`:
- Line 31: The en.json contains a duplicate key "orgChart" under
agents.workspace causing the later value to override the earlier one; remove the
duplicate by keeping one "orgChart" and rename the other to a distinct,
intent-specific key (e.g., "orgChartTitle" or "orgChartAction") in
src/i18n/locales/en.json, then update any code references that read the renamed
key (search for usages of agents.workspace.orgChart in the codebase) to use the
new key name so consumers continue to work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 15c5fd1b-6ec8-446f-be2b-5030235817e1

📥 Commits

Reviewing files that changed from the base of the PR and between 682605c and f21ffb4.

📒 Files selected for processing (14)
  • src/components/agents/agent-detail-v2.tsx
  • src/components/composer/start-work-dialog.tsx
  • src/components/composer/task-runtime-picker.tsx
  • src/components/help/whats-new-card.tsx
  • src/components/home/home-screen.tsx
  • src/components/layout/narrow-viewport-hint.tsx
  • src/components/settings/settings-page.tsx
  • src/components/tasks/board/filter-bar.tsx
  • src/components/tasks/board/kanban-view.tsx
  • src/components/tasks/board/view-toggle.tsx
  • src/i18n/locales/en.json
  • src/i18n/locales/he.json
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (2)
  • src/i18n/locales/zh-TW.json
  • src/i18n/locales/he.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/home/home-screen.tsx
  • src/components/agents/agent-detail-v2.tsx
  • src/i18n/locales/zh-CN.json

hilash added 2 commits May 15, 2026 07:39
…view, error fallbacks

Wires up the remaining first-run + scheduling + skills surfaces. Onboarding
wizard's '/Start your Cabinet/' launch heading now uses <Trans> with
<accent> markup. Mission-control's schedule-picker (the actual cron picker
mounted by composer/scheduling-fields, agents-workspace, new-routine-dialog)
gets translated mode labels (Interval/Daily/Weekdays/Weekly/Monthly/Custom),
weekday short labels (Mo–Su), Parse button, Show/Hide cron toggle. Task
board's schedule-view now passes locale to Intl.DateTimeFormat for proper
month names. Skills library/add gets Import/Imported/Importing + Preview.
Agent-library dialog errors translate.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/components/skills/skill-library.tsx (1)

403-466: ⚡ Quick win

Consider translating the section header and related UI text for consistency.

The import button labels are now localized, but the "Discoverable in your workspace ... — click to import" header (line 415) remains in English. For a cohesive user experience, users viewing the UI in Chinese should see both the section header and button labels in their selected language.

Similarly, lines 480 and 493–494 (system skills section) contain untranslated text.

Suggested additions to complete this section's i18n

Add translation keys for:

  • Line 415: Section header with discovered count and instruction
  • Line 480: System skills section header
  • Lines 493–494: System skills warning text
  • Line 374: "Add Skill" button
  • Line 389: "Add your first skill" button
🤖 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/components/skills/skill-library.tsx` around lines 403 - 466, The visible
English header "Discoverable in your workspace ({discovered.length}) — click to
import" should be wrapped with the i18n helper (t) and replaced with a
translation key (e.g. t("skillLibrary:discoverableHeader", { count:
discovered.length })); likewise replace the system section header text ("System
skills") and its warning lines with translation keys (e.g.
t("skillLibrary:systemHeader") and t("skillLibrary:systemWarning")), and change
the "Add Skill" and "Add your first skill" button labels to use
t("skillLibrary:addSkill") and t("skillLibrary:addFirstSkill") respectively;
update usages in the JSX where discoverOpen, discovered, handleDiscoveredImport,
and the Button components render text so they pull from t(...) and add the new
keys to the locale files.
🤖 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/components/mission-control/schedule-picker.tsx`:
- Line 250: When state.nlParsing is true the button becomes icon-only and loses
its accessible name; update the render inside the schedule-picker component so
that when state.nlParsing you either (a) keep the parse text visually hidden
(e.g., render t("schedulePicker:parse") with a "sr-only" class alongside
Loader2) or (b) add an explicit aria-label on the button using
t("schedulePicker:parse"); target the expression that currently renders
{state.nlParsing ? <Loader2 ... /> : t("schedulePicker:parse")} and ensure
accessibility by including the visible text as hidden or an aria-label while
keeping Loader2 for the spinner.

In `@src/components/tasks/board/schedule-view.tsx`:
- Around line 64-68: The comment and implementation of monthOf(d: Date)
incorrectly rely on toLocaleDateString(undefined) — change it to use the app's
explicit locale: import and call useLocale() to get the current app locale,
format it with bcp47() from src/i18n/formatters.ts, and pass that string as the
first argument to toLocaleDateString (i.e., replace undefined with the
bcp47(useLocale()) result) so monthOf and its comment reflect the app-selected
language rather than the browser default.

---

Nitpick comments:
In `@src/components/skills/skill-library.tsx`:
- Around line 403-466: The visible English header "Discoverable in your
workspace ({discovered.length}) — click to import" should be wrapped with the
i18n helper (t) and replaced with a translation key (e.g.
t("skillLibrary:discoverableHeader", { count: discovered.length })); likewise
replace the system section header text ("System skills") and its warning lines
with translation keys (e.g. t("skillLibrary:systemHeader") and
t("skillLibrary:systemWarning")), and change the "Add Skill" and "Add your first
skill" button labels to use t("skillLibrary:addSkill") and
t("skillLibrary:addFirstSkill") respectively; update usages in the JSX where
discoverOpen, discovered, handleDiscoveredImport, and the Button components
render text so they pull from t(...) and add the new keys to the locale files.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 181cacae-5288-4aab-97d6-c8ae5a924089

📥 Commits

Reviewing files that changed from the base of the PR and between f21ffb4 and 36eb912.

📒 Files selected for processing (10)
  • src/components/agents/v2/new-agent-dialog.tsx
  • src/components/mission-control/schedule-picker.tsx
  • src/components/onboarding/onboarding-wizard.tsx
  • src/components/skills/skill-add-dialog.tsx
  • src/components/skills/skill-library.tsx
  • src/components/tasks/board/schedule-view.tsx
  • src/i18n/locales/en.json
  • src/i18n/locales/he.json
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (4)
  • src/components/skills/skill-add-dialog.tsx
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/zh-TW.json
  • src/i18n/locales/he.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/agents/v2/new-agent-dialog.tsx
  • src/i18n/locales/en.json

className="px-3 py-1.5 text-[11px] bg-muted/30 border border-border/40 rounded-lg hover:bg-muted/60 disabled:opacity-40 flex items-center gap-1.5 shrink-0 transition-colors"
>
{state.nlParsing ? <Loader2 className="h-3 w-3 animate-spin" /> : "Parse"}
{state.nlParsing ? <Loader2 className="h-3 w-3 animate-spin" /> : t("schedulePicker:parse")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep an accessible label while parsing.

When nlParsing is true, the button becomes icon-only, which drops its accessible name for screen readers. Keep text (or add aria-label) in the loading state.

Suggested fix
-          {state.nlParsing ? <Loader2 className="h-3 w-3 animate-spin" /> : t("schedulePicker:parse")}
+          {state.nlParsing ? (
+            <>
+              <Loader2 className="h-3 w-3 animate-spin" />
+              <span>{t("schedulePicker:parse")}</span>
+            </>
+          ) : (
+            t("schedulePicker:parse")
+          )}
📝 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.

Suggested change
{state.nlParsing ? <Loader2 className="h-3 w-3 animate-spin" /> : t("schedulePicker:parse")}
{state.nlParsing ? (
<>
<Loader2 className="h-3 w-3 animate-spin" />
<span>{t("schedulePicker:parse")}</span>
</>
) : (
t("schedulePicker:parse")
)}
🤖 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/components/mission-control/schedule-picker.tsx` at line 250, When
state.nlParsing is true the button becomes icon-only and loses its accessible
name; update the render inside the schedule-picker component so that when
state.nlParsing you either (a) keep the parse text visually hidden (e.g., render
t("schedulePicker:parse") with a "sr-only" class alongside Loader2) or (b) add
an explicit aria-label on the button using t("schedulePicker:parse"); target the
expression that currently renders {state.nlParsing ? <Loader2 ... /> :
t("schedulePicker:parse")} and ensure accessibility by including the visible
text as hidden or an aria-label while keeping Loader2 for the spinner.

Comment on lines +64 to +68
// Use Intl.DateTimeFormat with `undefined` locale so the browser
// picks up document language automatically — this hands us proper
// Hebrew month names for `<html lang="he">` and Chinese for zh-*.
const monthOf = (d: Date) =>
d.toLocaleDateString(undefined, { month: "long" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In JavaScript Intl, does Date.prototype.toLocaleDateString(undefined, ...) use the browser default locale or HTML document language (<html lang>)? Please cite MDN/ECMA-402.

💡 Result:

Date.prototype.toLocaleDateString(undefined,...) uses the runtime/host environment’s default locale (ECMA-402 DefaultLocale), not the HTML document’s language. Reasoning grounded in ECMA-402/MDN: 1) ECMA-402 defines DefaultLocale as the locale identifier for the host environment’s current locale (not document language). [1][2] 2) Intl constructors (which toLocaleDateString delegates to when Intl is available) use the runtime’s default locale when the locales argument is undefined. MDN states that if undefined is passed, “the runtime’s default locale is used.” [3] 3) MDN also states that toLocaleDateString “Basic use of this method without specifying a locale returns a formatted string in the default locale,” and that locales/options correspond to Intl.DateTimeFormat parameters. [4] Therefore, passing undefined for the locales parameter makes toLocaleDateString follow the browser/host default locale derived by ECMA-402, which in practice typically tracks the user’s browser language settings, and not the attribute.

Citations:


🏁 Script executed:

# First, let's examine the schedule-view.tsx file to understand context and imports
head -100 src/components/tasks/board/schedule-view.tsx

Repository: hilash/cabinet

Length of output: 3654


🏁 Script executed:

# Search for i18n, locale, or language-related setup in the codebase
rg -i "i18n|locale|language" --max-count=20 -B 2 -A 2 src/

Repository: hilash/cabinet

Length of output: 50371


🏁 Script executed:

# Check if there's a language/locale context or hook
rg -i "uselocale|uselang|context.*lang|context.*locale" src/ -A 3

Repository: hilash/cabinet

Length of output: 50371


🏁 Script executed:

# Look for where <html lang> is set
rg -i "html.*lang|documentElement\.lang" src/ -B 2 -A 2

Repository: hilash/cabinet

Length of output: 4340


Code comment is misleading about <html lang> behavior; use app's locale instead.

The code comment incorrectly claims toLocaleDateString(undefined) will pick up the document's <html lang> attribute. Per ECMA-402, undefined uses the runtime's default locale (browser language settings), not the HTML document language. This causes mismatched labels when the app's selected language differs from the browser default.

Pass the app's locale explicitly instead. The codebase already provides useLocale() hook and a bcp47() formatter in src/i18n/formatters.ts for exactly this purpose.

Suggested direction
+  const { locale } = useLocale();
+  const bcp47 = (l: Locale | undefined | null) => 
+    LOCALE_TO_BCP47[l ?? DEFAULT_LOCALE];
   const monthOf = (d: Date) =>
-    d.toLocaleDateString(undefined, { month: "long" });
+    d.toLocaleDateString(bcp47(locale), { month: "long" });
📝 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.

Suggested change
// Use Intl.DateTimeFormat with `undefined` locale so the browser
// picks up document language automatically — this hands us proper
// Hebrew month names for `<html lang="he">` and Chinese for zh-*.
const monthOf = (d: Date) =>
d.toLocaleDateString(undefined, { month: "long" });
// Use app's locale explicitly to respect user's language preference
const { locale } = useLocale();
const monthOf = (d: Date) =>
d.toLocaleDateString(bcp47(locale), { month: "long" });
🤖 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/components/tasks/board/schedule-view.tsx` around lines 64 - 68, The
comment and implementation of monthOf(d: Date) incorrectly rely on
toLocaleDateString(undefined) — change it to use the app's explicit locale:
import and call useLocale() to get the current app locale, format it with
bcp47() from src/i18n/formatters.ts, and pass that string as the first argument
to toLocaleDateString (i.e., replace undefined with the bcp47(useLocale())
result) so monthOf and its comment reflect the app-selected language rather than
the browser default.

Translates every visible string: header, rating prompt, 5-star aria labels,
Like Cabinet? + GitHub star CTA + tooltip variants, background-field label
+ '(optional)', Discord prose, Maybe later / Send buttons, both trigger-2
and trigger-6 lead/q1/q1Hint/q2/q2Hint copy variants. The COPY object
keeps its English values as i18next defaultValue fallbacks so the dashboard
ingestion stays English by default while the UI renders the active locale.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/onboarding/feedback-popup.tsx (1)

418-421: ⚡ Quick win

Avoid concatenating translated fragments with hardcoded separators.

These labels are built from multiple keys plus " ", which constrains grammar and spacing per locale. Prefer single translatable strings for each full phrase/call-to-action.

♻️ Proposed direction
- {t("feedback:backgroundLabel")}{" "}
- <span className="text-muted-foreground/70 font-normal">
-   {t("feedback:optional")}
- </span>
+ {t("feedback:backgroundLabelOptional")}
- {t("feedback:discordCta")}
- {" "}
- <span className="underline">{t("feedback:joinDiscord")}</span> →
+ <span className="underline">{t("feedback:discordJoinCta")}</span>

Also applies to: 443-446

🤖 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/components/onboarding/feedback-popup.tsx` around lines 418 - 421, The JSX
in feedback-popup.tsx is concatenating translated fragments with a hardcoded
space (e.g., t("feedback:backgroundLabel") + " " + <span>...), which breaks
grammar and spacing for other locales; replace these concatenations with single
translation keys for the full phrase/call-to-action (update the strings used by
the t function where the snippets appear, including the similar occurrences
around lines 443-446) so each label is rendered from one
t("feedback:fullBackgroundLabel")-style key (or pluralized/variant keys as
needed) and remove the manual " " separator to ensure locale-correct punctuation
and spacing.
🤖 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.

Nitpick comments:
In `@src/components/onboarding/feedback-popup.tsx`:
- Around line 418-421: The JSX in feedback-popup.tsx is concatenating translated
fragments with a hardcoded space (e.g., t("feedback:backgroundLabel") + " " +
<span>...), which breaks grammar and spacing for other locales; replace these
concatenations with single translation keys for the full phrase/call-to-action
(update the strings used by the t function where the snippets appear, including
the similar occurrences around lines 443-446) so each label is rendered from one
t("feedback:fullBackgroundLabel")-style key (or pluralized/variant keys as
needed) and remove the manual " " separator to ensure locale-correct punctuation
and spacing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a906ea4-b982-4609-b3d9-3658e8c281cc

📥 Commits

Reviewing files that changed from the base of the PR and between 36eb912 and 9090f99.

📒 Files selected for processing (5)
  • src/components/onboarding/feedback-popup.tsx
  • src/i18n/locales/en.json
  • src/i18n/locales/he.json
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (2)
  • src/i18n/locales/zh-TW.json
  • src/i18n/locales/he.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/en.json

Settings: Profile (how-you-appear, displayName hint, icon hint, tint hint,
Save/Saving/Saved, browse-all/show-favorites/show-fewer), Workspace
subtitle, Appearance (sidebar desc + hidden-files hint), Storage (every
label/button/hint + error toasts + env-var note), Providers (subtitle,
default-runtime, CLI agents header, status text, empty matrix), About
(tagline, framework/storage/AI rows, philosophy, privacy body+link,
telemetry hints, Cabinet Cloud body+waitlist+errors, Connect section),
Notifications (channels + rules + preview, all 4 channels + 4 rules).

Schedule tab: explainer prose, Day/Week/Month buttons, about-aria;
schedule-calendar weekday names now via Intl.DateTimeFormat (locale-aware,
removed dead MONTH_NAMES).

Tasks board: bulk-delete confirm dialog (titles, bodies, filtered
variants, confirm labels, empty-state, filter-scope phrases, aria labels).

All 1081 keys at full parity across en/he/zh-CN/zh-TW.
@hilash
hilash merged commit 4d8da89 into main May 15, 2026
1 check passed
@hilash
hilash deleted the feat/i18n-chinese branch May 15, 2026 08:48
agenticocho pushed a commit to agenticocho/cabinet that referenced this pull request May 18, 2026
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 Request: Internationalization (i18n) support and Chinese localization

1 participant