# Dashboard Internationalization Plan

## Objective

Make every user-facing dashboard surface render in the user's selected language, with a foundation that scales to any number of locales without reworking the architecture later.

This plan covers:

- The React dashboard in `src/Armada.Dashboard/src`
- The legacy embedded dashboard in `src/Armada.Server/wwwroot`
- Pre-authentication UI, including the login experience and language selection before sign-in
- Shared formatting behavior for dates, times, numbers, units, statuses, alerts, and notifications

This plan does **not** assume a fixed locale list. It is designed so new locales are added by dropping in translation resources and passing validation gates.

## How To Use This File

- Mark completed items with `[x]`
- Mark active work with `[-]`
- Leave future work as `[ ]`
- Add owner/date notes inline under any item when work starts
- If a decision changes, append a short note rather than deleting history

Example:

```md
- [x] Add React locale provider
  Notes: 2026-04-23, JC - Added provider and persistence in localStorage.
```

## Scope And Success Criteria

The dashboard is not done until **all** of the following are true:

- Every visible label, caption, heading, breadcrumb, button, tab, nav item, tooltip, placeholder, modal title, confirm dialog, toast, empty state, loading state, error state, and help text is localizable
- Every `title`, `aria-label`, `aria-describedby`, `alt`, and similar accessibility string is localizable
- The login page allows the user to pick a language before authentication
- The selected language persists across reloads and applies both before and after sign-in
- Date, time, number, relative-time, plural, and unit formatting are locale-aware
- Text expansion does not break layout for long German/French strings or multi-byte CJK strings
- The architecture supports adding a new locale without editing application logic
- Missing translation keys are caught in CI
- Developers have a defined workflow for extraction, review, testing, and ongoing maintenance

## Locale Model

### Required Launch Locales

- `en` - English
- `es` - Spanish
- `zh-Hans` - Mandarin Chinese, Simplified
- `zh-Hant` - Mandarin Chinese, Traditional
- `yue-Hant` - Cantonese, Traditional
- `ja` - Japanese
- `de` - German
- `fr` - French
- `it` - Italian

### Strongly Recommended First-Pass Additions

- `pt-BR` - Portuguese (Brazil)
- `ko` - Korean
- `ar` - Arabic
- `en-XA` - pseudo-locale for expansion testing
- `ar-XB` - pseudo-locale for RTL testing

### Important Note On "Kanji"

`Kanji` is a writing system, not a standalone locale. Japanese (`ja`) already uses Kanji. Before translation work begins, record the product decision for what was intended here:

- If the request means Japanese, `ja` already covers it
- If the request means script preference, handle that as a locale/script policy, not a separate language
- If the request meant another locale, add the correct BCP-47 code here

## Non-Negotiable Design Rules

- Do not hard-code user-facing English strings in components, views, or JS modules
- Do not concatenate translated fragments into sentences when interpolation or pluralization is required
- Do not store pretranslated notification text in persistence; store semantic payload and render in the active locale
- Do not treat server enum values as display strings; map raw values to localized labels
- Do not use `toLocaleString()` with implicit browser defaults scattered throughout the codebase; route all formatting through shared helpers
- Always set `document.documentElement.lang`
- Set `document.documentElement.dir` from locale metadata, even if launch locales are LTR, so RTL can be added without a second refactor

## Recommended Architecture

### Translation Engine

- React dashboard: adopt `i18next` with `react-i18next`
- Legacy dashboard: use the same locale JSON resources with a lightweight vanilla JS translation layer
- Keep translation catalogs in a shared location so React and legacy do not fork terminology

### Proposed Resource Layout

```text
src/
  Shared/
    I18n/
      locales/
        en/
          common.json
          auth.json
          layout.json
          setup-wizard.json
          dashboard.json
          fleets.json
          vessels.json
          captains.json
          missions.json
          voyages.json
          merge-queue.json
          signals.json
          events.json
          notifications.json
          server.json
          doctor.json
          admin.json
          modals.json
          errors.json
        es/
          ...
```

If a shared .NET project is a poor fit for web asset loading, keep the same namespace structure under a repo-level web-assets folder instead. The important part is shared catalogs, not the exact folder name.

### Locale Selection Policy

Resolve locale in this order:

1. Explicit user selection from the language selector
2. Persisted browser preference in local storage
3. Stored user profile preference if server-side profile support is added later
4. `navigator.languages`
5. Fallback to `en`

### Shared Runtime Requirements

- Central locale registry with label, native label, script direction, fallback locale, and font stack hints
- Shared helper for `setActiveLocale(locale)`
- Shared helpers for:
  - absolute date/time
  - relative time
  - number formatting
  - percent formatting
  - duration formatting
  - byte/count/unit formatting
  - pluralization
