Internationalization (i18n) Architecture and Usage
August 10, 2026 · View on GitHub
This guide documents how localization works in the Agent Dashboard, including architecture, resources, runtime behavior, testing, and rollout.
Supported languages: English (en), Chinese (zh), Vietnamese (vi), Korean (ko), Spanish (es)
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", "zh", "vi", "ko", "es"]fallbackLng:"en"nonExplicitSupportedLngs:true(e.g.es-ESresolves toes)- 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/zh/*.jsonclient/src/i18n/locales/vi/*.jsonclient/src/i18n/locales/ko/*.jsonclient/src/i18n/locales/es/*.json
Active namespaces:
commonnavdashboardsessionsactivityanalyticsworkflowssettingskanbanerrors
erDiagram
LANGUAGE ||--o{ NAMESPACE : contains
NAMESPACE ||--o{ KEY : defines
KEY ||--o{ TRANSLATION : maps_to
LANGUAGE {
string code "en|zh|vi|ko|es"
string locale "en-US|zh-CN|vi-VN|ko-KR|es-ES"
}
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
en,zh,vi,ko, andesfiles for the same namespace. - Keep fallback behavior deterministic by ensuring
enis always complete.
Translation quality checklist
When adding user-visible copy:
- Write the English source string first, with enough nearby context for translators to understand where it appears.
- Add the same key to every supported locale in the same change; a fallback is a safety net, not a completed translation.
- Preserve interpolation tokens, Markdown fragments, code identifiers, command names, and environment-variable names exactly as written in English.
- Reuse established product terminology within each locale instead of translating the same concept differently on each page.
- Keep labels concise and test a narrow viewport after translation, especially for headings, buttons, and table actions.
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","zh","vi","ko","es"]
+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 control is the shared custom Select dropdown used by the Run Claude page. It calls i18n.changeLanguage() and the 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: Choose a language from the custom dropdown
SB->>I: changeLanguage("es")
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_zh: localStorage/navigator resolves zh
Detecting --> Loaded_vi: localStorage/navigator resolves vi
Detecting --> Loaded_ko: localStorage/navigator resolves ko
Detecting --> Loaded_es: localStorage/navigator resolves es
Detecting --> Loaded_en: unsupported locale -> fallback en
Loaded_en --> Loaded_zh: user switches to zh
Loaded_en --> Loaded_vi: user switches to vi
Loaded_zh --> Loaded_en: user switches to en
Loaded_zh --> Loaded_vi: user switches to vi
Loaded_vi --> Loaded_en: user switches to en
Loaded_vi --> Loaded_zh: user switches to zh
Loaded_en --> Loaded_ko: user switches to ko
Loaded_ko --> Loaded_en: user switches to en
Loaded_en --> Loaded_es: user switches to es
Loaded_es --> Loaded_en: user switches to en
5) Date and Number Localization Behavior
Formatting utilities are centralized in client/src/lib/format.ts.
en→en-USzh→zh-CNvi→vi-VNko→ko-KRes→es-ES
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 Settings page (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 -->|zh| F["Locale zh-CN"]
D -->|vi| G["Locale vi-VN"]
D -->|ko| H["Locale ko-KR"]
D -->|es| J["Locale es-ES"]
E --> K["toLocaleTimeString / toLocaleString"]
F --> K
G --> K
H --> K
J --> K
K --> 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/zh/vi/ko/es | Missing key detection in CI |
| Locale fallback | Unknown locales fall back to en | es-ES resolves to es |
| Terminology consistency | Canonical terms stay stable | Agent/Subagent expectations |
| Date/number formatting | Locale-specific output shape | zh-CN, vi-VN, ko-KR, es-ES formatting |
| Runtime switching | UI rerenders without reload | Sidebar custom language dropdown |
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/zh/vi/ko/es 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
en,zh,vi,ko,es - 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.,
es-ES,vi-VN,zh-CN) - 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