Files
AgentHub/docs/I18N.md
T
nntrivi2001 a6bd33bcff feat: workspaces own their sessions; desktop parity; sidebar and settings cleanup
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).
2026-10-02 13:19:50 +07:00

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

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.ts
  • client/src/lib/__tests__/format.test.ts
  • client/src/components/__tests__/Sidebar.test.tsx

Run:

npm run test:client
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
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 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