- Shared translation key naming conventions
- Shared missing-key logging in development

## Workstream 0: Decisions To Lock Before Coding

- [ ] Confirm the canonical locale list for phase 1
  Notes:
  - Include the requested locales above
  - Decide whether `zh-Hant` ships alongside `zh-Hans` and `yue-Hant`
  - Resolve the `Kanji` ambiguity explicitly
- [ ] Decide the shared translation asset location
- [ ] Decide whether the proxy-hosted shell in `src/Armada.Proxy/wwwroot` is included now or in a follow-on effort
- [ ] Decide whether server-originated error messages stay English in phase 1 or get code-based translation support
- [ ] Decide terminology policy for Armada-specific nouns
  Notes:
  - `Fleet`
  - `Vessel`
  - `Captain`
  - `Dock`
  - `Voyage`
  - `Mission`
  - `Signal`
  - `Merge Queue`

## Workstream 1: Exhaustive Inventory And Freeze

- [ ] Create a string inventory for the React dashboard
  Target files:
  - `src/Armada.Dashboard/src/App.tsx`
  - `src/Armada.Dashboard/src/components/**/*.tsx`
  - `src/Armada.Dashboard/src/context/**/*.tsx`
  - `src/Armada.Dashboard/src/pages/**/*.tsx`
- [ ] Create a string inventory for the legacy dashboard
  Target files:
  - `src/Armada.Server/wwwroot/index.html`
  - `src/Armada.Server/wwwroot/views/*.html`
  - `src/Armada.Server/wwwroot/js/dashboard.js`
  - `src/Armada.Server/wwwroot/js/modules/*.js`
- [ ] Classify every string into one of these buckets:
  - visible UI copy
  - accessibility copy
  - placeholder/help text
  - toast/alert/confirm/modal text
  - status/enum labels
  - relative time and units
  - validation and error text
  - telemetry/log/debug text that should remain English
- [ ] Freeze introduction of new hard-coded UI strings until the i18n foundation is merged
- [ ] Add a temporary audit document or CSV with owner and extraction status

## Workstream 2: Shared Locale Infrastructure

- [x] Add a locale registry with:
  Notes: 2026-04-23 - Shared registry landed in `src/Armada.Dashboard/src/i18n/runtime.ts` and `src/Armada.Server/wwwroot/js/i18n.js`, with locale code, labels, native labels, direction, aliases, and fallback behavior.
  - locale code
  - English display name
  - native display name
  - text direction
  - fallback locale
  - script/font notes
- [x] Add shared translation resource loading
  Notes: 2026-04-23 - Shared catalog added at `src/Armada.Server/wwwroot/i18n/armada.json` and loaded by both dashboard runtimes from `/dashboard/i18n/armada.json`.
- [ ] Add a shared missing-key strategy
- [x] Add a shared locale persistence key
  Recommendation: `armada_locale`
- [x] Add a shared utility to set `document.documentElement.lang`
- [x] Add a shared utility to set `document.documentElement.dir`
- [x] Add a fallback strategy for unsupported browser locales
- [ ] Add a pseudo-localization mode for expansion testing

## Workstream 3: React Dashboard Foundation

- [x] Add a React i18n bootstrap module
  Likely new files:
  - `src/Armada.Dashboard/src/i18n/index.ts`
  - `src/Armada.Dashboard/src/i18n/locales/...` or shared loader wrappers
- [x] Add a locale provider or integrate locale state into an existing provider tree
  Provider insertion point:
  - `src/Armada.Dashboard/src/main.tsx`
  - `src/Armada.Dashboard/src/App.tsx`
- [x] Add a `useLocale()` hook for locale state and metadata
- [x] Add a language selector component reusable in login and authenticated layout
- [-] Ensure route changes update page title and route metadata via translation keys
  Notes: 2026-04-23 - Document title is localized through the shared runtime. Route/page metadata still needs a route-by-route extraction pass.
- [-] Ensure all React notifications render from keys and interpolation, not English literals
  Notes: 2026-04-23 - Runtime translation now covers rendered notification text, but semantic-key notification generation is still pending.
- [ ] Ensure Suspense/loading boundaries can render translated loading copy

## Workstream 4: Legacy Dashboard Foundation

- [x] Add a lightweight translation runtime for the legacy dashboard
  Likely new files:
  - `src/Armada.Server/wwwroot/js/i18n.js`
  - shared locale JSON loader or generated asset manifest
- [x] Add a legacy locale store and boot logic
- [x] Add DOM helpers for:
  - translating static elements by key
  - translating Alpine-rendered content
  - formatting interpolated strings
  - updating `lang` and `dir`
