# Markus Web-UI Internationalization (i18n) Convention

## Overview

Markus uses **i18next** + **react-i18next** for internationalization.
All user-visible strings in the web-ui must go through the translation system —
no hardcoded display text in components.

| Item | Value |
|---|---|
| Framework | `i18next` + `react-i18next` |
| Language detection | `i18next-browser-languagedetector` (localStorage → navigator) |
| localStorage key | `markus-language` |
| Fallback language | `en` |
| Default namespace | `common` |
| Config entry | `src/i18n/index.ts` |

Supported languages:

| Code | Label |
|---|---|
| `en` | English |
| `zh-CN` | 中文 |

---

## Directory Structure

```
src/
├── i18n/
│   └── index.ts            # i18next init & config
└── locales/
    ├── en/
    │   ├── common.json      # shared UI primitives (buttons, status, units)
    │   ├── nav.json         # sidebar / bottom-nav labels
    │   ├── auth.json        # login / change-password
    │   ├── home.json        # home / dashboard page
    │   ├── team.json        # team management
    │   ├── work.json        # work / task queue page
    │   ├── settings.json    # settings page
    │   ├── onboarding.json  # first-run onboarding
    │   ├── builder.json     # agent builder
    │   ├── store.json       # agent / skill / template store
    │   ├── agent.json       # agent profile page
    │   ├── deliverables.json
    │   └── reports.json
    └── zh-CN/
        └── (mirrors en/ — same files, same keys)
```

---

## Namespaces

Each JSON file is an i18next **namespace**. A namespace maps 1-to-1 with a
page or a logical feature area.

| Namespace | Scope |
|---|---|
| `common` | Shared buttons, labels, status badges, units, time formats, generic modals |
| `nav` | Sidebar links, section headers, bottom navigation tabs |
| `auth` | Login, logout, change-password flows |
| `home` | Home / dashboard page |
| `team` | Team page, team modals, chat sidebar team features |
| `work` | Work queue, task details, approval flows |
| `settings` | All settings sections (appearance, LLM, storage, etc.) |
| `onboarding` | First-run setup wizard |
| `builder` | Agent builder page |
| `store` | Agent store, skill store, template marketplace |
| `agent` | Agent profile / detail page |
| `deliverables` | Deliverables page |
| `reports` | Reports page |

### When to create a new namespace

Create a new namespace only when adding a **new top-level page or large
feature area** that would add 30+ keys to an existing namespace. Keep the
total number of namespaces small; prefer extending an existing namespace.

When adding a new namespace:
1. Create `src/locales/en/<ns>.json` and `src/locales/zh-CN/<ns>.json`.
2. Add static imports for both to `src/i18n/index.ts`.
3. Register the namespace in `NAMESPACES` and in the `resources` object.

---

## Translation Key Conventions

### Naming

- Use **camelCase** for keys: `saveChanges`, `networkError`.
- Use **dot-separated nesting** for logical groups: `appearance.language`, `status.pending`.
- Prefix page-section keys with the section name: `setupGuide.title`, `llm.providerLabel`.
- Keep keys **descriptive but concise**. Prefer `confirmDelete` over `areYouSureYouWantToDeleteThisItem`.

### Flat vs. nested

Use a shallow hierarchy (max 2–3 levels). Deeply nested keys are hard to
discover and maintain.

```json
{
  "appearance": {
    "title": "Appearance",
    "language": "Language",
    "languageDesc": "Choose the display language"
  }
}
```

### Interpolation

Use `{{variable}}` for dynamic values:

```json
{
  "error": "Error: {{message}}",
  "selected": "{{count}} selected",
  "units": {
    "members": "{{count}} members"
  }
}
```

In code:

```tsx
t('error', { message: err.message })
t('selected', { count: items.length })
```

### Pluralization

Use i18next plural suffixes `_one` / `_other`:

```json
{
  "toolsExecuted_one": "{{count}} tool executed",
  "toolsExecuted_other": "{{count}} tools executed"
}
```

### HTML in translations

Use `<Trans>` from react-i18next when a string contains inline JSX:

```tsx
<Trans i18nKey="onboarding.welcome" t={t}>
  Welcome to <strong>Markus</strong>
</Trans>
```

---

## Usage in Components

### Basic usage

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

function MyPage() {
  const { t } = useTranslation('work');
  return <h1>{t('title')}</h1>;
}
```

### Multiple namespaces

When a component needs keys from several namespaces, pass an array.
The **first** namespace becomes the default; prefix keys from others:

```tsx
const { t } = useTranslation(['team', 'common']);

t('title')                    // → team:title
t('common:save')              // → common:save
t('common:status.active')     // → common:status.active
```

### Accessing the i18n instance

When you need the language or `changeLanguage`:

```tsx
const { t, i18n } = useTranslation('settings');
i18n.changeLanguage('zh-CN');
```

### Non-component contexts

For utility functions or non-React modules, import the singleton:

```ts
import i18n from '../i18n/index.ts';
i18n.t('common:networkError');
```

Avoid this pattern when possible — prefer passing translated strings from
the calling component.

---

## Adding a New Language

1. Create a new directory under `src/locales/` (e.g. `src/locales/ja/`).
2. Copy **every** JSON file from `src/locales/en/` and translate all values.
   **Keys must be identical** across all locales.
3. In `src/i18n/index.ts`:
   - Add static imports for the new locale files.
   - Add a new entry in the `resources` object.
   - Add the language to `SUPPORTED_LANGUAGES`.
4. The language switcher in Settings will automatically pick it up.

---

## Rules for New Code

1. **Never hardcode user-visible strings** — always use `t()` or `<Trans>`.
2. **Pick the right namespace** — use the namespace that matches the page or
   feature where the string appears.
3. **Reuse `common` keys** — generic labels like "Save", "Cancel", "Loading…"
   already exist in `common`. Don't duplicate them.
4. **Add keys to both locales** — every key added to `en/*.json` must also be
   added to `zh-CN/*.json` (and any other supported locale) in the same PR.
5. **Don't translate** programmatic values (API endpoints, enum identifiers,
   CSS class names, log messages, etc.).
6. **Keep locale files sorted** — within each JSON file, maintain alphabetical
   ordering at each nesting level for easier diffing.
7. **Test both languages** — switch to each language in Settings → Appearance
   and verify no raw keys (`team.title`) appear in the UI.
8. **Avoid string concatenation** — use interpolation (`{{name}}`) instead
   of `t('hello') + ' ' + name`. Concatenation breaks in languages with
   different word order.
9. **Avoid splitting sentences** — a full sentence should be a single key.
   Never split "You have 3 items" into separate keys for "You have" and "items".

---

## Checklist for Pull Requests

- [ ] All new user-visible strings use `t()` or `<Trans>`
- [ ] Keys added to **all** locale files (`en/` and `zh-CN/`)
- [ ] Key names follow camelCase, dot-nested conventions
- [ ] No duplicate keys (reuse `common` namespace where appropriate)
- [ ] No string concatenation for translated text
- [ ] Language switcher shows correct translations
- [ ] No raw i18n keys visible in the UI for any supported language
