Skip to content

feat: Add UI localization (i18n) across dashboard and terminal UI#1352

Merged
gsxdsm merged 29 commits into
mainfrom
gsxdsm/localization
Jun 4, 2026
Merged

feat: Add UI localization (i18n) across dashboard and terminal UI#1352
gsxdsm merged 29 commits into
mainfrom
gsxdsm/localization

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

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/i18n package — authored catalogs ({locale}/{namespace}.json), shared i18next config (namespace split, script-aware zh-CN/zh-TW fallback), and a generated static CLI import map.
  • CoreLocale, SUPPORTED_LOCALES, DEFAULT_LOCALE, isLocale, validateLocale, and GlobalSettings.language.
  • Dashboard — react-i18next runtime with per-locale Vite code-splitting (only the active locale loads on first paint; verified 15 chunks, no main-bundle leak), localStorage→navigator→en detection, a Settings language switcher with three-tier persistence (in-place, no reload), and locale-aware date/number formatting.
  • Terminal UI — synchronous Node-side i18next instance on Ink 7 (native CJK double-width), locale precedence --lang → settings → env → en. A spike confirms react-i18next works under Ink's reconciler.
  • Workflowi18next-cli (extract/sync/types/status/lint) so adding a language is translate-only; contributor guide in docs/i18n-contributing.md.
  • Translationszh-CN, zh-TW, fr, es, independently localized (Simplified vs Traditional + Taiwan vocabulary). Machine-drafted, flagged for human review in packages/i18n/locales/TRANSLATION_STATUS.md.

Plan: docs/plans/2026-06-03-001-feat-ui-localization-i18n-plan.md.

Testing

  • @fusion/core 4904 tests pass (zero regression); new locale-settings tests.
  • New tests: @fusion/i18n config + normalizeToSupportedLocale; dashboard useLanguage (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).
  • Typecheck + lint clean on all changed packages. Dashboard build assertion (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_Hant env mis-resolution bug, agent-native fn settings set language parity, --lang help, derived chunk count, prebuild:client hook) 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/core is a runtime value dep).

Known Residuals

  • Commit task-ID convention — commits use feat(i18n):-style scopes, not the feat(FN-XXXX): task-ID prefix AGENTS.md prescribes; no ticket was assigned for this work. Apply an FN- ID at squash-merge if desired.
  • CI gate wiring — the i18next-cli status/extract/types checks and verify:locale-chunks are 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)

  • Long-tail string migration across the remaining dashboard views and TUI labels — the reusable primitives (useLocaleFormat, useColumnLabel, t() pattern) are in place; remaining clusters are mechanical.
  • Human review of the machine-drafted translations.

Post-Deploy Monitoring & Validation

Low runtime risk — additive UI feature, English default unchanged when no locale is chosen.

  • Watch: dashboard load errors / blank-screen reports after deploy (the first-paint i18n gate); fn dashboard startup on non-UTF-8 / CJK terminals.
  • Healthy signals: dashboard renders English by default; switching language in Settings updates copy in place and persists across reload; fn dashboard --lang zh-TW renders Traditional Chinese without layout breakage.
  • Failure signals / rollback trigger: raw translation keys visible in UI (catalog-sync/codesplit regression — check verify:locale-chunks), or TUI layout corruption under CJK. Mitigation: revert the branch; no data migration is involved.
  • Window/owner: first 24h post-deploy, feature author.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • UI localization across dashboard and terminal (en, zh‑CN, zh‑TW, fr, es).
    • Dashboard language selector with “Auto” option; CLI now supports --lang and respects precedence (flag → saved → env).
    • Locale-aware date/number formatting and translated TUI strings to avoid flashing keys.
  • Documentation

    • CLI --lang docs, contributor i18n guide, and rollout/architecture plan.
  • Chores

    • New i18n workspace, generation/sync tooling, build checks, and initial translation catalogs.
  • Tests

    • Coverage for locale resolution, hooks, formatting, and TUI rendering.

gsxdsm and others added 12 commits June 3, 2026 07:51
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>
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a full i18n foundation: core locale types and persisted language setting; a shared @fusion/i18n package with config and generated CLI catalogs; dashboard and CLI runtime wiring (lazy browser imports, synchronous CLI catalogs); LanguageSelector UI and settings integration; i18next-cli tooling; locale catalogs for en/zh-CN/zh-TW/fr/es; and tests/docs.

