feat(i18n): Chinese localization — Simplified + Traditional - #86
Conversation
…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.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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. Changesi18n + CJK integration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/components/settings/settings-page.tsx (1)
239-244: ⚡ Quick winGenerate 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 winDecouple locale plumbing from the theme registry module.
use-localeonly needs CJK font loading, but importing from@/lib/themescouples i18n flow to a large theme module. MovingloadCjkFontsinto 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
📒 Files selected for processing (10)
src/app/globals.csssrc/components/settings/settings-page.tsxsrc/i18n/formatters.tssrc/i18n/index.tssrc/i18n/locales/en.jsonsrc/i18n/locales/he.jsonsrc/i18n/locales/zh-CN.jsonsrc/i18n/locales/zh-TW.jsonsrc/i18n/use-locale.tssrc/lib/themes.ts
| "description": "變更語言和閱讀方向。既有頁面會保留各自的文件方向。", | ||
| "english": "English", | ||
| "hebrew": "עברית", | ||
| "chineseSimplified": "简体中文", |
There was a problem hiding this comment.
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.
| "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.
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.
There was a problem hiding this comment.
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 liftIncomplete 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 liftIncomplete 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 winIncomplete 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 liftIncomplete 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 winRemove unused translation hook.
The
Stagecomponent declaresconst { 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 winVerify that translated
typedCommandstrings contain@mentions.The regex split on line 55 expects the translated command to include
@wordor@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
📒 Files selected for processing (24)
src/components/agents/agent-detail-v2.tsxsrc/components/agents/agent-row.tsxsrc/components/agents/v2/agents-tab.tsxsrc/components/agents/v2/heartbeats-tab.tsxsrc/components/agents/v2/new-agent-dialog.tsxsrc/components/agents/v2/routines-tab.tsxsrc/components/agents/v2/tab-explainer.tsxsrc/components/agents/v2/tabs-layout.tsxsrc/components/cabinets/depth-dropdown.tsxsrc/components/help/help-page.tsxsrc/components/help/help-visuals.tsxsrc/components/home/home-screen.tsxsrc/components/layout/status-bar.tsxsrc/components/onboarding/tour/mockup-sidebar.tsxsrc/components/onboarding/tour/slide-agents.tsxsrc/components/onboarding/tour/slide-data.tsxsrc/components/onboarding/tour/slide-intro.tsxsrc/components/onboarding/tour/slide-tasks.tsxsrc/components/sidebar/tree-node.tsxsrc/components/sidebar/tree-view.tsxsrc/i18n/locales/en.jsonsrc/i18n/locales/he.jsonsrc/i18n/locales/zh-CN.jsonsrc/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
| {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")} |
There was a problem hiding this comment.
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.
| {t("slideDataCopy:paragraphPrefix")} | ||
| <span className="font-mono" style={{ color: P.accent }}>@</span> | ||
| {t("slideDataCopy:paragraphSuffix")} | ||
| </p> |
There was a problem hiding this comment.
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.
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.
There was a problem hiding this comment.
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 winDuplicate
orgChartkey inagents.workspacecauses silent override.
orgChartis defined twice (Line 31 and Line 59). In JSON objects, the later value wins, so the Line 31 string is effectively ignored. Please keep oneorgChartkey and rename the other intent-specific label (for example,orgChartTitlevsorgChartAction) 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
📒 Files selected for processing (14)
src/components/agents/agent-detail-v2.tsxsrc/components/composer/start-work-dialog.tsxsrc/components/composer/task-runtime-picker.tsxsrc/components/help/whats-new-card.tsxsrc/components/home/home-screen.tsxsrc/components/layout/narrow-viewport-hint.tsxsrc/components/settings/settings-page.tsxsrc/components/tasks/board/filter-bar.tsxsrc/components/tasks/board/kanban-view.tsxsrc/components/tasks/board/view-toggle.tsxsrc/i18n/locales/en.jsonsrc/i18n/locales/he.jsonsrc/i18n/locales/zh-CN.jsonsrc/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
…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.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/components/skills/skill-library.tsx (1)
403-466: ⚡ Quick winConsider 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
📒 Files selected for processing (10)
src/components/agents/v2/new-agent-dialog.tsxsrc/components/mission-control/schedule-picker.tsxsrc/components/onboarding/onboarding-wizard.tsxsrc/components/skills/skill-add-dialog.tsxsrc/components/skills/skill-library.tsxsrc/components/tasks/board/schedule-view.tsxsrc/i18n/locales/en.jsonsrc/i18n/locales/he.jsonsrc/i18n/locales/zh-CN.jsonsrc/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")} |
There was a problem hiding this comment.
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.
| {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.
| // 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" }); |
There was a problem hiding this comment.
🧩 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:
- 1: https://ecma-international.org/wp-content/uploads/ECMA-402_11th_edition_june_2024.pdf
- 2: https://402.ecma-international.org/9.0/
- 3: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
- 4: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString
🏁 Script executed:
# First, let's examine the schedule-view.tsx file to understand context and imports
head -100 src/components/tasks/board/schedule-view.tsxRepository: 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 3Repository: 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 2Repository: 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.
| // 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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/onboarding/feedback-popup.tsx (1)
418-421: ⚡ Quick winAvoid 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
📒 Files selected for processing (5)
src/components/onboarding/feedback-popup.tsxsrc/i18n/locales/en.jsonsrc/i18n/locales/he.jsonsrc/i18n/locales/zh-CN.jsonsrc/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.
Closes #79.
Summary
en.jsonandhe.jsonchineseSimplified/chineseTraditional) added toen.jsonandhe.jsonso every locale labels the new options in its own scriptCJK font fallback
loadCjkFonts(locale)insrc/lib/themes.tspulls Noto Sans SC or Noto Sans TC from Google Fonts when locale is Chinese — same<link>swap pattern as the existing theme font loaderhtml:lang(zh-*)inglobals.cssappend the loaded CJK family + system fallbacks (PingFang SC/TC, Microsoft YaHei/JhengHei) after the theme's Latin fontWhat's here vs. what's not
resources/getting-started-*) — not translated; covered by P4 of the i18n PRDTest plan
npx tsc --noEmitcleanjqvalidates both JSON filespaths(scalars)parity check: zero missing keys vs en.json (only the 2 new label keys are extra)<html lang="zh-CN">set, Noto Sans SC<link>loaded, settings page renders translated (姓名 / 显示名称 / 工作区 / 头像 / etc.), brand renders as 凯比内特 in "新建凯比内特"Summary by CodeRabbit
New Features
Chores