- [x] Add a language selector to the legacy login overlay
- [x] Add a language selector to the authenticated legacy top bar or settings area
- [-] Ensure legacy modals and partials re-render correctly after locale switches
  Notes: 2026-04-23 - Mutation-observer translation is in place and locale changes trigger dashboard/detail refreshes. Remaining work is a full surface audit.

## Workstream 5: Login And Pre-Auth Experience

### React Login

- [x] Localize `src/Armada.Dashboard/src/components/LoginFlow.tsx`
- [x] Add language selector before any login step
- [x] Ensure selector is keyboard accessible
- [x] Persist locale before auth succeeds
- [x] Translate all of the following:
  - headings
  - explanatory copy
  - form labels
  - placeholders
  - button text
  - loading text
  - validation messages
  - auth errors
  - tooltip text
  - theme switch labels
  - alt text
  - `aria-label` values

### Legacy Login

- [x] Localize the login overlay in `src/Armada.Server/wwwroot/index.html`
- [x] Add the same language selector before email entry
- [x] Translate tenant lookup, tenant selection, password entry, and back-navigation copy
- [x] Translate auth failure, offline, and retry states emitted from `dashboard.js`

## Workstream 6: Shared Formatting And Dynamic Copy

- [-] Replace ad hoc `toLocaleString`, `toLocaleDateString`, and `toLocaleTimeString` calls with shared helpers
  Notes: 2026-04-23 - Shared helpers landed, and locale-sensitive built-in overrides now force existing dashboard calls to honor the selected language. File-by-file cleanup still remains.
- [-] Replace inline relative-time builders such as `5m ago`, `2h ago`, `3d ago`
  Notes: 2026-04-23 - Shared relative-time helpers and DOM translation patterns are in place. Remaining work is cleanup of hand-rolled builders.
- [ ] Replace inline unit builders such as:
  - `ms`
  - `chars`
  - `ahead`
  - `behind`
  - `Live`
  - `Online`
  - `Offline`
- [ ] Standardize number formatting for:
  - counts
  - percentages
  - durations
  - priorities
  - row counts
  - retry counts
- [ ] Add plural-aware helpers for English and non-English locales
- [ ] Audit every status badge to ensure raw values stay stable while labels are localized
- [ ] Audit every toast/notification builder to ensure semantic payload is stored and translated at render time

## Workstream 7: React Surface Extraction

### Shared Components And Contexts

- [ ] Localize `src/Armada.Dashboard/src/components/Layout.tsx`
- [ ] Localize `src/Armada.Dashboard/src/components/SetupWizard.tsx`
- [ ] Localize `src/Armada.Dashboard/src/components/shared/*`
- [ ] Localize `src/Armada.Dashboard/src/context/NotificationContext.tsx`
- [ ] Review `AuthContext`, `ThemeContext`, and `WebSocketContext` for locale-sensitive copy and metadata

### Route Surfaces

- [ ] Localize `Dashboard.tsx`
- [ ] Localize `Dispatch.tsx`
- [ ] Localize `Voyages.tsx`
- [ ] Localize `VoyageCreate.tsx`
- [ ] Localize `VoyageDetail.tsx`
- [ ] Localize `Missions.tsx`
- [ ] Localize `MissionDetail.tsx`
- [ ] Localize `Fleets.tsx`
- [ ] Localize `FleetDetail.tsx`
- [ ] Localize `Vessels.tsx`
- [ ] Localize `VesselDetail.tsx`
- [ ] Localize `Captains.tsx`
- [ ] Localize `CaptainDetail.tsx`
- [ ] Localize `Docks.tsx`
- [ ] Localize `DockDetail.tsx`
- [ ] Localize `MergeQueue.tsx`
- [ ] Localize `MergeQueueDetail.tsx`
- [ ] Localize `Signals.tsx`
- [ ] Localize `SignalDetail.tsx`
- [ ] Localize `Events.tsx`
- [ ] Localize `EventDetail.tsx`
- [ ] Localize `Notifications.tsx`
- [ ] Localize `Personas.tsx`
- [ ] Localize `PersonaDetail.tsx`
- [ ] Localize `Pipelines.tsx`
- [ ] Localize `PipelineDetail.tsx`
- [ ] Localize `PromptTemplates.tsx`
- [ ] Localize `PromptTemplateDetail.tsx`
- [ ] Localize `Server.tsx`
- [ ] Localize `Doctor.tsx`
- [ ] Localize `admin/Tenants.tsx`
- [ ] Localize `admin/Users.tsx`
- [ ] Localize `admin/Credentials.tsx`

