# UI i18n conventions (read before extracting strings)

The scaffold is `ui/src/i18n/index.ts` (i18next + react-i18next + browser language
detector). English is the default/fallback; Japanese is the `ja` resource. The
user picks the language in **Settings → Preferences** (persisted to
`localStorage["maestro.lang"]`).

## Namespaces = one resource file per UI area

Resources live at `ui/src/i18n/locales/{en,ja}/{namespace}.json` and are
auto-registered via a Vite glob — **just drop a file in, no wiring needed.**
Each area owns its namespace so parallel extractors never edit the same file:

| namespace | owns |
|-----------|------|
| `common` | shared words (Save/Cancel/Delete/Loading/Error) |
| `layout` | TopBar, NavDrawer, shell chrome |
| `settings` | `components/settings/**` |
| `detail` | `components/detail/**` (task detail tabs) |
| `list` | `components/list/**` (task list) |
| `create` | `components/create/**` (CreateTaskDialog) |
| `files` | `components/files/**` (FilePreview etc.) |
| `auth` | login/admin/user dialogs |
| `console` | console/SSH UI |
| `knowledge` | knowledge/notes UI |

Add keys ONLY to your area's namespace (+ `common` for truly shared words).

## The pattern (copy this)

```tsx
import { useTranslation } from 'react-i18next';

function MyComponent() {
  const { t } = useTranslation('settings'); // your namespace
  return <h3>{t('section.title')}</h3>;
}
```

- Keys are dot-namespaced **semantic** names: `preferences.visibility.title`,
  not `text1`. Group by feature.
- Put the English text in `en/<ns>.json` and the ORIGINAL Japanese in
  `ja/<ns>.json` under the same key. Both files, same key shape.
- Reference implementations to mirror: `components/settings/PreferencesForm.tsx`
  + `locales/{en,ja}/settings.json`, and `components/layout/TopBar.tsx` +
  `locales/{en,ja}/layout.json`.

## Interpolation

```tsx
t('subtasks.count', { count })          // "3 subtasks"
```
```json
{ "subtasks": { "count_one": "{{count}} subtask", "count_other": "{{count}} subtasks" } }
```
Use `{{var}}` in the JSON. Never build sentences by string concatenation.

## Attributes & non-JSX strings

Translate `placeholder`, `aria-label`, `title`, `alt`, and strings in
`toast()/alert()/throw new Error()` the same way: `placeholder={t('...')}`.

## Module-level constants (IMPORTANT)

Constants defined at module scope can't call `useTranslation`. Store a KEY and
translate at the render site. Canonical example: `NAV_ITEMS` in `TopBar.tsx`
holds Japanese `label`s today — the `layout` extractor must change `label` to a
`labelKey` (e.g. `'nav.tasks'`) and translate at each consumer:
`TopBar.tsx`, `NavDrawer.tsx`, and `App.tsx` (the `navItems` map ~line 243).
Update ALL consumers in the same change.

## Out of scope — DO NOT translate

- Agent-facing content: piece YAML, system/movement prompts, LLM output, lessons.
- Backend-provided strings rendered as data (API responses). If the UI shows
  Japanese that came from the server, that's a server concern — leave it and note it.
- Code/config literals, command strings, config keys, file paths, URLs, the name
  "MAESTRO". Translate prose only.

## Verify

`npm run build` (vite) in `ui/` must pass. Spot-check that no code/config literal
was altered. Add keys to BOTH `en` and `ja`; a missing key falls back to English
then to the key name.
