a6bd33bcff
Workspace sessions (docs/plans/desktop-parity, phases 1-5 and 7): - sessions.lane_id: a workspace lists only the sessions its panes held, stamped by bindRunSession (first writer wins, nested `claude -p` excluded); GET /api/sessions?lane=&archived= - session row state dot, double-click rename, archive (sessions.archived_at, hides only); the activity bar's duplicate Sessions tab removed - Fork beside Resume (--fork-session, Claude only), Ctrl/Cmd+click a live session opens it in a second pane, a diff line's comment is pasted into the workspace's last-focused pane (lib/panePaste.ts) dashboard_runs stability: - a pane's previous run is archived as <pane>@<started_at> instead of being overwritten by INSERT OR REPLACE - a run is flagged abandoned only once its tmux session is gone, at boot and on the run-heal beat (panes outlive a server restart) UI: - sidebar: no footer, language or theme block; small collapse button in the brand corner; version + update check in the connection details modal, a status-bar dot while a release is out; language in Settings -> General - one Switch component for every switch, the themed Checkbox for every checkbox (no native checkboxes) Also: AI Tools catalog trimmed to 10 built-ins (custom-file prompt modes tested through fixtures), and the workspace plans under docs/plans/ (the multi-user server plan lives in the AgentHub-Server repo).
300 lines
8.8 KiB
Markdown
300 lines
8.8 KiB
Markdown
# 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.
|
|
|
|
```mermaid
|
|
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-VN` resolves to `vi`)
|
|
- Detection order: `localStorage` → `navigator`
|
|
|
|
---
|
|
|
|
## 2) Resource and Namespace Strategy
|
|
|
|
Translation resources are stored per language and namespace:
|
|
|
|
- `client/src/i18n/locales/en/*.json`
|
|
- `client/src/i18n/locales/vi/*.json`
|
|
|
|
Active namespaces:
|
|
- `common`
|
|
- `nav`
|
|
- `dashboard`
|
|
- `sessions`
|
|
- `activity`
|
|
- `analytics`
|
|
- `workflows`
|
|
- `settings`
|
|
- `kanban`
|
|
- `errors`
|
|
|
|
```mermaid
|
|
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 `en` and `vi` files for the same namespace.
|
|
- Keep fallback behavior deterministic by ensuring `en` is always complete.
|
|
|
|
---
|
|
|
|
## 3) Key Naming Conventions
|
|
|
|
Use stable semantic keys, not English sentence literals.
|
|
|
|
### Convention rules
|
|
1. Use namespace-scoped keys: `namespace:key`
|
|
2. Use lower camelCase key segments
|
|
3. Keep terminology consistent across locales (for example, keep `Agent` / `Subagent` terms stable where required)
|
|
4. Use suffixes for plurals when needed (e.g. `_plural`)
|
|
5. Group nested concepts by domain (e.g. `time.justNow`, `time.mAgo`)
|
|
|
|
### Examples
|
|
- `nav:dashboard`
|
|
- `nav:languageNames.vi`
|
|
- `common:time.justNow`
|
|
- `common:time.mAgo`
|
|
- `kanban:agentCount`
|
|
- `kanban:agentCount_plural`
|
|
|
|
```mermaid
|
|
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 SettingsLanguagePicker {
|
|
+changeLanguage()
|
|
}
|
|
|
|
class FormatUtils {
|
|
+getCurrentLocale()
|
|
+formatTime()
|
|
+formatDateTime()
|
|
+fmtCostFull()
|
|
+formatModelName()
|
|
}
|
|
|
|
I18nConfig --> NamespaceResource
|
|
ReactComponent --> I18nConfig
|
|
ReactComponent --> NamespaceResource
|
|
SettingsLanguagePicker --> I18nConfig
|
|
FormatUtils --> I18nConfig
|
|
```
|
|
|
|
---
|
|
|
|
## 4) Language Detection and Switching Flow
|
|
|
|
The language picker in Settings → General (`client/src/pages/Settings.tsx`, a `SettingsSegmented` of English / Tiếng Việt, found by the settings search too) calls `i18n.changeLanguage()` and UI updates reactively through `useTranslation`. The sidebar has no language control.
|
|
|
|
```mermaid
|
|
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
|
|
```
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```mermaid
|
|
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.ts`
|
|
- `client/src/lib/__tests__/format.test.ts`
|
|
- `client/src/components/__tests__/Sidebar.test.tsx`
|
|
|
|
Run:
|
|
|
|
```bash
|
|
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 | Settings → General language picker |
|
|
|
|
---
|
|
|
|
## 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|vi` and key exists in target namespace |
|
|
| 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
|
|
|
|
```mermaid
|
|
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 `en` and `vi`
|
|
- [ ] Confirm key parity across all locale JSON files
|
|
- [ ] Confirm language switching from Settings → General re-renders the whole app
|
|
- [ ] 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.ts`
|
|
- `client/src/components/Sidebar.tsx`
|
|
- `client/src/lib/format.ts`
|
|
- `client/src/i18n/__tests__/i18n.test.ts`
|
|
- `client/src/lib/__tests__/format.test.ts`
|