feat: Add UI localization (i18n) across dashboard and terminal UI#1352
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SUPPORTED_LOCALES (en, zh-CN, zh-TW, fr, es), Locale, DEFAULT_LOCALE, isLocale, and validateLocale to @fusion/core, plus an optional GlobalSettings.language field registered in the schema defaults and keys. Locale primitives live in types.ts so the dashboard's @fusion/core->types.ts Vite alias can reach them. language defaults to undefined (resolve-at-runtime). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng (U2) Create the @fusion/i18n package as the authored source-of-truth: shared i18next config (namespace split, script-aware zh-CN/zh-TW fallback, plural setup), en base catalogs, and a generated CLI static-import map so the terminal surface is drop-in for new locales. Add the i18next-cli workflow (extract/sync/types/status/lint) wired as root i18n:* scripts, install the i18next stack into dashboard + CLI, strip @fusion/i18n from the published CLI manifest, gitignore the generated dashboard catalog tree, and add a changeset. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Initialize the browser i18next instance with lazy per-locale catalog loading, localStorage->navigator->htmlTag detection, script-aware zh fallback, and the shared @fusion/i18n config. Catalogs are synced from @fusion/i18n into a gitignored app/locales/ (predev/prebuild) and imported app-relative so Vite emits one chunk per locale/namespace (verified: 15 chunks, none inlined into the main bundle). First paint is gated on i18nReady to avoid raw-key flashes; <I18nextProvider> wraps the App provider stack; vendor-i18n manualChunk added. A build-assertion script (verify:locale-chunks) guards the KTD3a splitting invariant. Fallback/namespace behavior is covered by @fusion/i18n config tests; the live instance is verified via the build assertion rather than a unit test (the dynamic catalog backend is impractical to exercise in vitest). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add useLanguage (three-tier persistence mirroring useTheme: localStorage cache + server GlobalSettings write-through + hydrate-on-mount, local choice wins) and a LanguageSelector rendered in the Settings Appearance section. Switching applies in place via i18n.changeLanguage — no reload, so unsaved state and in-flight agent views survive. Endonyms name each language in its own script. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ting (U5) Add the two reusable migration primitives: useLocaleFormat (date/number bound to the active locale, replacing implicit-browser-locale toLocale* calls — R8) and useColumnLabel (translate core COLUMN_LABELS via common:columns.* with English fallback). Migrate the Settings Appearance heading to t() as a worked example. The bulk migration of the ~464-file long tail is deferred follow-up work per the plan's Scope Boundaries; the pattern and primitives are in place so remaining clusters are mechanical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a synchronous Node-side i18next instance built from the generated @fusion/i18n CLI catalog map (no async backend, first frame localized), with locale precedence --lang flag -> GlobalSettings.language -> env (LC_ALL/LANG/..) -> en. Wrap the Ink DashboardApp render in <I18nextProvider> and thread a --lang flag through runDashboard. Upgrade ink 6.8 -> 7.0 (native CJK double-width measurement) and raise the react/@types/react peer floor to ^19.2.0. A spike test confirms react-i18next works under Ink's custom reconciler: localized first frame + re-render on changeLanguage (including CJK), retiring the KTD1 unknown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Establish the terminal-UI migration pattern: route BoardView's loading/empty
labels through useTranslation("cli"), keeping single-letter keybinding
accelerators literal via interpolation ([{{key}}]). Add the keys to the en cli
catalog, sync the four locales, and regenerate the CLI import map. Tests cover
the migrated label and accelerator preservation. The remaining TUI labels are
deferred long-tail work; the pattern is in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fill zh-CN, zh-TW, fr, and es catalogs for the migrated keys. zh-CN and zh-TW are independently localized (Simplified vs Traditional script + Taiwan vocabulary, e.g. 项目/任务 vs 專案/工作), not script-converted. Translations are machine-drafted and flagged for human review in locales/TRANSLATION_STATUS.md. Add docs/i18n-contributing.md (translate / add-a-language / --lang usage) and cross-link it from contributing.md and cli-reference.md. i18n:status reports 100% for all four locales across the migrated key set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add /* global */ directives to the i18n node scripts (matching the existing prepare-publish-manifest.mjs convention) and flip the plan status to completed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Validate GlobalSettings.language at the write boundary (store.ts) via validateLocale, so invalid locales are dropped not persisted (api-contract P1). - Fix detectEnvLocale: Traditional-script env tags (zh_Hant/zh_Hant_TW/zh_HK/ zh_MO) now resolve to zh-TW instead of Simplified; use replaceAll for multi-underscore POSIX tags (adversarial P2). Add coverage. - Agent-native parity: add 'language' to the CLI settings allowlist (VALID_SETTINGS + GLOBAL_ONLY + enum) so 'fn settings set language' works like the dashboard switcher. - Document --lang in the bin.ts help table (api-contract P3). - Derive the expected locale-chunk count from the locales dir instead of a hardcoded 5 (maintainability), and add prebuild:client so a raw vite build on a fresh clone still syncs catalogs (adversarial P2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Extract a shared normalizeToSupportedLocale helper in @fusion/i18n used by both the CLI env detection and the dashboard navigator detection (convertDetectedLanguage), fixing multi-subtag navigator tags (zh-Hans-CN) and unifying Chinese script resolution. - Add a cross-tab storage listener to useLanguage so a language change in one tab propagates to others. - Dedup the namespace lists into packages/i18n/namespaces.json, consumed by config.ts and all three build scripts (no more 3-way drift risk). - Add tests: dashboard i18n/index.ts (document.lang mirror, init non-blocking), useLanguage concurrent-hydration race + cross-tab adoption, normalizeToSupportedLocale, namespace-source consistency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
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 a full i18n foundation: core locale types and persisted language setting; a shared ChangesUI Localization Foundation (i18n)
🎯 4 (Complex) | ⏱️ ~60 minutes
✨ Finishing Touches🧪 Generate unit tests (beta)
|
- settings.test.ts mocks @fusion/core; add SUPPORTED_LOCALES to the mock so the new language enum import resolves (shard 1). - LanguageSelector.css used the banned --text-secondary token; switch to the canonical --text-muted (shard 2 text-token-canonicalization guard). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
packages/i18n/src/__tests__/config.test.ts (1)
76-80: ⚡ Quick winAdd a regression case for Simplified-script tags with Traditional-region subtags.
The Simplified cases here don't exercise
zh-Hans-HK/zh-Hans-MO, which is exactly the input that exposes the script/region precedence issue flagged inconfig.ts. Adding it locks the expectedzh-CNresolution once that ordering is fixed.💚 Suggested assertion
it("resolves Simplified/other Chinese to zh-CN", () => { expect(normalizeToSupportedLocale("zh")).toBe("zh-CN"); expect(normalizeToSupportedLocale("zh-Hans-CN")).toBe("zh-CN"); expect(normalizeToSupportedLocale("zh-SG")).toBe("zh-CN"); + expect(normalizeToSupportedLocale("zh-Hans-HK")).toBe("zh-CN"); });🤖 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 `@packages/i18n/src/__tests__/config.test.ts` around lines 76 - 80, Add regression assertions to the test that ensure Simplified-script tags with Traditional-region subtags resolve to zh-CN; specifically, call normalizeToSupportedLocale with "zh-Hans-HK" and "zh-Hans-MO" and assert the result is "zh-CN" so the test covers the script/region precedence bug in config.ts (look for normalizeToSupportedLocale in the test file).packages/cli/src/i18n/__tests__/i18n.test.tsx (1)
74-83: ⚡ Quick winShared i18next singleton leaks state across tests.
cliI18nis a module singleton: this test switches it tozh-CNand registers azh-CNbundle that persist into later tests. The subsequent migration tests callinitCliI18n("en"), butinitCliI18nissues a fire-and-forgetvoid i18next.changeLanguage("en")(index.ts Line 68) that isn't awaited, so renders there execute whilelanguageis stillzh-CN— they only pass because thedefaultValuemasks it. Reset/restore the locale in anafterEach(orawait changeLanguage) so tests don't depend on execution order.🤖 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 `@packages/cli/src/i18n/__tests__/i18n.test.tsx` around lines 74 - 83, The shared i18next singleton (cliI18n) leaks state across tests because initCliI18n calls void i18next.changeLanguage("en") without awaiting it and tests register zh-CN bundles on the singleton; fix by either awaiting the language change in initCliI18n (replace the fire-and-forget i18next.changeLanguage call with an awaited/then-handled call) or add test teardown that restores the singleton locale (call cliI18n.changeLanguage("en") and await it in an afterEach) so cliI18n and language state are deterministic between tests.packages/dashboard/app/i18n/format.ts (1)
14-24: 💤 Low valueConsider memoizing the returned formatters.
The hook returns a fresh object with four new function identities on every render. With ~45 planned call sites, consumers that place
formatDate/formatNumberetc. intouseEffect/useMemodependency arrays will re-run on each render. Wrapping the return inuseMemokeyed onlocalekeeps references stable.♻️ Proposed memoization
-import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; @@ const { i18n } = useTranslation(); const locale = i18n.resolvedLanguage || i18n.language || "en"; - return { - locale, - formatDate: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) => - new Date(value).toLocaleDateString(locale, options), - formatTime: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) => - new Date(value).toLocaleTimeString(locale, options), - formatDateTime: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) => - new Date(value).toLocaleString(locale, options), - formatNumber: (value: number, options?: Intl.NumberFormatOptions) => - value.toLocaleString(locale, options), - }; + return useMemo( + () => ({ + locale, + formatDate: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) => + new Date(value).toLocaleDateString(locale, options), + formatTime: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) => + new Date(value).toLocaleTimeString(locale, options), + formatDateTime: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) => + new Date(value).toLocaleString(locale, options), + formatNumber: (value: number, options?: Intl.NumberFormatOptions) => + value.toLocaleString(locale, options), + }), + [locale], + );🤖 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 `@packages/dashboard/app/i18n/format.ts` around lines 14 - 24, The returned object currently constructs new formatter functions on each render (formatDate, formatTime, formatDateTime, formatNumber), causing unstable references for consumers; wrap the return value in React's useMemo and memoize the object keyed on locale so the same function identities are preserved until locale changes—import/use useMemo in the module/hook and return the memoized object instead of recreating functions each render.
🤖 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 `@docs/plans/2026-06-03-001-feat-ui-localization-i18n-plan.md`:
- Around line 407-411: Duplicate bullet "Ink 6.8 → 7.0 upgrade for CJK width
(U6)" appears twice; merge them into one consolidated entry that includes both
the CJK double-width/column alignment note, the reworked input-handling
regression risk, and the React peer floor bump to 19.2, and remove the redundant
paragraph so the plan contains a single, complete Ink-upgrade risk block (search
for the exact phrase "Ink 6.8 → 7.0 upgrade for CJK width (U6)" to locate both
occurrences).
In `@packages/cli/src/bin.ts`:
- Around line 737-739: The parsed --lang value (dashLangIdx / lang) must be
validated against SUPPORTED_LOCALES before handing it to runDashboard to avoid
passing invalid locales into the TUI; update the code that computes lang to
check if the candidate is included in SUPPORTED_LOCALES and only pass it to
runDashboard when valid (otherwise set lang to undefined or a safe default and
optionally emit a warning), referencing the dashLangIdx/lang variables and the
runDashboard call so the validation sits directly before the final await
runDashboard(...).
In `@packages/cli/src/i18n/__tests__/i18n.test.tsx`:
- Around line 60-72: The test currently uses t("tui.loading", "Loading…") so the
assertion can pass via the inline defaultValue; update the test to verify a real
catalog lookup by initializing a non-en locale with initCliI18n (e.g.,
initCliI18n("fr") or another language present in the test catalogs), and either
remove the inline defaultValue in the Loading component's useTranslation call
(use t("tui.loading")) or replace the defaultValue with a sentinel string that
won't match the catalog; then assert that lastFrame() contains the actual
localized string from the catalog (referencing the Loading component,
useTranslation("cli"), initCliI18n, and I18nextProvider to locate the changes).
In `@packages/dashboard/app/components/LanguageSelector.tsx`:
- Around line 24-31: The container currently uses role="radiogroup" while each
button uses aria-pressed, mixing radio and toggle semantics; remove the
radiogroup role from the div with className "language-options" and keep the
button toggle semantics (buttons rendered in supportedLocales map, class
"language-option", click handler setLanguage, and aria-pressed on each) so
screen readers treat them as toggle buttons rather than radios.
In `@packages/dashboard/app/hooks/useLanguage.ts`:
- Around line 72-91: The hydration guard currently uses !readCachedLanguage(),
which fails when the detector auto-caches detected locale; update the logic so
the serverLocale override runs only when the user hasn't explicitly chosen a
language and there is no persistent cached language from the detector: replace
the !readCachedLanguage() check in the useEffect (the block using
fetchGlobalSettings, userSetRef, readCachedLanguage, and i18n.changeLanguage)
with a more robust test such as readCachedLanguage() === null (or a dedicated
isUserChosenLanguage() helper) so it only skips when a user-set value exists;
alternatively, disable detector auto-caching by removing "localStorage" from the
i18n detector config in packages/dashboard/app/i18n/index.ts (the caches:
["localStorage"] setting) so the original !readCachedLanguage() behavior becomes
reliable.
In `@packages/dashboard/app/i18n/index.ts`:
- Around line 41-49: The i18next detector currently writes LANGUAGE_STORAGE_KEY
during init because detection.caches includes "localStorage", which causes
useLanguage's mount guard to skip applying GlobalSettings.language; change the
detector config so detection.caches does not include "localStorage" on initial
detection (e.g., set detection.caches to an empty array or only enable caching
when a user explicitly changes language), and instead persist
LANGUAGE_STORAGE_KEY only from the explicit language-change flow (the handler
that calls i18n.changeLanguage / the code path in useLanguage that responds to
user choice). Update the detection.caches entry in the detection object and
ensure any existing logic that writes LANGUAGE_STORAGE_KEY on change still runs
so user selections continue to be cached.
In `@packages/i18n/src/config.ts`:
- Around line 60-70: The current branch that maps Chinese locales checks region
tags (-hk/-mo) before script tags, causing inputs like "zh-Hans-HK" to return
"zh-TW"; change the order so script precedence is applied first: in the block
where you inspect the lowercase locale string (variable lower), detect explicit
scripts ("hans" -> return "zh-CN", "hant" -> return "zh-TW") before checking
region substrings ("-tw","-hk","-mo"); ensure "hans" wins for hk/mo cases while
preserving the fallback behavior that returns "zh-CN" when no script/region
matches.
---
Nitpick comments:
In `@packages/cli/src/i18n/__tests__/i18n.test.tsx`:
- Around line 74-83: The shared i18next singleton (cliI18n) leaks state across
tests because initCliI18n calls void i18next.changeLanguage("en") without
awaiting it and tests register zh-CN bundles on the singleton; fix by either
awaiting the language change in initCliI18n (replace the fire-and-forget
i18next.changeLanguage call with an awaited/then-handled call) or add test
teardown that restores the singleton locale (call cliI18n.changeLanguage("en")
and await it in an afterEach) so cliI18n and language state are deterministic
between tests.
In `@packages/dashboard/app/i18n/format.ts`:
- Around line 14-24: The returned object currently constructs new formatter
functions on each render (formatDate, formatTime, formatDateTime, formatNumber),
causing unstable references for consumers; wrap the return value in React's
useMemo and memoize the object keyed on locale so the same function identities
are preserved until locale changes—import/use useMemo in the module/hook and
return the memoized object instead of recreating functions each render.
In `@packages/i18n/src/__tests__/config.test.ts`:
- Around line 76-80: Add regression assertions to the test that ensure
Simplified-script tags with Traditional-region subtags resolve to zh-CN;
specifically, call normalizeToSupportedLocale with "zh-Hans-HK" and "zh-Hans-MO"
and assert the result is "zh-CN" so the test covers the script/region precedence
bug in config.ts (look for normalizeToSupportedLocale in the test file).
🪄 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 Plus
Run ID: 84fbd4a8-5f79-4dc0-9789-327f5fa1632a
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (71)
.changeset/i18n-localization-foundation.md.gitignoredocs/cli-reference.mddocs/contributing.mddocs/i18n-contributing.mddocs/plans/2026-06-03-001-feat-ui-localization-i18n-plan.mdi18next.config.tspackage.jsonpackages/cli/package.jsonpackages/cli/scripts/prepare-publish-manifest.mjspackages/cli/src/bin.tspackages/cli/src/commands/dashboard-tui/app.tsxpackages/cli/src/commands/dashboard-tui/controller.tspackages/cli/src/commands/dashboard.tspackages/cli/src/commands/settings.tspackages/cli/src/i18n/__tests__/i18n.test.tsxpackages/cli/src/i18n/index.tspackages/core/src/__tests__/locale-settings.test.tspackages/core/src/index.tspackages/core/src/settings-schema.tspackages/core/src/settings-validation.tspackages/core/src/store.tspackages/core/src/types.tspackages/dashboard/app/App.tsxpackages/dashboard/app/components/LanguageSelector.csspackages/dashboard/app/components/LanguageSelector.tsxpackages/dashboard/app/components/SettingsModal.tsxpackages/dashboard/app/components/__tests__/LanguageSelector.test.tsxpackages/dashboard/app/hooks/__tests__/useLanguage.test.tspackages/dashboard/app/hooks/useLanguage.tspackages/dashboard/app/i18n/__tests__/format.test.tspackages/dashboard/app/i18n/__tests__/index.test.tspackages/dashboard/app/i18n/__tests__/labels.test.tspackages/dashboard/app/i18n/format.tspackages/dashboard/app/i18n/index.tspackages/dashboard/app/i18n/labels.tspackages/dashboard/app/main.tsxpackages/dashboard/package.jsonpackages/dashboard/scripts/assert-locale-chunks.mjspackages/dashboard/scripts/sync-locales.mjspackages/dashboard/vite.config.tspackages/i18n/locales/TRANSLATION_STATUS.mdpackages/i18n/locales/en/app.jsonpackages/i18n/locales/en/cli.jsonpackages/i18n/locales/en/common.jsonpackages/i18n/locales/en/errors.jsonpackages/i18n/locales/es/app.jsonpackages/i18n/locales/es/cli.jsonpackages/i18n/locales/es/common.jsonpackages/i18n/locales/es/errors.jsonpackages/i18n/locales/fr/app.jsonpackages/i18n/locales/fr/cli.jsonpackages/i18n/locales/fr/common.jsonpackages/i18n/locales/fr/errors.jsonpackages/i18n/locales/zh-CN/app.jsonpackages/i18n/locales/zh-CN/cli.jsonpackages/i18n/locales/zh-CN/common.jsonpackages/i18n/locales/zh-CN/errors.jsonpackages/i18n/locales/zh-TW/app.jsonpackages/i18n/locales/zh-TW/cli.jsonpackages/i18n/locales/zh-TW/common.jsonpackages/i18n/locales/zh-TW/errors.jsonpackages/i18n/namespaces.jsonpackages/i18n/package.jsonpackages/i18n/scripts/gen-cli-catalogs.mjspackages/i18n/src/__tests__/config.test.tspackages/i18n/src/cli-catalogs.tspackages/i18n/src/config.tspackages/i18n/src/index.tspackages/i18n/tsconfig.jsonpackages/i18n/vitest.config.ts
Greptile SummaryThis PR adds a comprehensive i18n foundation across both the React dashboard and Ink terminal UI, shipping
Confidence Score: 4/5Safe to merge with one fix: the server hydration path in useLanguage needs to set hasExplicitChoice=true to avoid silently clearing a cross-device language preference. The i18n foundation is well-engineered — shared config, correct zh-Hant/Hans normalization, validated write boundary in the store, and a properly gated first paint. The one concrete defect is in useLanguage's hydration path: when it successfully applies a server-stored locale (e.g., from another device), it never marks the choice as explicit. This leaves the 'Auto' button visually selected while the UI is already showing the server's language; a user clicking the Auto button to confirm or explore will silently issue updateGlobalSettings({ language: null }), permanently erasing a preference they set on a different machine. packages/dashboard/app/hooks/useLanguage.ts — the fetchGlobalSettings hydration effect (around line 106) needs setHasExplicitChoice(true) added when changeLanguage is called. Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser
participant main.tsx
participant i18n/index.ts
participant useLanguage
participant LanguageSelector
participant localStorage
participant Server as GlobalSettings API
Browser->>main.tsx: load
main.tsx->>i18n/index.ts: await i18nReady (gate first paint)
i18n/index.ts->>Browser: LanguageDetector (localStorage → navigator → htmlTag)
i18n/index.ts-->>main.tsx: resolved locale, catalogs loaded
main.tsx->>Browser: render App
Note over useLanguage: mount
useLanguage->>localStorage: readCachedLanguage()
localStorage-->>useLanguage: "Locale | undefined (sets hasExplicitChoice)"
useLanguage->>Server: fetchGlobalSettings()
Server-->>useLanguage: "{ language: "fr" }"
alt no local choice AND server differs from current
useLanguage->>i18n/index.ts: changeLanguage("fr")
Note over useLanguage: ⚠ hasExplicitChoice stays false
end
Note over LanguageSelector: user clicks explicit locale
LanguageSelector->>useLanguage: setLanguage("zh-TW")
useLanguage->>i18n/index.ts: changeLanguage("zh-TW")
useLanguage->>localStorage: setItem(LANGUAGE_STORAGE_KEY, "zh-TW")
useLanguage->>Server: "updateGlobalSettings({ language: "zh-TW" })"
Note over LanguageSelector: user clicks Auto
LanguageSelector->>useLanguage: clearLanguage()
useLanguage->>localStorage: removeItem(LANGUAGE_STORAGE_KEY)
useLanguage->>Server: "updateGlobalSettings({ language: null })"
useLanguage->>Browser: detectBrowserLocale() → navigator.languages
useLanguage->>i18n/index.ts: changeLanguage(detected)
Note over Browser: StorageEvent (other tab)
Browser->>useLanguage: storage event (LANGUAGE_STORAGE_KEY)
useLanguage->>i18n/index.ts: changeLanguage(event.newValue)
Reviews (7): Last reviewed commit: "feat(i18n): add Korean locale + localize..." | Re-trigger Greptile |
…anguage entry - docs/solutions/architecture-patterns/: knowledge doc covering the Vite code-split catalog constraint, zh-CN/zh-TW routing, three-tier language persistence, CLI inline-resources init, and the CI failure modes hit - CONCEPTS.md: seed shared domain vocabulary (Surface, Global Settings, Three-Tier Setting, Supported Locale) - AGENTS.md: surface docs/solutions/ and CONCEPTS.md in Reference docs - docs/settings-reference.md: add the missing GlobalSettings.language row Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@AGENTS.md`:
- Line 208: Update the AGENTS.md line describing docs/solutions frontmatter to
match the actual schema used in the example file (see
architecture-patterns/i18n-foundation-vite-ink-monorepo-code-split-catalogs.md):
replace `tags` with `category` and add the missing frontmatter fields
(`applies_when`, `severity`, `related_components`) and keep `module` (and any
other fields present in that example) so the description accurately reflects the
real YAML frontmatter structure.
🪄 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 Plus
Run ID: c9dd7ce9-a348-4973-92d5-4b029b0b184d
📒 Files selected for processing (4)
AGENTS.mdCONCEPTS.mddocs/settings-reference.mddocs/solutions/architecture-patterns/i18n-foundation-vite-ink-monorepo-code-split-catalogs.md
✅ Files skipped from review due to trivial changes (3)
- CONCEPTS.md
- docs/settings-reference.md
- docs/solutions/architecture-patterns/i18n-foundation-vite-ink-monorepo-code-split-catalogs.md
- config: explicit Hans script wins over HK/MO region (zh-Hans-HK -> zh-CN), with regression tests for script-vs-region precedence - dashboard i18n: detection.caches [] so the detector's init-time auto-persist can't masquerade as a user choice and suppress server-settings hydration - LanguageSelector: role=group (radiogroup conflicted with aria-pressed) - bin: validate --lang against SUPPORTED_LOCALES (fail loudly, not silent fallback); help text clarifies the flag is terminal-UI-only - cli i18n test: assert a real fr catalog lookup (defaultValue could mask a catalog that never loaded); comment the async changeLanguage re-init seam - assert-locale-chunks: only dirs containing common.json count as locales - plan doc: merge duplicate Ink 6.8->7.0 risk bullets - AGENTS.md/solution doc: frontmatter field list + normalizer excerpt synced Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed review feedback in 75213bb: Fixed
Deferred
|
Resolves the remaining review threads: - Reset to auto-detect across all three layers (Greptile): store passes language:null through as null-as-delete; dashboard gains an Auto option (clearLanguage + hasExplicitChoice in useLanguage, re-detects from navigator, syncs cross-tab); CLI accepts 'fn settings set language auto'. Catalog keys added for all five locales; tests at every layer. - CLI i18n test singleton: afterEach locale restore so zh-CN/fr switches can't leak across cases (CodeRabbit nitpick) - useLocaleFormat memoized per locale for stable formatter identities (CodeRabbit nitpick) - settings-reference.md documents the auto reset path Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/src/commands/settings.ts (1)
13-35:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winExpose the new
languagesetting infn settings.
languageis now writable, butrunSettingsShow()never prints it because none of the display groups include that key. That leaves users unable to confirm whether they are pinned to a locale or back on auto-detect afterfn settings set language auto.🤖 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 `@packages/cli/src/commands/settings.ts` around lines 13 - 35, runSettingsShow() never shows the new "language" setting because while "language" is listed in VALID_SETTINGS and GLOBAL_ONLY_SETTINGS, it wasn't added to the display group used by runSettingsShow(); update the display groups used by runSettingsShow (the group that lists global/user-facing settings) to include "language" so that runSettingsShow prints current value after changes, referencing VALID_SETTINGS, GLOBAL_ONLY_SETTINGS and the runSettingsShow function to locate where to add the key.
🤖 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 `@packages/dashboard/app/hooks/useLanguage.ts`:
- Around line 56-58: The hook currently fails to mark hasExplicitChoice true
when a language comes from server-hydration rather than localStorage; update the
logic in useLanguage.ts (the state and effects around hasExplicitChoice,
readCachedLanguage, fetchGlobalSettings, serverLocale, i18n.resolvedLanguage and
changeLanguage) so that when fetchGlobalSettings() returns a saved language or
when serverLocale === i18n.resolvedLanguage and that language is a persisted
user preference, you call setHasExplicitChoice(true); ensure both the initial
state thunk and the effects that handle serverLocale/changeLanguage also
setHasExplicitChoice(true) whenever the active locale is derived from a
persisted user preference rather than purely “auto.”
---
Outside diff comments:
In `@packages/cli/src/commands/settings.ts`:
- Around line 13-35: runSettingsShow() never shows the new "language" setting
because while "language" is listed in VALID_SETTINGS and GLOBAL_ONLY_SETTINGS,
it wasn't added to the display group used by runSettingsShow(); update the
display groups used by runSettingsShow (the group that lists global/user-facing
settings) to include "language" so that runSettingsShow prints current value
after changes, referencing VALID_SETTINGS, GLOBAL_ONLY_SETTINGS and the
runSettingsShow function to locate where to add the key.
🪄 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 Plus
Run ID: 4e8bd7ec-0b3f-419b-b778-9e9c006557d5
📒 Files selected for processing (16)
docs/settings-reference.mdpackages/cli/src/commands/__tests__/settings.test.tspackages/cli/src/commands/settings.tspackages/cli/src/i18n/__tests__/i18n.test.tsxpackages/core/src/__tests__/settings-precedence.test.tspackages/core/src/store.tspackages/dashboard/app/components/LanguageSelector.tsxpackages/dashboard/app/components/__tests__/LanguageSelector.test.tsxpackages/dashboard/app/hooks/__tests__/useLanguage.test.tspackages/dashboard/app/hooks/useLanguage.tspackages/dashboard/app/i18n/format.tspackages/i18n/locales/en/app.jsonpackages/i18n/locales/es/app.jsonpackages/i18n/locales/fr/app.jsonpackages/i18n/locales/zh-CN/app.jsonpackages/i18n/locales/zh-TW/app.json
✅ Files skipped from review due to trivial changes (3)
- packages/i18n/locales/en/app.json
- packages/i18n/locales/zh-TW/app.json
- docs/settings-reference.md
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/i18n/locales/fr/app.json
- packages/cli/src/commands/tests/settings.test.ts
- packages/i18n/locales/zh-CN/app.json
- packages/i18n/locales/es/app.json
- packages/dashboard/app/i18n/format.ts
- packages/core/src/store.ts
- packages/cli/src/i18n/tests/i18n.test.tsx
…validation Root cause of the recurring ChatView skill-menu CI flakes (and a real UX bug): the highlight-reset effect keyed on filteredSkills array identity, but useDiscoveredSkillsCache (SWR) re-delivers content-identical lists with fresh identities — cache reads re-parse JSON and revalidation notifies a new array. A revalidation landing between a user's (or the test's) arrow-key press and the next frame wiped the highlight back to 0. Key the reset on the joined skill-id list instead, so only a semantic list change resets the keyboard position. Regression test proves the invariant: deferred revalidation with identical content lands mid- navigation and the highlight persists (fails on the old identity-keyed reset in all three vitest projects). This test family needed three prior stabilization passes (FN-5864, FN-5745, FN-5725) — this addresses the underlying race rather than the assertions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on CONCEPTS.md vocabularies and docs/solutions reference line
…B + document the learning The ce-compound simplicity review of the new solution doc surfaced that QuickChatFAB.tsx carried the same identity-keyed highlight-reset effect fixed in ChatView (87d044f) — per FN-5893, the invariant now holds on both surfaces. - QuickChatFAB: reset keyed on joined skill ids, not array identity - docs/solutions/ui-bugs/skill-autocomplete-highlight-reset-on-swr- revalidation.md: full root-cause learning (symptoms, three failed stabilization passes, fix, reusable identity-churn regression-test pattern, working grep heuristic) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… and mission-wedge fixes; union CONCEPTS.md Missions section with localization vocabulary
- ko added to SUPPORTED_LOCALES and every enumeration site (config, settings enum, help text, tests); Korean catalogs authored for all current keys; CLI bundles regenerated for 6 locales; 한국어 endonym - README translated into zh-CN, zh-TW, fr, es, ko; every README carries a language-switcher line and the localized ones note that English is canonical Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#1352) Migration (multi-agent sweep over 216 files, 60 batches): - Every user-visible dashboard + TUI string moved to t() with the exact English inline default (en rendering byte-identical) - Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now carry ~5,930 keys each across common/app/errors/cli namespaces; CLI bundles regenerated (6 locales incl. ko) Integration fixes: - 18 type errors: reserved {{count}} interpolations renamed, malformed plural call, hand-rolled t-param types replaced with TFunction<"app"> - 23 lint errors: superseded label constants/helpers removed - ExecutorStatusBar hook-order violation (keyboard-open early return moved below hooks) - TUI tests wrapped in I18nextProvider (uninitialized fallback renders literal {{placeholders}}); dashboard vitest.setup boots a minimal en i18next instance for the same reason Known WIP (next commits): ~457 residual strings across 50 batches, Korean drafts for swept keys, and a dashboard test-suite pass that is still being stabilized (~283 failures under investigation — fake-timer waitFor interaction, likely stale node_modules vs merged lockfile). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Components now call useTranslation(); without an initialized instance,
react-i18next's fallback returns inline defaults WITHOUT interpolation
(literal {{count}} in output) and t identity flips once init completes,
double-firing effects that list t in deps. Awaiting a backend-less en
init (all namespaces pre-loaded, useSuspense off) keeps t(key, default,
options) interpolating and identity-stable from the first render.
Cleared ~770 of the 1,051 dashboard test failures; the remainder were
triaged against a clean origin/main worktree baseline: 253 fail
identically there (local-env fake-timer waitFor hangs; CI passes them)
and 23 sweep-caused regressions are being fixed in the round-2 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elity restored (#1352) - 51 fix agents covered every dirty batch from the round-1 verifiers: helper-function labels (roles, statuses, relative time), constant label maps (SETTINGS_SECTIONS, PROVIDER_INFO, EVENT_TYPE_LABELS), TUI help overlay + tab labels, toasts, placeholders, aria-labels - Inline markup flattened by round 1 restored with <Trans> (DbCorruptionBanner storage-docs link, UpdateAvailableBanner code chip) - Catalogs merged: +527 en keys across 5 locales; CLI bundles + app locale tree regenerated (6 locales) - All 23 sweep-caused test regressions fixed: delta vs the clean-main baseline is now zero (remaining 4 local failures reproduce identically on origin/main; CI-green upstream) - typecheck/lint clean; TUI 82/82, core locale 9/9 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g plugin; re-applied i18n t() wrapping to main's restructured promote sections
… all 6 locales (#1352) ko now covers every key in every namespace (common/app/errors/cli), machine-drafted with placeholder/markup preservation. CLI bundles and the dashboard locale tree regenerated; production build emits per-locale chunks for all 6 locales with no main-bundle leak (assert passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…8n t() wrapping to restructured WorkflowStepManager header and TaskCard status chain
Summary
Adds a localization (i18n) foundation across both Fusion UI surfaces — the React dashboard (and the Capacitor mobile wrapper) and the Ink terminal UI — and ships four non-English locales on top of an English source-of-truth base.
@fusion/i18npackage — authored catalogs ({locale}/{namespace}.json), shared i18next config (namespace split, script-aware zh-CN/zh-TW fallback), and a generated static CLI import map.Locale,SUPPORTED_LOCALES,DEFAULT_LOCALE,isLocale,validateLocale, andGlobalSettings.language.localStorage→navigator→endetection, a Settings language switcher with three-tier persistence (in-place, no reload), and locale-aware date/number formatting.--lang → settings → env → en. A spike confirms react-i18next works under Ink's reconciler.i18next-cli(extract/sync/types/status/lint) so adding a language is translate-only; contributor guide indocs/i18n-contributing.md.zh-CN,zh-TW,fr,es, independently localized (Simplified vs Traditional + Taiwan vocabulary). Machine-drafted, flagged for human review inpackages/i18n/locales/TRANSLATION_STATUS.md.Plan:
docs/plans/2026-06-03-001-feat-ui-localization-i18n-plan.md.Testing
@fusion/core4904 tests pass (zero regression); new locale-settings tests.@fusion/i18nconfig +normalizeToSupportedLocale; dashboarduseLanguage(persistence, hydration race, cross-tab),LanguageSelector,useLocaleFormat/useColumnLabel,i18n/index(document.lang); CLI i18n (env-detection incl.zh_Hant→zh-TW, precedence, Ink-reconciler spike, migration).verify:locale-chunks) confirms per-locale splitting.Code review
A multi-agent review ran on this branch. Six findings were fixed (server-side locale validation, a
zh_Hantenv mis-resolution bug, agent-nativefn settings set languageparity,--langhelp, derived chunk count,prebuild:clienthook) plus the residual test/refactor pass (shared normalizer, namespace dedup, cross-tab listener, added coverage). Two findings were dismissed as false positives (the two-variable dynamic import splits correctly per the build;@fusion/coreis a runtime value dep).Known Residuals
feat(i18n):-style scopes, not thefeat(FN-XXXX):task-ID prefix AGENTS.md prescribes; no ticket was assigned for this work. Apply anFN-ID at squash-merge if desired.i18next-clistatus/extract/types checks andverify:locale-chunksare not yet wired into.github/workflows(plan-deferred; the status gate trivially passes until more strings are migrated).Deferred (by design, per plan Scope Boundaries)
useLocaleFormat,useColumnLabel,t()pattern) are in place; remaining clusters are mechanical.Post-Deploy Monitoring & Validation
Low runtime risk — additive UI feature, English default unchanged when no locale is chosen.
fn dashboardstartup on non-UTF-8 / CJK terminals.fn dashboard --lang zh-TWrenders Traditional Chinese without layout breakage.verify:locale-chunks), or TUI layout corruption under CJK. Mitigation: revert the branch; no data migration is involved.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores
Tests