### React Extraction Rules

- [ ] Replace hard-coded table headers with translation keys
- [ ] Replace hard-coded action menu labels
- [ ] Replace hard-coded modal copy
- [ ] Replace hard-coded confirm strings
- [ ] Replace hard-coded filter labels and placeholders
- [ ] Replace hard-coded badge/status labels
- [ ] Replace hard-coded empty/loading/error states
- [ ] Replace hard-coded chart legends, tooltips, and axis labels
- [ ] Replace hard-coded breadcrumb titles and route titles

## Workstream 8: Legacy Surface Extraction

### Root Shell

- [ ] Localize `src/Armada.Server/wwwroot/index.html`
- [ ] Localize `src/Armada.Server/wwwroot/js/dashboard.js`

### View Partials

- [ ] Localize `views/home.html`
- [ ] Localize `views/dispatch.html`
- [ ] Localize `views/voyages.html`
- [ ] Localize `views/missions.html`
- [ ] Localize `views/fleets-list.html`
- [ ] Localize `views/fleets.html`
- [ ] Localize `views/captains.html`
- [ ] Localize `views/docks.html`
- [ ] Localize `views/signals.html`
- [ ] Localize `views/events.html`
- [ ] Localize `views/merge-queue.html`
- [ ] Localize `views/server.html`
- [ ] Localize `views/doctor.html`
- [ ] Localize `views/modals.html`

### JS Modules

- [ ] Localize `js/modules/action-menu.js`
- [ ] Localize `js/modules/data-loaders.js`
- [ ] Localize `js/modules/filtering.js`
- [ ] Localize `js/modules/navigation.js`
- [ ] Localize `js/modules/pagination.js`
- [ ] Localize `js/modules/partial-loader.js`
- [ ] Localize `js/modules/selection.js`
- [ ] Localize `js/modules/sorting.js`
- [ ] Localize `js/modules/status.js`
- [ ] Localize `js/modules/viewers.js`
- [ ] Localize `js/modules/websocket.js`

### Legacy Extraction Rules

- [ ] Replace all inline `title="..."` strings with translation bindings
- [ ] Replace all inline button labels and section headers
- [ ] Replace modal labels, helper text, char counters, and validation copy
- [ ] Replace generated strings in Alpine expressions and JS template literals
- [ ] Replace hard-coded connection-state text and doctor/server status text
- [ ] Replace confirm/delete/cancel copy in JS actions
- [ ] Replace copied-to-clipboard success/failure messages

## Workstream 9: Notifications, Events, Statuses, And Server Data

- [ ] Create a single source of truth for localized labels for:
  - mission statuses
  - voyage statuses
  - captain states
  - merge queue states
  - doctor statuses
  - signal types
  - notification asset types
  - role names shown in the UI
- [ ] Ensure WebSocket-driven UI updates re-render in the current locale
- [ ] Ensure persisted notifications store raw type/id/status data rather than rendered English strings
- [ ] Review API payloads for strings currently emitted by the server and decide:
  - translate client-side from codes/enums
  - display server text as-is
  - add server-side message codes later

## Workstream 10: Styling, Typography, And Layout Resilience

- [ ] Add locale-aware font stacks for Latin, Simplified Chinese, Traditional Chinese, Cantonese, Japanese, and Korean
- [ ] Verify the login page, setup wizard, tables, modals, and action menus survive 30-50 percent text expansion
- [ ] Verify long German/French copy does not clip buttons or overflow table controls
- [ ] Verify CJK line height and character density remain readable
- [ ] Verify hover tooltips can render longer localized strings without truncating critical meaning
- [ ] Verify sticky headers, sidebars, and modal footers still behave correctly after expansion
- [ ] Verify responsive behavior on narrow widths for translated labels
- [ ] Add `dir`-aware CSS hooks now, even if RTL ships later

## Workstream 11: Accessibility

- [ ] Localize every `aria-label`, `aria-live` message, and screen-reader-only label
- [ ] Ensure the language selector is reachable and understandable by keyboard and assistive tech
- [ ] Ensure locale switching announces meaningful state changes where appropriate
- [ ] Ensure `lang` is correct at document level
- [ ] If mixed-language text appears, use element-level `lang` overrides where needed
- [ ] Re-run accessibility checks in at least:
  - English
  - German
  - Japanese
  - Arabic pseudo-locale or actual Arabic if shipped

## Workstream 12: Testing And CI

- [ ] Add unit tests for locale resolution and persistence
- [ ] Add unit tests for translation helpers and formatters
- [ ] Add component tests for:
  - login language selector
  - authenticated language selector
  - translated nav/layout
  - translated setup wizard
  - translated notifications/toasts
