# Localization & Language Dropdown — Design

**Date:** 2026-06-29
**Status:** Approved design, pre-plan
**Scope:** The `simulator/` web experience — localize all UI controls and the
Left/Right brain annotations, selectable via a language dropdown.

---

## 1. Goal

Let a visitor switch the experience's language from a dropdown. Switching
re-renders **both** the UI chrome (controls, labels, hints) **and** the
Left/Right brain annotation content (object labels, facts, emotion words) into
the chosen language, live, without reloading the page or interrupting playback.

## 2. Languages (this pass)

Four languages, English as the source/default:

| code | native name | notes |
|------|-------------|-------|
| `en` | English     | source of truth, fallback |
| `es` | Español     | |
| `fr` | Français    | |
| `ja` | 日本語       | CJK glyphs; no layout-direction change |

Hebrew and Chinese were considered and **deliberately deferred** to a later
pass. Hebrew was the only right-to-left language, so **no RTL / UI-mirror work
is in scope here.** The language set is data-driven, so adding more later
(including an RTL pass) is additive.

## 3. What needs translating — two very different costs

1. **UI chrome** — ~20 fixed control strings (`Loading Universe`, `Output`,
   `Video`, `Audio`, `Altitude`, `Think`, `Feel`, `Mood`, the six scale names,
   hints, dev-mode labels). Small, static.
2. **Annotations** — the bulk. The manifest holds **41 clips / 753 authored
   English string-variants** (Left labels + facts + Right emotions, many as
   tier-lists). Authoring `es/fr/ja` = **753 × 3 ≈ 2,260 strings**. This is
   *content*, and it is the dominant effort.

## 4. Architecture — three isolated pieces

### 4.1 Language registry
A single small module (`simulator/static/i18n.js` or a sibling) exporting the
ordered list `[{code, nativeName}]` for `en, es, fr, ja`. It is the **one source
of truth** consumed by the dropdown and `<html lang>`. No `dir` field needed
this pass (no RTL).

### 4.2 UI string catalog
A `{ key: {en, es, fr, ja} }` map for the ~20 control strings, living alongside
the registry. `index.html`'s static text is tagged with `data-i18n="key"`
attributes (removing hardcoded English from the markup); an
`applyUiStrings(lang)` function fills every tagged node on load and on every
language change.

### 4.3 Annotation strings — no schema change
The manifest already stores `strings: {"en": {key: text}}` per clip. We add
`es / fr / ja` sibling keys to each clip's `strings` object. The two render
sites change from a hardcoded `.en`:

- `simulator/static/app.js:505` (Left labels/facts)
- `simulator/static/app.js:556` (Right emotions)

…to `clip.strings[activeLang] || clip.strings.en` — an **English fallback** so a
missing key never blanks the overlay. Tier-list structure (general→scientific,
emotion escalation) is preserved per language.

## 5. The dropdown & selection behavior

- A `<select>` in the control panel (under "Output"), populated from the
  registry, showing **native names** (Español, 日本語, …).
- `activeLang` is a module-level variable defaulting to `"en"`.
- **No persistence** (no localStorage): every page load starts in English; a
  visitor's selection lasts only the session. Fits an installation that resets
  between visitors.
- On change: set `activeLang` → `applyUiStrings(lang)` → set
  `document.documentElement.lang` → re-render the current frame's labels and
  emotions. **No reload, no video interruption.**

## 6. Authoring the translations (one-shot tooling)

`tools/i18n/translate_manifest.py`:
- Reads each clip's `strings.en` from the manifest.
- Produces `es / fr / ja` translations, **preserving tier-list structure** (a
  list stays a list of the same length) and the general→scientific /
  emotion-escalation intent.
- Writes them back into the manifest in place.
- **Idempotent / re-runnable:** skips keys already present in a target language,
  so it can be run incrementally.
- Output is committed as content.

**Authorship caveat:** translations are LLM-generated and committed as the real
content. A human spot-check of the Japanese pass precedes merge; this design
does not claim native-quality fluency.

## 7. Testing

- **Unit (node):**
  - registry integrity (codes unique, English present);
  - `applyUiStrings` swaps every `data-i18n` node and leaves none in English
    when a non-English language is active;
  - annotation picker falls back to `en` on a missing key;
  - tier-list length is preserved across languages.
- **Manifest test (pytest):** every clip's `strings` has all four language keys,
  with **identical key-sets** and **identical tier-counts** to `en` (catches a
  translation that dropped a key or a tier).
- **E2E (Playwright):** select each language → control labels change to that
  language; select back to English → original restored; annotations re-render on
  switch.

## 8. Out of scope (this pass)

- Hebrew / RTL / full-UI-mirror.
- Chinese.
- Browser language auto-detection.
- Persisting the chosen language across loads.
- Localizing author-mode (`/author.html`) tooling UI.

## 9. Open follow-ups (noted, not built)

- A later RTL pass (Hebrew/Arabic) would add a `dir` field to the registry and a
  logical-property CSS migration.
- Native-speaker review of the committed translations.
