- AI Tools moves from a Settings section into a modal behind a button at the Settings tab bar's right edge (AiToolsModal, `actions` on PageTabs, /settings#ai-tools); rows get an enable switch, Default badge, and an Update button for every tool whose catalog names an updater (no version lookup on open); not-installed tools fold behind a disclosure, a finished job's log folds to one line; shared AiToolIcon - codex updates through its own `codex update`, openclaw/hermes gain `update --yes`; update-all runs every installed updater and skips an npm one only when npm does not manage the binary - Add-lane tool picker is one button showing the chosen tool that opens the enabled tools as buttons - Claude Code / Codex AgentHub sub-tab folded into Overview (home editor, hooks, pricing); the path list drops CLAUDE_HOME/CODEX_HOME; HomeDirSetting shows the saved value only while the field differs - Preview/Raw toggle for markdown files in the config FileViewer and the Changes panel's whole-file view (MarkdownPreview: react-markdown + remark-gfm, raw HTML never run, frontmatter as a code block) - docs and the CLAUDE.md marker updated
8.7 KiB
Internationalization (i18n) Architecture and Usage
This guide documents how localization works in the Agent Dashboard, including architecture, resources, runtime behavior, testing, and rollout.
Supported languages: English (en), Vietnamese (vi)
1) Architecture Overview
Localization is implemented in the frontend with i18next + react-i18next and browser language detection.
flowchart TB
subgraph Browser
User["User"]
LS["localStorage<br/>i18nextLng"]
Nav["navigator.language"]
end
subgraph ClientApp["React Client"]
Detector["i18next-browser-languagedetector"]
I18n["i18n init<br/>client/src/i18n/index.ts"]
NS["Namespace bundles<br/>common/nav/dashboard/..."]
UI["Pages + components<br/>useTranslation()"]
Format["format.ts<br/>locale-aware date/number/model-name"]
end
User --> UI
LS --> Detector
Nav --> Detector
Detector --> I18n
I18n --> NS
NS --> UI
I18n --> Format
Key runtime facts
supportedLngs:["en", "vi"]fallbackLng:"en"nonExplicitSupportedLngs:true(e.g.vi-VNresolves tovi)- Detection order:
localStorage→navigator
2) Resource and Namespace Strategy
Translation resources are stored per language and namespace:
client/src/i18n/locales/en/*.jsonclient/src/i18n/locales/vi/*.json
Active namespaces:
commonnavdashboardsessionsactivityanalyticsworkflowssettingskanbanerrors
erDiagram
LANGUAGE ||--o{ NAMESPACE : contains
NAMESPACE ||--o{ KEY : defines
KEY ||--o{ TRANSLATION : maps_to
LANGUAGE {
string code "en|vi"
string locale "en-US|vi-VN"
}
NAMESPACE {
string name "common|nav|dashboard|..."
string file_path "locales/{lang}/{namespace}.json"
}
KEY {
string id "dot.notation.or.leaf"
string type "string|pluralized"
}
TRANSLATION {
string value "localized text"
}
Strategy notes
- Keep namespace boundaries page/domain focused.
- Keep key parity across
enandvifiles for the same namespace. - Keep fallback behavior deterministic by ensuring
enis always complete.
3) Key Naming Conventions
Use stable semantic keys, not English sentence literals.
Convention rules
- Use namespace-scoped keys:
namespace:key - Use lower camelCase key segments
- Keep terminology consistent across locales (for example, keep
Agent/Subagentterms stable where required) - Use suffixes for plurals when needed (e.g.
_plural) - Group nested concepts by domain (e.g.
time.justNow,time.mAgo)
Examples
nav:dashboardnav:languageNames.vicommon:time.justNowcommon:time.mAgokanban:agentCountkanban:agentCount_plural
classDiagram
class I18nConfig {
+supportedLngs: ["en","vi"]
+fallbackLng: "en"
+defaultNS: "common"
+detectionOrder: ["localStorage","navigator"]
}
class NamespaceResource {
+languageCode
+namespace
+jsonFilePath
+keys[]
}
class ReactComponent {
+useTranslation(namespace)
+t(key, params)
}
class SidebarLanguageSwitch {
+SUPPORTED_LANGUAGES
+normalizeLanguage()
+changeLanguage()
}
class FormatUtils {
+getCurrentLocale()
+formatTime()
+formatDateTime()
+fmtCostFull()
+formatModelName()
}
I18nConfig --> NamespaceResource
ReactComponent --> I18nConfig
ReactComponent --> NamespaceResource
SidebarLanguageSwitch --> I18nConfig
FormatUtils --> I18nConfig
4) Language Detection and Switching Flow
The sidebar language controls call i18n.changeLanguage() and UI updates reactively through useTranslation.
sequenceDiagram
participant U as User
participant SB as Sidebar.tsx
participant I as i18next
participant LD as LanguageDetector
participant NS as Locale Resources
participant UI as React Components
U->>SB: Click language button (EN/ZH/VI/KO)
SB->>I: changeLanguage("vi")
I->>NS: Resolve namespace bundles
NS-->>I: Return translations
I->>LD: Persist i18nextLng in localStorage
I-->>UI: Trigger rerender
UI->>UI: Re-evaluate t(...) keys
UI-->>U: Localized labels displayed
stateDiagram-v2
[*] --> Detecting
Detecting --> Loaded_en: localStorage/navigator resolves en
Detecting --> Loaded_vi: localStorage/navigator resolves vi
Detecting --> Loaded_en: unsupported locale -> fallback en
Loaded_en --> Loaded_vi: user switches to vi
Loaded_vi --> Loaded_en: user switches to en
5) Date and Number Localization Behavior
Formatting utilities are centralized in client/src/lib/format.ts.
en→en-US(also the default for any other language)vi→vi-VN
formatTime, formatDateTime, and fmtCostFull use locale-aware toLocale* APIs.
Timestamp parsing normalizes timezone-less SQLite datetime strings to UTC before display formatting.
formatModelName converts raw model identifiers (e.g. claude-opus-4-7-20260101, claude-opus-4-7[1m]) into human-friendly display names (e.g. "Claude Opus 4.7", "Claude Opus 4.7 (1M)"). This is locale-independent (brand names are proper nouns) and is applied across all UI surfaces except the Model Pricing table (Settings → Claude Code → Overview, Settings → Codex → Overview and Settings → Antigravity), which shows raw patterns for pricing rule configuration.
flowchart LR
A["Raw timestamp / numeric value"] --> B["parseDate() normalization"]
B --> C["getCurrentLanguage()"]
C --> D{"Language"}
D -->|en| E["Locale en-US"]
D -->|vi| G["Locale vi-VN"]
E --> H["toLocaleTimeString / toLocaleString"]
G --> H
H --> I["Localized date/time/number output"]
6) Testing Strategy
Use client tests to verify translation correctness, fallback behavior, and locale formatting:
client/src/i18n/__tests__/i18n.test.tsclient/src/lib/__tests__/format.test.tsclient/src/components/__tests__/Sidebar.test.tsx
Run:
npm run test:client
Recommended test matrix
| Area | What to verify | Example |
|---|---|---|
| Resource parity | Same key coverage across en/vi |
Missing key detection in CI |
| Locale fallback | Unknown locales fall back to en |
vi-VN resolves to vi |
| Terminology consistency | Canonical terms stay stable | Agent/Subagent expectations |
| Date/number formatting | Locale-specific output shape | en-US, vi-VN formatting |
| Runtime switching | UI rerenders without reload | Sidebar language toggle |
7) Troubleshooting
| Symptom | Likely cause | Resolution |
|---|---|---|
| UI stays in old language after switch | Cached key or stale component state | Confirm i18n.changeLanguage(...) is called and component uses useTranslation |
| Unexpected fallback to English | Unsupported locale code | Ensure code normalizes to `en |
| Missing text on one page | Namespace file key missing | Add key to all language files for that namespace |
| Date/time looks wrong | Locale mapping or timezone parse issue | Verify getCurrentLocale() and parseDate() behavior |
| Inconsistent term translation | Manual translation drift | Enforce glossary and update locale tests |
8) Rollout Checklist
gantt
title i18n rollout plan
dateFormat YYYY-MM-DD
axisFormat %m/%d
section Resource Preparation
Lock key inventory :a1, 2026-01-01, 3d
Fill en/vi namespace files :a2, after a1, 5d
section Runtime Integration
Wire detection + persistence :b1, after a2, 2d
Validate sidebar switching :b2, after b1, 2d
Validate locale formatting :b3, after b1, 2d
section Verification
Add/refresh i18n tests :c1, after b2, 3d
Run regression suite :c2, after c1, 2d
section Release
Staged release + monitoring :d1, after c2, 2d
Post-release translation audit :d2, after d1, 3d
Operational checklist
- Confirm all namespaces exist for
enandvi - Confirm key parity across all locale JSON files
- Confirm language switching works in collapsed and expanded sidebar modes
- Confirm fallback behavior for region tags (e.g.,
vi-VN,en-US) - Confirm date/time/currency formatting for all supported languages
- Confirm client tests pass before release
- Confirm docs references are updated (
README,ARCHITECTURE,docs/README)
References
client/src/i18n/index.tsclient/src/components/Sidebar.tsxclient/src/lib/format.tsclient/src/i18n/__tests__/i18n.test.tsclient/src/lib/__tests__/format.test.ts