- [ ] Add regression tests for language persistence across logout/login/reload
- [ ] Add pseudo-localization test runs to catch clipping and missing keys
- [ ] Add a missing-key CI check
- [ ] Add a dead-key/orphan-key CI check
- [ ] Add a lint or static check that blocks new hard-coded UI strings in dashboard code
- [ ] Add smoke coverage for at least these locales:
  - `en`
  - `es`
  - `zh-Hans`
  - `yue-Hant`
  - `ja`
- [ ] Add at least one visual regression pass for expanded strings and CJK layouts

## Workstream 13: Translation Workflow

- [ ] Define translation key naming conventions
- [ ] Define interpolation conventions for variables, links, counts, and dates
- [ ] Define pluralization conventions
- [ ] Define review ownership for source English keys and localized catalogs
- [ ] Decide whether machine translation is allowed for bootstrap only, with human review required before release
- [ ] Add translator notes for Armada-specific concepts that should not be mistranslated
- [ ] Add a glossary for product terms and operational jargon

## Workstream 14: Documentation And Rollout

- [ ] Document how to add a new locale
- [ ] Document how to extract new strings
- [ ] Document how to test pseudo-locales locally
- [ ] Document the fallback policy
- [ ] Document the terminology glossary
- [ ] Document what remains intentionally untranslated in phase 1
- [ ] Add release notes for the new language support and selection behavior

## Minimum File Touch List

The following files are known high-priority starting points and should be treated as mandatory in the first implementation wave.

### React

- `src/Armada.Dashboard/src/main.tsx`
- `src/Armada.Dashboard/src/App.tsx`
- `src/Armada.Dashboard/src/components/Layout.tsx`
- `src/Armada.Dashboard/src/components/LoginFlow.tsx`
- `src/Armada.Dashboard/src/components/SetupWizard.tsx`
- `src/Armada.Dashboard/src/context/NotificationContext.tsx`
- `src/Armada.Dashboard/src/pages/*.tsx`
- `src/Armada.Dashboard/src/pages/admin/*.tsx`

### Legacy

- `src/Armada.Server/wwwroot/index.html`
- `src/Armada.Server/wwwroot/js/dashboard.js`
- `src/Armada.Server/wwwroot/js/modules/*.js`
- `src/Armada.Server/wwwroot/views/*.html`

## Implementation Order

- [ ] Phase 1: Lock locale policy, resource layout, and shared helper strategy
- [ ] Phase 2: Land the shared locale infrastructure
- [ ] Phase 3: Land React language selection and provider wiring
- [ ] Phase 4: Land legacy language selection and runtime wiring
- [ ] Phase 5: Migrate login flows
- [ ] Phase 6: Migrate layout/chrome/navigation
- [ ] Phase 7: Migrate formatting helpers and dynamic status labels
- [ ] Phase 8: Migrate page surfaces route-by-route
- [ ] Phase 9: Add tests, pseudo-locales, and CI gates
- [ ] Phase 10: Complete translation content, glossary, and docs

## Exit Criteria

- [ ] No hard-coded user-facing English strings remain in dashboard source files except explicitly approved product names or raw server payloads
- [ ] Both login flows support language selection before auth
- [ ] React and legacy dashboards render correctly in all launch locales
- [ ] Missing key checks pass in CI
- [ ] Pseudo-locale expansion tests pass
- [ ] Locale persists across reloads and sign-in state changes
- [ ] Documentation for adding locales and maintaining catalogs is merged

## Risks And Mitigations

- [ ] Risk: React and legacy dashboards diverge in terminology
  Mitigation: shared catalogs and glossary
- [ ] Risk: Server-sent English error strings leak into localized UI
  Mitigation: move toward code-based client translation where possible
- [ ] Risk: Long strings break tables, badges, and modal footers
  Mitigation: pseudo-locales, visual regression, expansion review
- [ ] Risk: Notification persistence stores English text forever
  Mitigation: persist semantic payload only
- [ ] Risk: `Kanji` is treated as a locale and creates bad locale design
  Mitigation: resolve it as a product decision before translation work starts

## Suggested First Pull Request

- [ ] Add shared locale registry and persistence
- [ ] Add React i18n bootstrap and language selector
- [ ] Add legacy i18n bootstrap and language selector
- [ ] Wire `lang` and `dir`
- [ ] Localize login flows only
- [ ] Add pseudo-locale scaffolding
- [ ] Add missing-key CI check

This first PR should intentionally stop after infrastructure plus login. It creates the skeleton needed to migrate the rest of the dashboard without mixing architecture work and broad copy extraction in one giant change.