Changes

UI Localization Foundation (i18n)

Layer / File(s) Summary
Core locale contracts and global settings
packages/core/src/types.ts, packages/core/src/settings-schema.ts, packages/core/src/settings-validation.ts, packages/core/src/store.ts, packages/core/src/index.ts, packages/core/src/__tests__/locale-settings.test.ts
Introduces SUPPORTED_LOCALES, Locale type, DEFAULT_LOCALE, and isLocale type-guard; adds validateLocale validator; extends GlobalSettings with optional language field; validates language on settings write.
@fusion/i18n shared configuration package
packages/i18n/package.json, packages/i18n/src/config.ts, packages/i18n/src/index.ts, packages/i18n/namespaces.json, packages/i18n/tsconfig.json, packages/i18n/vitest.config.ts, packages/i18n/src/__tests__/config.test.ts
Defines namespace typings and constants (NAMESPACES, DASHBOARD_NAMESPACES, CLI_NAMESPACES), FALLBACK_LNG for zh-CN/zh-TW distinction, normalizeToSupportedLocale() with script/region heuristics, and baseInitOptions() shared i18next config.
i18n tooling, workspace scripts, and dependencies
i18next.config.ts, root package.json, packages/cli/package.json, packages/cli/scripts/prepare-publish-manifest.mjs, packages/dashboard/package.json, packages/dashboard/vite.config.ts
Adds i18next-cli config for monorepo extract/sync/types/lint/status, root npm scripts (i18n:extract, i18n:sync, i18n:gen-cli), CLI and dashboard package dependency updates, prebuild locale sync hooks, and Vite vendor-i18n chunking.
Dashboard i18n bootstrap and provider
packages/dashboard/app/i18n/index.ts, packages/dashboard/app/App.tsx, packages/dashboard/app/main.tsx, packages/dashboard/app/i18n/__tests__/index.test.ts
Initializes i18next with LanguageDetector (localStorage → navigator → htmlTag), lazy per-locale/per-namespace resource loading via dynamic imports, updates document.documentElement.lang on language change, and gates initial React render to i18nReady.
Dashboard language persistence and sync
packages/dashboard/app/hooks/useLanguage.ts, packages/dashboard/app/hooks/__tests__/useLanguage.test.ts
Implements three-tier language persistence (localStorage → server GlobalSettings.language), guarded hydration, explicit-choice tracking, cross-tab sync via storage events, and resilience to unavailable localStorage.
Dashboard language selector UI and settings
packages/dashboard/app/components/LanguageSelector.tsx, packages/dashboard/app/components/LanguageSelector.css, packages/dashboard/app/components/SettingsModal.tsx, packages/dashboard/app/components/__tests__/LanguageSelector.test.tsx
Adds LanguageSelector component with endonym labels, Auto toggle, aria states and styling; integrates into SettingsModal Appearance section with translated heading.
CLI locale detection and resolution
packages/cli/src/i18n/index.ts (detectEnvLocale, resolveCliLocale), packages/cli/src/i18n/__tests__/i18n.test.tsx
Parses POSIX env vars (LC_ALL/LC_MESSAGES/LANG/LANGUAGE) with normalization; resolves active locale via precedence: --lang flag → persisted setting → env → DEFAULT_LOCALE.
CLI i18n initialization and Ink integration
packages/cli/src/i18n/index.ts (initCliI18n), packages/cli/src/commands/dashboard.ts, packages/cli/src/commands/dashboard-tui/controller.ts, packages/cli/src/bin.ts
Synchronous CLI i18n init with inline generated catalogs and useSuspense: false; exposes --lang flag; wires DashboardTUI.lang and wraps Ink app with I18nextProvider.
CLI string migration to i18n
packages/cli/src/commands/dashboard-tui/app.tsx
Migrates Board view strings (loading, no-tasks, project-switch hint) to useTranslation("cli") with t() calls and defaultValue fallbacks; preserves literal keybinding accelerators via interpolation.
Locale-aware formatting helpers
packages/dashboard/app/i18n/format.ts, packages/dashboard/app/i18n/labels.ts, packages/dashboard/app/i18n/__tests__/format.test.ts, packages/dashboard/app/i18n/__tests__/labels.test.ts
Adds useLocaleFormat() hook for locale-aware date/time/number formatting using Intl APIs; adds useColumnLabel() hook for column translations with fallback to core constants.
Complete locale catalogs for 5 languages
packages/i18n/locales/{en,zh-CN,zh-TW,fr,es}/{app,common,cli,errors}.json, packages/i18n/locales/TRANSLATION_STATUS.md, packages/i18n/scripts/gen-cli-catalogs.mjs, packages/i18n/src/cli-catalogs.ts
JSON catalogs across common, app, cli, errors; CLI catalogs statically generated and bundled; TRANSLATION_STATUS marks non-English catalogs as machine-drafted.
Comprehensive i18n test coverage
packages/core/src/__tests__/locale-settings.test.ts, packages/cli/src/i18n/__tests__/i18n.test.tsx, packages/dashboard/app/hooks/__tests__/useLanguage.test.ts, packages/dashboard/app/i18n/__tests__/*.test.ts, packages/i18n/src/__tests__/config.test.ts
Tests locale validation, CLI env detection and precedence, dashboard hydration and cross-tab sync, Ink rendering with i18n, locale-aware formatting, and i18n config relationships.
i18n contributor guides and documentation
docs/i18n-contributing.md, docs/contributing.md, docs/cli-reference.md, .changeset/i18n-localization-foundation.md, docs/plans/2026-06-03-001-feat-ui-localization-i18n-plan.md, .gitignore
Contributor guide, CLI reference --lang docs, architecture plan and solutions page, settings-reference update for language, and a changeset for a minor release.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A dozen tongues now speak through Fusion's heart,
From en to zh, each locale plays its part,
Persisted, synced, and formatted just right,
The UI shines in every language's light! ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gsxdsm/localization

- 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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
packages/i18n/src/__tests__/config.test.ts (1)

76-80: ⚡ Quick win

Add 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 in config.ts. Adding it locks the expected zh-CN resolution 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 win

Shared i18next singleton leaks state across tests.

cliI18n is a module singleton: this test switches it to zh-CN and registers a zh-CN bundle that persist into later tests. The subsequent migration tests call initCliI18n("en"), but initCliI18n issues a fire-and-forget void i18next.changeLanguage("en") (index.ts Line 68) that isn't awaited, so renders there execute while language is still zh-CN — they only pass because the defaultValue masks it. Reset/restore the locale in an afterEach (or await 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 value

Consider 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/formatNumber etc. into useEffect/useMemo dependency arrays will re-run on each render. Wrapping the return in useMemo keyed on locale keeps 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

📥 Commits

Reviewing files that changed from the base of the PR and between 04a5cd1 and 4f6013f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (71)
  • .changeset/i18n-localization-foundation.md
  • .gitignore
  • docs/cli-reference.md
  • docs/contributing.md
  • docs/i18n-contributing.md
  • docs/plans/2026-06-03-001-feat-ui-localization-i18n-plan.md
  • i18next.config.ts
  • package.json
  • packages/cli/package.json
  • packages/cli/scripts/prepare-publish-manifest.mjs
  • packages/cli/src/bin.ts
  • packages/cli/src/commands/dashboard-tui/app.tsx
  • packages/cli/src/commands/dashboard-tui/controller.ts
  • packages/cli/src/commands/dashboard.ts
  • packages/cli/src/commands/settings.ts
  • packages/cli/src/i18n/__tests__/i18n.test.tsx
  • packages/cli/src/i18n/index.ts
  • packages/core/src/__tests__/locale-settings.test.ts
  • packages/core/src/index.ts
  • packages/core/src/settings-schema.ts
  • packages/core/src/settings-validation.ts
  • packages/core/src/store.ts
  • packages/core/src/types.ts
  • packages/dashboard/app/App.tsx
  • packages/dashboard/app/components/LanguageSelector.css
  • packages/dashboard/app/components/LanguageSelector.tsx
  • packages/dashboard/app/components/SettingsModal.tsx
  • packages/dashboard/app/components/__tests__/LanguageSelector.test.tsx
  • packages/dashboard/app/hooks/__tests__/useLanguage.test.ts
  • packages/dashboard/app/hooks/useLanguage.ts
  • packages/dashboard/app/i18n/__tests__/format.test.ts
  • packages/dashboard/app/i18n/__tests__/index.test.ts
  • packages/dashboard/app/i18n/__tests__/labels.test.ts
  • packages/dashboard/app/i18n/format.ts
  • packages/dashboard/app/i18n/index.ts
  • packages/dashboard/app/i18n/labels.ts
  • packages/dashboard/app/main.tsx
  • packages/dashboard/package.json
  • packages/dashboard/scripts/assert-locale-chunks.mjs
  • packages/dashboard/scripts/sync-locales.mjs
  • packages/dashboard/vite.config.ts
  • packages/i18n/locales/TRANSLATION_STATUS.md
  • packages/i18n/locales/en/app.json
  • packages/i18n/locales/en/cli.json
  • packages/i18n/locales/en/common.json
  • packages/i18n/locales/en/errors.json
  • packages/i18n/locales/es/app.json
  • packages/i18n/locales/es/cli.json
  • packages/i18n/locales/es/common.json
  • packages/i18n/locales/es/errors.json
  • packages/i18n/locales/fr/app.json
  • packages/i18n/locales/fr/cli.json
  • packages/i18n/locales/fr/common.json
  • packages/i18n/locales/fr/errors.json
  • packages/i18n/locales/zh-CN/app.json
  • packages/i18n/locales/zh-CN/cli.json
  • packages/i18n/locales/zh-CN/common.json
  • packages/i18n/locales/zh-CN/errors.json
  • packages/i18n/locales/zh-TW/app.json
  • packages/i18n/locales/zh-TW/cli.json
  • packages/i18n/locales/zh-TW/common.json
  • packages/i18n/locales/zh-TW/errors.json
  • packages/i18n/namespaces.json
  • packages/i18n/package.json
  • packages/i18n/scripts/gen-cli-catalogs.mjs
  • packages/i18n/src/__tests__/config.test.ts
  • packages/i18n/src/cli-catalogs.ts
  • packages/i18n/src/config.ts
  • packages/i18n/src/index.ts
  • packages/i18n/tsconfig.json
  • packages/i18n/vitest.config.ts

Comment thread docs/plans/2026-06-03-001-feat-ui-localization-i18n-plan.md Outdated
Comment thread packages/cli/src/bin.ts
Comment thread packages/cli/src/i18n/__tests__/i18n.test.tsx
Comment thread packages/dashboard/app/components/LanguageSelector.tsx Outdated
Comment thread packages/dashboard/app/hooks/useLanguage.ts
Comment thread packages/dashboard/app/i18n/index.ts
Comment thread packages/i18n/src/config.ts
@greptile-apps

greptile-apps Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a comprehensive i18n foundation across both the React dashboard and Ink terminal UI, shipping en, zh-CN, zh-TW, fr, es, and ko locales with a shared @fusion/i18n package, per-locale Vite code-splitting, a language switcher with Auto/explicit three-tier persistence, and a synchronous CLI i18next instance for flash-free TUI rendering.

  • @fusion/i18n — shared normalizeToSupportedLocale (handles POSIX + BCP-47 zh-Hant/Hans edge cases), static cliResources map, and baseInitOptions consumed by both surfaces.
  • DashboarduseLanguage hook with localStorage → server → navigator precedence, cross-tab StorageEvent sync, and LanguageSelector with Auto + endonym buttons; first paint gated on i18nReady to avoid raw-key flicker.
  • CLIdetectEnvLocale + resolveCliLocale with --lang → settings → env → en precedence; fn settings set language auto wired to null-as-delete at the GlobalSettingsStore boundary.

Confidence Score: 4/5

Safe 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

Filename Overview
packages/dashboard/app/hooks/useLanguage.ts Three-tier language persistence hook; server hydration path omits setHasExplicitChoice(true), causing 'Auto' to appear selected when a cross-device preference is hydrated — clicking Auto then silently clears it.
packages/i18n/src/config.ts Shared i18next base config; normalizeToSupportedLocale correctly handles zh-Hant/zh-Hans/region tags for both browser and POSIX env detection.
packages/core/src/store.ts Language validated at the write boundary; null passes through as delete-key signal; invalid values are silently dropped preserving prior setting.
packages/cli/src/i18n/index.ts Synchronous CLI i18next init with static resources; module-level singleton pattern with async changeLanguage on re-init acknowledged in comments.
packages/dashboard/app/i18n/index.ts Dashboard i18next init with lazy per-locale chunks; detection order and caches:[] intentionally prevent the detector from writing localStorage (explicit user choice only).
packages/core/src/types.ts SUPPORTED_LOCALES, Locale, DEFAULT_LOCALE, isLocale, and GlobalSettings.language added cleanly; ko included alongside zh-CN/zh-TW/fr/es.
packages/dashboard/scripts/assert-locale-chunks.mjs Build-time regression guard for per-locale code splitting; locale count derived safely from directories with common.json, but the inline-content guard uses fragile English strings.
packages/dashboard/app/components/LanguageSelector.tsx Language switcher with Auto option; endonyms are intentionally untranslated; aria-pressed toggle semantics are correct.
packages/cli/src/commands/settings.ts language added to VALID_SETTINGS and GLOBAL_ONLY_SETTINGS; 'auto' sentinel correctly maps to null-as-delete for GlobalSettingsStore.

Sequence Diagram

sequenceDiagram
    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)
Loading

Reviews (7): Last reviewed commit: "feat(i18n): add Korean locale + localize..." | Re-trigger Greptile

Comment thread packages/dashboard/scripts/assert-locale-chunks.mjs
Comment thread packages/cli/src/bin.ts Outdated
Comment thread packages/core/src/store.ts Outdated
Comment thread packages/cli/src/i18n/index.ts
…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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between cfff270 and 0c39603.

📒 Files selected for processing (4)
  • AGENTS.md
  • CONCEPTS.md
  • docs/settings-reference.md
  • docs/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

Comment thread AGENTS.md Outdated
- 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>
@gsxdsm

gsxdsm commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review feedback in 75213bb:

Fixed

  • zh-Hans-HK/zh-Hans-MO misroute → explicit Hans script now wins over region (packages/i18n/src/config.ts), with regression tests (CodeRabbit)
  • detection.caches: [] — the detector's init-time auto-persist was writing the storage key before any user choice, which would have suppressed the server-settings hydration tier (CodeRabbit, both findings)
  • ✅ LanguageSelector role="group" — radiogroup conflicted with aria-pressed (CodeRabbit)
  • --lang now validated against SUPPORTED_LOCALES with a loud error instead of silent fallback; help text clarifies the flag is terminal-UI-only (CodeRabbit + Greptile)
  • ✅ CLI i18n test now asserts a real fr catalog lookup with no defaultValue escape hatch (CodeRabbit)
  • assert-locale-chunks only counts dirs containing common.json as locales — stray dirs can't inflate the expected count (Greptile)
  • ✅ Re-init changeLanguage async seam documented (Greptile)
  • ✅ Plan doc: duplicate Ink 6.8→7.0 risk bullets merged (CodeRabbit)
  • ✅ AGENTS.md frontmatter field list corrected (CodeRabbit — note the solution doc does have tags; added category/applies_when for accuracy)

Deferred

  • ⏳ "No path to reset language to auto-detect" (Greptile) — real gap, but adding a System-default option spans all three layers (LanguageSelector UI, fn settings enum, store clear semantics + re-detection). Tracking as a follow-up rather than expanding this PR.

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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Expose the new language setting in fn settings.

language is now writable, but runSettingsShow() 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 after fn 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

📥 Commits

Reviewing files that changed from the base of the PR and between 75213bb and e08a563.

📒 Files selected for processing (16)
  • docs/settings-reference.md
  • packages/cli/src/commands/__tests__/settings.test.ts
  • packages/cli/src/commands/settings.ts
  • packages/cli/src/i18n/__tests__/i18n.test.tsx
  • packages/core/src/__tests__/settings-precedence.test.ts
  • packages/core/src/store.ts
  • packages/dashboard/app/components/LanguageSelector.tsx
  • packages/dashboard/app/components/__tests__/LanguageSelector.test.tsx
  • packages/dashboard/app/hooks/__tests__/useLanguage.test.ts
  • packages/dashboard/app/hooks/useLanguage.ts
  • packages/dashboard/app/i18n/format.ts
  • packages/i18n/locales/en/app.json
  • packages/i18n/locales/es/app.json
  • packages/i18n/locales/fr/app.json
  • packages/i18n/locales/zh-CN/app.json
  • packages/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

Comment thread packages/dashboard/app/hooks/useLanguage.ts
gsxdsm and others added 7 commits June 3, 2026 13:16
…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>
gsxdsm and others added 6 commits June 3, 2026 20:41
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant