# Translation Guide

> **Quick start for contributors:** This guide helps you add a new language translation to Open Design in ~2 hours instead of ~8 hours. Follow the checklist, avoid common mistakes, and ship your PR with confidence.

For general contribution flow, see [CONTRIBUTING.md](CONTRIBUTING.md). The "Localization maintenance" section there documents the boundary between translated surfaces and agent-facing source material. This file covers **how** to add and maintain a locale across the surfaces contributors touch most often: UI chrome, root READMEs, core docs, and display metadata.

> **Why a separate file?** i18n contributors usually only need this surface — keeping locale workflow out of the main contribution guide isolates jargon (BCP-47, fallback chains, regional glossaries) from the broader code-workflow audience. CONTRIBUTING.md cross-links here for discovery.

---

## 🚀 Quick Start: Adding Your Language in 5 Steps

**New to translation contributions?** Start here. This checklist covers the 80% case.

### Step 1: Choose Your Language Code

Pick a standard code:
- Two-letter for most languages: `de`, `fr`, `it`, `sv`
- Regional variants when needed: `pt-BR`, `zh-CN`, `zh-TW`, `es-ES`
- Use hyphens, not underscores: `zh-CN` ✅ not `zh_CN` ❌

### Step 2: Translate the README

Translations live in `docs/i18n/`; only the English `README.md` stays in the repo root (GitHub renders the root README as the project home page).

```bash
# Copy and translate
cp README.md docs/i18n/README.it.md
# Edit docs/i18n/README.it.md in your editor
```

**What to translate:**
- ✅ All text, headings, descriptions
- ✅ Alt text: `alt="Open Design banner"`
- ✅ Link text: `[Quickstart](../../QUICKSTART.md)` → `[Guida rapida](QUICKSTART.it.md)` (paths are relative to `docs/i18n/`: link a translated core doc as a sibling filename, or fall back to the English target at `../../QUICKSTART.md`)

**What NOT to translate:**
- ❌ Code snippets, commands, file paths
- ❌ URLs, GitHub usernames, repo names
- ❌ Brand names: "Open Design", "Claude Code"
- ❌ Technical terms: CLI, API, BYOK, daemon

### Step 3: Update ALL Language Switchers (Critical!)

**This is the most commonly forgotten step.** You must update the language switcher in:
1. Your new `docs/i18n/README.it.md` (bold your language)
2. **Every existing README** — the English `README.md` in the repo root *and* every `docs/i18n/README.*.md` (add your language as a link)

The switcher uses two link conventions depending on which file it lives in:

- **English root `README.md`** — bold `English`; link translations with the `docs/i18n/` prefix:
  ```html
  <p align="center"><b>English</b> · <a href="docs/i18n/README.es.md">Español</a> · ... · <a href="docs/i18n/README.it.md">Italiano</a></p>
  ```
- **Translated `docs/i18n/README.xx.md`** — bold your own language; link English with `../../README.md` and the other translations as sibling filenames:
  ```html
  <p align="center"><a href="../../README.md">English</a> · <a href="README.es.md">Español</a> · ... · <b>Italiano</b></p>
  ```

**Files to update:** `README.md` (root), `docs/i18n/README.ar.md`, `docs/i18n/README.de.md`, `docs/i18n/README.es.md`, `docs/i18n/README.fr.md`, `docs/i18n/README.ja-JP.md`, `docs/i18n/README.ko.md`, `docs/i18n/README.pt-BR.md`, `docs/i18n/README.ru.md`, `docs/i18n/README.tr.md`, `docs/i18n/README.uk.md`, `docs/i18n/README.zh-CN.md`, `docs/i18n/README.zh-TW.md`

### Step 4: Add UI Dictionary (Optional but Recommended)

Create `apps/web/src/i18n/locales/it.ts`:

```typescript
import type { Dict } from '../types';
import { en } from './en';

export const it: Dict = {
  ...en, // Fallback to English for missing keys
  // Translate these UI strings
  'common.create': 'Crea',
  'common.cancel': 'Annulla',
  'settings.language': 'Lingua',
  'entry.tabDesigns': 'Design',
  'entry.tabTemplates': 'Modelli',
  // ... see en.ts for full list
};
```

> **Note:** The `Dict` type enforces that all keys match those in `en.ts`. Invented keys like `'nav.home'` will fail TypeScript compilation.

Then register it in `apps/web/src/i18n/index.tsx` and `apps/web/src/i18n/types.ts` (see [detailed steps below](#adding-a-new-locale)).

**Don't forget to update test fixtures:** Add your locale code to `EXPECTED_LOCALES` in `apps/web/tests/i18n/locales.test.ts` and add a `LOCALE_LABEL` assertion (e.g., `expect(LOCALE_LABEL.it).toBe('Italiano');`). Run `pnpm --filter @open-design/web test` to verify.

### Step 5: Test and Submit

```bash
# Type check
pnpm typecheck

# Run i18n checks
pnpm i18n:check

# Visual check: open your docs/i18n/README.it.md in GitHub preview
# Verify all links work, images load, language switcher displays correctly
```

**PR title:** `feat(i18n): add Italian translation`

**PR checklist:**
- [ ] README translated
- [ ] Language switcher updated in ALL existing READMEs
- [ ] UI dictionary added (if applicable)
- [ ] All links tested
- [ ] `pnpm i18n:check` passes

---

## 📋 Supported Languages

Open Design currently supports **19 languages** across different surfaces:

| Language             | Code    | README | UI Dict | Core Docs | Status |
| -------------------- | ------- | ------ | ------- | --------- | ------ |
| English              | `en`    | ✅     | ✅      | ✅        | source |
| العربية (Arabic)     | `ar`    | ✅     | ✅      | —         | active |
| Deutsch              | `de`    | ✅     | ✅      | ✅        | active |
| Español              | `es-ES` | ✅     | ✅      | —         | active |
| فارسی (Persian)      | `fa`    | —      | ✅      | —         | active |
| Français             | `fr`    | ✅     | ✅      | ✅        | active |
| Magyar (Hungarian)   | `hu`    | —      | ✅      | —         | active |
| Bahasa Indonesia     | `id`    | —      | ✅      | —         | active |
| Italiano             | `it`    | —      | ✅      | —         | active |
| 日本語 (Japanese)    | `ja`    | ✅     | ✅      | ✅        | active |
| 한국어 (Korean)      | `ko`    | ✅     | ✅      | ✅        | active |
| Polski (Polish)      | `pl`    | —      | ✅      | —         | active |
| Português (Brasil)   | `pt-BR` | ✅     | ✅      | ✅        | active |
| Русский (Russian)    | `ru`    | ✅     | ✅      | —         | active |
| ภาษาไทย (Thai)       | `th`    | —      | ✅      | —         | active |
| Türkçe (Turkish)     | `tr`    | ✅     | ✅      | —         | active |
| Українська           | `uk`    | ✅     | ✅      | —         | active |
| 简体中文             | `zh-CN` | ✅     | ✅      | ✅        | active |
| 繁體中文             | `zh-TW` | ✅     | ✅      | —         | active |

**Translation surfaces:**
- **README**: Project README, translated into `docs/i18n/README.{lang}.md` (English source stays at root `README.md`)
- **UI Dict**: Web interface strings (`apps/web/src/i18n/locales/{lang}.ts`)
- **Core Docs**: `docs/i18n/QUICKSTART.{lang}.md`, `docs/i18n/CONTRIBUTING.{lang}.md` (English sources stay at root `QUICKSTART.md`, `CONTRIBUTING.md`)

> **Note:** You can contribute any subset of these surfaces. Start with README (highest impact), then add UI dictionary and core docs when you have time.

### File Locations

- **UI dictionaries**: [`apps/web/src/i18n/locales/`](apps/web/src/i18n/locales/)
- **English sources**: [`README.md`](README.md), [`QUICKSTART.md`](QUICKSTART.md), [`CONTRIBUTING.md`](CONTRIBUTING.md), [`MAINTAINERS.md`](MAINTAINERS.md) stay in the project root
- **Translated docs**: [`docs/i18n/`](docs/i18n/) holds every `README.{lang}.md`, `QUICKSTART.{lang}.md`, `CONTRIBUTING.{lang}.md`, and `MAINTAINERS.{lang}.md`
- **Display metadata**: `apps/web/src/i18n/content*.ts` (optional, for gallery/examples)

The `LOCALES` array in [`apps/web/src/i18n/types.ts`](apps/web/src/i18n/types.ts) is the authoritative list for UI dictionaries. README language switchers cover every locale that has a README translation in `docs/i18n/`; this set can differ from `LOCALES`.

---

## 📖 Detailed Guide

### Adding a new locale

**For UI dictionary + README translation:**

1. **Pick a BCP-47 code.** Use the regional form (`pt-BR`, `es-ES`, `zh-TW`) when the variant matters; the bare code (`fr`, `ru`, `it`) when it doesn't. `pt-BR` and a hypothetical `pt-PT` would coexist as separate locales — the same precedent applies to `en-US` / `en-GB` if a contributor wants to maintain both.

2. **Update [`apps/web/src/i18n/types.ts`](apps/web/src/i18n/types.ts):**
   - Extend the `Locale` union with your code
   - Append your code to the `LOCALES` array
   - Add a `LOCALE_LABEL[<code>]` entry — use the **native name** of the language (`Italiano`, `日本語`, not `it`, `ja`)

   ```typescript
   export type Locale = 'en' | 'de' | 'fr' | 'it' | /* ... */;
   
   export const LOCALES: Locale[] = ['en', 'de', 'fr', 'it', /* ... */];
   
   export const LOCALE_LABEL: Record<Locale, string> = {
     en: 'English',
     de: 'Deutsch',
     fr: 'Français',
     it: 'Italiano',
     // ...
   };
   ```

   **Then update test fixtures:** In [`apps/web/tests/i18n/locales.test.ts`](apps/web/tests/i18n/locales.test.ts), add your locale to the `EXPECTED_LOCALES` array and add a `LOCALE_LABEL` assertion:
   
   ```typescript
   const EXPECTED_LOCALES = ['en', 'id', 'de', /* ... */, 'it', /* ... */];
   
   // In the test body:
   expect(LOCALE_LABEL.it).toBe('Italiano');
   ```

   **If your locale is RTL (Arabic, Hebrew, Persian, Urdu, etc.):** also append your code to `RTL_LOCALES` in [`apps/web/src/i18n/index.tsx`](apps/web/src/i18n/index.tsx). This array controls the `dir="rtl"` attribute on `<html>` at runtime — without it the web UI renders LTR regardless of language. The current list is:

   ```typescript
   const RTL_LOCALES: Locale[] = ['ar', 'fa'];
   ```

3. **Create the dictionary** at `apps/web/src/i18n/locales/<code>.ts`:
   - Copy from `en.ts` and translate the values
   - Keys must match `en.ts` exactly
   - Missing keys fall back to English at runtime
   - Use `...en` spread for partial translations

   ```typescript
   import type { Dict } from '../types';
   import { en } from './en';

  export const it: Dict = {
    ...en, // Fallback for untranslated keys
    'common.create': 'Crea',
    'common.cancel': 'Annulla',
    'common.save': 'Salva',
    'settings.language': 'Lingua',
    'entry.tabDesigns': 'Design',
    'entry.tabTemplates': 'Modelli',
    // ... translate all keys from en.ts
  };
   ```

4. **Register your dictionary** in [`apps/web/src/i18n/index.tsx`](apps/web/src/i18n/index.tsx):

   ```typescript
   import { it } from './locales/it';
   // ...
   const DICTS: Record<Locale, Dict> = {
     en,
     de,
     fr,
     it, // Add your locale here
     // ...
   };
   ```

5. **Translate the README:**
   - Copy `README.md` to `docs/i18n/README.<code>.md` (translations live under `docs/i18n/`; the English `README.md` is the only one in the repo root)
   - Repository precedent may use a documentation-region code that differs from the UI dict code when that is the familiar docs filename, such as `README.ja-JP.md` with UI locale `ja`, or `README.es.md` with UI locale `es-ES`
   - Translate all prose, headings, alt text, and link text
   - Keep code snippets, URLs, and brand names in English
   - Fix relative paths for the new location: links to repo-root resources (`apps/`, `docs/`, `LICENSE`, the English `README.md`, etc.) need a `../../` prefix; links to a sibling translated core doc stay as a bare filename. Example: `[Quickstart](../../QUICKSTART.md)` → `[Guida rapida](QUICKSTART.it.md)` if that translation exists, else `[Guida rapida](../../QUICKSTART.md)`

6. **Update the language switcher in EVERY README** — the root `README.md` and every `docs/i18n/README.*.md` (line ~25 of each):
   - Match the order used in the English README
   - Include the same set everywhere
   - Bold the current language: `<b>Italiano</b>`
   - The link form differs by file location (see below)

   **English root `README.md`** — bold `English`, link translations with the `docs/i18n/` prefix:
   ```html
   <p align="center"><b>English</b> · <a href="docs/i18n/README.es.md">Español</a> · <a href="docs/i18n/README.pt-BR.md">Português</a> · <a href="docs/i18n/README.de.md">Deutsch</a> · <a href="docs/i18n/README.fr.md">Français</a> · <a href="docs/i18n/README.zh-CN.md">简体中文</a> · <a href="docs/i18n/README.zh-TW.md">繁體中文</a> · <a href="docs/i18n/README.ko.md">한국어</a> · <a href="docs/i18n/README.ja-JP.md">日本語</a> · <a href="docs/i18n/README.ar.md">العربية</a> · <a href="docs/i18n/README.ru.md">Русский</a> · <a href="docs/i18n/README.uk.md">Українська</a> · <a href="docs/i18n/README.tr.md">Türkçe</a> · <a href="docs/i18n/README.it.md">Italiano</a></p>
   ```

   **Translated `docs/i18n/README.<code>.md`** — link English with `../../README.md`, the other translations as sibling filenames, bold your own:
   ```html
   <p align="center"><a href="../../README.md">English</a> · <a href="README.es.md">Español</a> · <a href="README.pt-BR.md">Português</a> · <a href="README.de.md">Deutsch</a> · <a href="README.fr.md">Français</a> · <a href="README.zh-CN.md">简体中文</a> · <a href="README.zh-TW.md">繁體中文</a> · <a href="README.ko.md">한국어</a> · <a href="README.ja-JP.md">日本語</a> · <a href="README.ar.md">العربية</a> · <a href="README.ru.md">Русский</a> · <a href="README.uk.md">Українська</a> · <a href="README.tr.md">Türkçe</a> · <b>Italiano</b></p>
   ```

7. **(Optional) Translate core docs:**
   - Copy `QUICKSTART.md` → `docs/i18n/QUICKSTART.<code>.md`
   - Copy `CONTRIBUTING.md` → `docs/i18n/CONTRIBUTING.<code>.md`
   - Follow existing examples: `docs/i18n/QUICKSTART.fr.md`, `docs/i18n/CONTRIBUTING.pt-BR.md`, `docs/i18n/CONTRIBUTING.ja-JP.md`
   - Apply the same `../../`-for-root-resources rule; links between translated docs in `docs/i18n/` stay as bare sibling filenames
   - Update links from the translated README to the translated core docs

8. **(Optional) Translate display metadata** in `apps/web/src/i18n/content*.ts`:
   - Keep this to display-only metadata for examples, gallery cards, and localized content chrome
   - Agent-executed prompts, skill instructions, design systems, and prompt bodies stay in their source language so prompt QA remains centralized

9. **Run checks:**
   ```bash
   pnpm typecheck  # Confirms locale union and DICTS map agree
   pnpm i18n:check  # Enforces UI locale registration and README switcher consistency
   pnpm --filter @open-design/web test  # Covers locale/content drift tests
   ```

### Translation Best Practices

**What to translate:**
- ✅ All prose text, headings, descriptions
- ✅ Alt text in images: `alt="Open Design banner"` → `alt="Banner di Open Design"`
- ✅ Badge labels where appropriate: `discord-join` → `discord-unisciti`
- ✅ Code comments in examples (if instructional)
- ✅ Link text: `[Quickstart](../../QUICKSTART.md)` → `[Guida rapida](QUICKSTART.it.md)` (sibling translation in `docs/i18n/` if it exists; otherwise keep the English target `../../QUICKSTART.md`)

**What NOT to translate:**
- ❌ Code snippets (commands, file paths, variable names)
- ❌ URLs and domain names
- ❌ GitHub usernames and repository names
- ❌ Brand names: "Open Design", "Claude Code", "Anthropic", "Vercel"
- ❌ Technical terms with no standard translation: CLI, API, SDK, BYOK, daemon, sidecar, monorepo, artifact, iframe
- ❌ Command output (keep terminal output in English as it appears in actual software)

**Terminology guidelines:**
- Use the English term with a brief explanation in parentheses on first use if no standard translation exists:
  ```
  Open Design è un'alternativa open-source (codice aperto) a Claude Design.
  ```
- For regional variants (zh-CN vs zh-TW, pt-BR vs pt-PT), choose the most widely understood variant for your target audience
- See [Regional terminology](#regional-terminology) section for specific glossaries

### Badge Translation

Some badges in the README can be localized by changing the badge URL:

```markdown
<!-- English -->
<a href="https://discord.gg/qhbcCH8Am4"><img alt="Discord" src="https://img.shields.io/badge/discord-join-5865F2?style=flat-square&logo=discord&logoColor=white" /></a>

<!-- Italian -->
<a href="https://discord.gg/qhbcCH8Am4"><img alt="Discord" src="https://img.shields.io/badge/discord-unisciti-5865F2?style=flat-square&logo=discord&logoColor=white" /></a>
```

**Translate these badge labels:**
- Download button: `download` → your language
- Quickstart badge: `quickstart` → your language  
- Discord: `join` → your language

**Keep these badges in English:**
- GitHub stats (stars, forks, issues, PRs, contributors, commits)
- Version numbers and release info
- License
- Technical counts (agents, skills, design systems)

---

## 🌍 Regional Terminology

### General Guidelines

Translations follow the conventions of the target region's tech writing community. Maintainers trust contributors to make idiomatic choices and will not gate-keep on style.

**Technical terms to keep in English:**
- Open Design, Claude Code, Claude Design
- Skills, Design Systems
- BYOK (Bring Your Own Key)
- CLI, API, SDK
- Daemon, sidecar
- Monorepo, workspace
- Artifact, iframe
- Git, GitHub, Vercel

**Terms to translate when standard exists:**
- "local-first" → your language's equivalent
- "open-source" → your language's equivalent
- "installation" → your language's equivalent
- "quickstart" → your language's equivalent
- "settings" → your language's equivalent

### French (`fr`) Glossary

French UI copy should read naturally for a technical product audience without
turning product/runtime terms into vague French approximations. Keep these
rules stable across `apps/web/src/i18n/locales/fr.ts`, French core docs, and
French display metadata.

#### Keep in English

Keep the exact English/token form for names, protocols, commands, environment
variables, code identifiers, package names, file extensions, and technical
runtime nouns that are clearer in English:

| English source | French usage |
| -------------- | ------------ |
| Open Design | Open Design |
| Claude Code, Codex, Cursor, Gemini, OpenCode | Claude Code, Codex, Cursor, Gemini, OpenCode |
| CLI, API, SDK, MCP, HTTP, REST, SSE, JSONL | CLI, API, SDK, MCP, HTTP, REST, SSE, JSONL |
| BYOK | BYOK |
| runtime | runtime |
| daemon | daemon |
| sidecar | sidecar |
| headless | headless |
| plugin | plugin |
| prompt | prompt |
| token | token |
| iframe | iframe |
| monorepo, workspace | monorepo, workspace |
| `od`, `pnpm`, `pnpm tools-dev` | `od`, `pnpm`, `pnpm tools-dev` |
| `OD_DATA_DIR`, `OD_WEB_PORT`, `{provider}` | `OD_DATA_DIR`, `OD_WEB_PORT`, `{provider}` |
| `.zip`, `.html`, `.md`, `.json` | `.zip`, `.html`, `.md`, `.json` |

Use French grammar around preserved terms:

- `le daemon local`, `un runtime`, `des plugins`, `les prompts`
- `l’API`, `un endpoint REST`, `un flux SSE`
- `la CLI locale`, `un serveur MCP`

#### Translate When Standard

Translate ordinary UI terms, workflow labels, and non-identifier product copy
when a natural French equivalent exists:

| English source | French |
| -------------- | ------ |
| Settings | Paramètres |
| Save | Enregistrer |
| Cancel | Annuler |
| Delete | Supprimer |
| Folder | Dossier |
| File | Fichier |
| Download | Télécharger |
| Upload | Téléverser |
| Search | Rechercher |
| Preview | Aperçu |
| Project | Projet |
| Conversation | Conversation |
| Dashboard | Tableau de bord |
| Schedule | Planification |
| Automation | Automatisation |
| Artifact | Artefact |
| Live artifact | Artefact dynamique |
| Design files | Fichiers de design |
| Slide deck | Présentation |
| Engineering handoff | Transmission aux ingénieurs |
| Shipped (product/software status) | Livré |

#### Context-Sensitive Choices

- `Skill` stays `Skill` when it names the Open Design/Claude skill format.
  Translate only generic prose such as "ability" or "capability" as
  `capacité`.
- `Design System` may stay `Design System` when referring to the product
  registry/object name. In explanatory prose, `système de design` is also
  acceptable when it improves readability.
- `runtime` stays `runtime` as a noun. Labels like "execution mode" can still
  use `mode d’exécution`.
- `source` can stay `source` for provenance labels, but translate ordinary
  "data source" as `source de données`.
- Do not translate command output or examples that users should see exactly in
  their terminal.

### zh-CN ↔ zh-TW Glossary

When converting between Simplified and Traditional Chinese, prefer Taiwan-specific phrasing in zh-TW rather than character-only conversion. This list grew out of [PR #194](https://github.com/nexu-io/open-design/pull/194) and is meant as a starting point, not a rulebook.

**Tooling:** [OpenCC](https://github.com/BYVoid/OpenCC) with `s2twp.json` handles most core terms automatically. The idiomatic table below is where human review pays off.

#### Core terms (automated by OpenCC)

| English      | zh-CN  | zh-TW   |
| ------------ | ------ | ------- |
| screen       | 屏幕   | 螢幕    |
| stack        | 栈     | 堆疊    |
| project      | 项目   | 專案    |
| software     | 软件   | 軟體    |
| video        | 视频   | 影片    |
| file         | 文件   | 檔案    |
| document     | 文档   | 文件    |
| message      | 信息   | 訊息    |
| network      | 网络   | 網路    |
| database     | 数据库 | 資料庫  |
| user         | 用户   | 使用者  |
| default      | 默认   | 預設    |
| real-time    | 实时   | 即時    |
| install      | 安装   | 安裝    |
| settings     | 设置   | 設定    |
| menu         | 菜单   | 選單    |
| compatible   | 兼容   | 相容    |
| bind         | 绑定   | 綁定    |
| desktop      | 桌面端 | 桌面版  |
| mobile       | 移动端 | 行動版  |

#### Idiomatic / domain-specific (requires human judgment)

These mappings needed human judgment in #194 — OpenCC won't catch them and they're the **most useful to record** because the next translator will hit the same choices:

| English / context        | zh-CN     | zh-TW     |
| ------------------------ | --------- | --------- |
| fallback / safety net    | 兜底      | 備援      |
| bundle / package up      | 捆绑      | 納入      |
| live, dynamic            | 活的      | 動態的    |
| plan (noun)              | 计划      | 計畫      |
| color palette            | 色板      | 色票      |
| spec doc                 | 规范文件  | 規格文件  |
| course-correction        | 介入纠偏  | 介入修正  |
| crash, screw up (slang)  | 翻车      | 出包      |
| go viral (slang)         | 出圈      | 爆紅      |

### Portuguese: pt-BR vs pt-PT

**Brazilian Portuguese (`pt-BR`)** differs significantly from European Portuguese:

| English    | pt-BR      | pt-PT (avoid) |
| ---------- | ---------- | ------------- |
| app        | aplicativo | aplicação     |
| screen     | tela       | ecrã          |
| download   | baixar     | descarregar   |
| mouse      | mouse      | rato          |
| to click   | clicar     | clicar        |

Use Brazilian Portuguese for `pt-BR` translations. If a contributor wants to add European Portuguese, use code `pt-PT`.

### Spanish: `es-ES` (Spain)

The shipped UI locale is **`es-ES`** with label `Español (España)`, so the dictionary and root README target European Spanish. The README filename `README.es.md` is a docs-precedent code that differs from the UI code (see the [adding a new locale](#adding-a-new-locale) step that documents this pattern); both surfaces describe the same Spain Spanish locale.

| English    | es-ES (use)  | Avoid (Latin American) |
| ---------- | ------------ | ---------------------- |
| computer   | ordenador    | computadora (LatAm)    |
| app        | aplicación   | app (anglicism)        |
| to download| descargar    | bajar (informal)       |
| file       | archivo      | fichero (dated Spain)  |
| mobile     | móvil        | celular (LatAm)        |

If a contributor wants neutral or Latin American Spanish, propose a separate locale (e.g. `es-419`) in a follow-up PR — do not drift `es-ES` toward a different regional variant, as the existing `Español (España)` label sets reader expectations.

### Arabic: RTL and Technical Terms

**Arabic (`ar`)** uses Modern Standard Arabic (MSA) understood across all Arabic-speaking regions:

- Use right-to-left (RTL) text direction — **Markdown handles this automatically for `README.*.md` files**
- The **web UI requires manual registration**: append your locale code to `RTL_LOCALES` in [`apps/web/src/i18n/index.tsx`](apps/web/src/i18n/index.tsx) (currently `['ar', 'fa']`), otherwise `<html dir="rtl">` is never set and the UI renders LTR
- Technical terms are often kept in English with Arabic explanation
- Numbers and dates can use Western Arabic numerals (0-9) for technical content
- Keep code blocks and URLs left-to-right

**Example:**
```markdown
Open Design هو البديل مفتوح المصدر لـ Claude Design
```

### Other Languages

Other CJK / RTL glossaries can extend this section as locales mature. Don't pre-emptively fill empty tables — add a row when a contributor hits a real terminology choice that future PRs will face.

---

## ✅ Testing Your Translation

Before submitting your PR, verify:

### 1. Visual Check

Open your translated README in GitHub's preview or a local Markdown viewer:
- ✅ Language switcher displays correctly
- ✅ All links work (no 404s)
- ✅ Images load
- ✅ Code blocks render properly
- ✅ Tables are aligned
- ✅ Badges display
- ✅ RTL text flows correctly (for Arabic, Persian, etc.)

### 2. Link Validation

Check all internal links point to existing files:

```bash
# Example: verify Italian links (translations live in docs/i18n/)
grep -o 'README\.[a-z-]*\.md' docs/i18n/README.it.md | sort -u
grep -o 'QUICKSTART\.[a-z-]*\.md' docs/i18n/README.it.md | sort -u
grep -o 'CONTRIBUTING\.[a-z-]*\.md' docs/i18n/README.it.md | sort -u
```

All linked files should exist in the repository. Sibling translations resolve relative to `docs/i18n/`; English sources resolve via the `../../` prefix. If a translated file doesn't exist yet, link to the English version at `../../`.

### 3. Language Switcher Audit

Verify the language switcher in your new file:
- ✅ Lists all supported languages
- ✅ Current language is bolded: `<b>Italiano</b>`
- ✅ All other languages are links (sibling `<a href="README.es.md">` from a `docs/i18n/` file; `docs/i18n/`-prefixed from the root `README.md`)
- ✅ Links use correct file names (e.g., `README.ja-JP.md` not `README.ja.md`)
- ✅ Order matches the standard order

### 4. Consistency Check

Compare structure with English version:
- ✅ Same number of sections
- ✅ Same heading hierarchy (H1, H2, H3)
- ✅ Same code examples (untranslated)
- ✅ Same images and badges (with translated alt text)
- ✅ No missing or extra content

### 5. Run Automated Checks

```bash
# Type check (if you added UI dictionary)
pnpm typecheck

# i18n structural checks
pnpm i18n:check

# Web package tests (if you added UI dictionary)
pnpm --filter @open-design/web test
```

All checks must pass before submitting your PR.

---

## 📤 Submitting Your Translation

### PR Title Format

```
feat(i18n): add [Language] translation
```

**Examples:**
- `feat(i18n): add Italian translation`
- `feat(i18n): add Swedish translation`
- `feat(i18n): add Vietnamese translation`

### PR Description Template

```markdown
## Summary
Adds [Language] translation for Open Design documentation.

## Translation Scope
- [x] docs/i18n/README.[lang].md
- [ ] docs/i18n/QUICKSTART.[lang].md (optional)
- [ ] docs/i18n/CONTRIBUTING.[lang].md (optional)
- [x] UI dictionary (`apps/web/src/i18n/locales/[lang].ts`)
- [x] Language switcher updated in all existing READMEs

## Files Modified
Updated language switcher in:
- [x] README.md (root)
- [x] docs/i18n/README.ar.md
- [x] docs/i18n/README.de.md
- [x] docs/i18n/README.es.md
- [x] docs/i18n/README.fr.md
- [x] docs/i18n/README.ja-JP.md
- [x] docs/i18n/README.ko.md
- [x] docs/i18n/README.pt-BR.md
- [x] docs/i18n/README.ru.md
- [x] docs/i18n/README.tr.md
- [x] docs/i18n/README.uk.md
- [x] docs/i18n/README.zh-CN.md
- [x] docs/i18n/README.zh-TW.md

## Translation Notes
[Any regional choices, terminology decisions, or context for reviewers]

Example:
- Used neutral Spanish terminology to be understood across all regions
- Kept technical terms like "CLI", "API", "BYOK" in English as they're widely recognized
- Translated "open-source" as "código abierto" (standard term in Spanish tech community)

## Checklist
- [ ] All prose text translated
- [ ] Code snippets kept in English
- [ ] Internal links updated to point to translated files (or English if not available)
- [ ] Language switcher added to new files
- [ ] Language switcher updated in ALL existing README files
- [ ] Badges localized where appropriate
- [ ] Visual preview looks correct
- [ ] All links tested (no 404s)
- [ ] `pnpm typecheck` passes (if UI dictionary added)
- [ ] `pnpm i18n:check` passes
```

### Review Process

**Native-speaker review is strongly preferred but not blocking.** Maintainers may merge a locale PR with a `nit` label if no native speaker has reviewed within ~7 days and CI passes. Subsequent fixes are welcome as separate PRs.

> The 7-day window is a starting point, not a hard policy. Adjust based on your locale's contributor availability and the size of the change.

## 🔄 Maintaining Existing Translations

### When English Content Changes

Translations are **not automatically updated** when the English source changes. This is intentional — we prefer slightly outdated translations over machine-translated ones.

**If you notice outdated content:**
1. Check the English version's recent commits
2. Update the translated sections that changed
3. Submit a PR with title: `fix(i18n): update [Language] translation`

**You are NOT required to:**
- Monitor English changes continuously
- Update translations immediately
- Translate every minor edit

### Maintenance Workflow

When a PR changes English copy, check which surface changed and update the matching translated surfaces deliberately:

- **UI chrome:** Update `apps/web/src/i18n/locales/en.ts` first, then add translated values to active locale dictionaries when the PR owns that refresh. Partial dictionaries may inherit from English with `...en`.
- **README:** Keep language switchers in sync across the root `README.md` and every `docs/i18n/README.*.md`. Check badge counts, Quickstart links, supported agent lists, and release/download links against the English `README.md` during a refresh.
- **Core docs:** Keep translated `docs/i18n/QUICKSTART.*.md` and `docs/i18n/CONTRIBUTING.*.md` aligned with their English source (`QUICKSTART.md`, `CONTRIBUTING.md` in root) when the locale owns those docs.
- **Display metadata:** Update `apps/web/src/i18n/content*.ts` alongside `content.ts` when that locale maintains display metadata.

### Automated Checks

**P0 check (hard-fail in CI):**
```bash
pnpm i18n:check
```

This enforces:
- UI locale registration
- Root README switcher consistency
- Root README links to translated core docs

These are structural issues that must be fixed before merge.

### Known Drift

Several translated READMEs currently lag behind English in:
- Badge counts
- Supported agent lists
- Quickstart/download links

These will be cleaned up in focused PRs. See [Backport policy](#backport-policy) below.

---

## 📋 Backport Policy

When the English README or UI dict gains new sections/keys, contributors are **not required** to backport. The English fallback covers missing keys at runtime. Locale maintainers (volunteers, often the original author) are encouraged to refresh in a follow-up PR.

**Keep refresh PRs focused: one locale per PR, no mixed feature work.**

### Drift Threshold

A locale is considered drifted when **either**:

- **≥20 untranslated UI keys** vs. `en.ts` (today this is checked manually with a key-diff; a CI warning is tracked as a follow-up — see [Open questions](#open-questions)), **or**
- **No refresh PR in 6+ months** while the English README or dict has changed

These are tripwires for moving a locale to **stale** status (below); they're not auto-rejection rules.

### Stale Locales

We don't delete locales. When a locale crosses a drift tripwire above:

1. Add a `⚠️ Stale (last refreshed YYYY-MM)` cell to its row in the [Supported Languages](#-supported-languages) table.
2. Drop a frontmatter comment at the top of the locale's `.ts` file:

   ```typescript
   // ⚠️ Stale: last refreshed 2025-09. See TRANSLATIONS.md.
   export const fr: Dict = { ... };
   ```

3. The locale keeps compiling and rendering — readers still get partially-translated UI, which is better than removing it.

A new contributor can pick it up by submitting a refresh PR; the markers come off when the drift threshold is back under control.

### Partial Translations

It's okay to translate only README initially. Add QUICKSTART and CONTRIBUTING later when you have time.

**Mark partial translations in your PR:**
```markdown
## Translation Status
- [x] docs/i18n/README.it.md (complete)
- [ ] docs/i18n/QUICKSTART.it.md (planned)
- [ ] docs/i18n/CONTRIBUTING.it.md (planned)
```

---

## ❓ FAQ

### Q: Which file should I translate first?

**A:** Always start with `README.md`. It's the first thing users see and has the highest impact. Then add UI dictionary, then QUICKSTART, then CONTRIBUTING.

### Q: Do I need to translate code comments in examples?

**A:** Yes, if they're instructional. No, if they're part of actual code output.

```bash
# English
pnpm tools-dev  # Start the development server

# Italian
pnpm tools-dev  # Avvia il server di sviluppo
```

### Q: Should I translate command output?

**A:** No. Keep terminal output in English as it appears in the actual software.

```bash
# Keep this in English
$ pnpm tools-dev
Starting daemon on port 17456...
Web server running at http://localhost:17573
```

### Q: What if my language doesn't have a word for "open-source"?

**A:** Use the English term with a brief explanation in parentheses on first use:

```markdown
Open Design è un'alternativa open-source (codice aperto) a Claude Design.
```

After the first use, you can use just the English term.

### Q: How do I handle right-to-left (RTL) languages like Arabic?

**README:** Markdown and GitHub automatically handle RTL text direction — just write naturally in your language and keep code blocks / URLs left-to-right.

**UI locale:** The web app does not auto-detect. You must append your locale code to `RTL_LOCALES` in [`apps/web/src/i18n/index.tsx`](apps/web/src/i18n/index.tsx) (currently `['ar', 'fa']`). Without this, the `<html dir="rtl">` attribute is never set and the UI renders LTR regardless of language. See the [detailed steps](#adding-a-new-locale) under step 2.

```markdown
<!-- README: Arabic text flows RTL automatically -->
Open Design هو البديل مفتوح المصدر لـ Claude Design

<!-- Code blocks stay LTR -->
```bash
pnpm tools-dev
```
```

### Q: Can I use machine translation?

**A:** Machine translation as a starting point is fine, but you **must** review and edit it carefully. Native-quality translation is the goal. Reviewers will check for machine-translation artifacts like:
- Unnatural phrasing
- Incorrect technical terms
- Missing context
- Literal translations that don't make sense

### Q: What if I find an error in the English version?

**A:** Fix the English version first in a separate PR, then update translations. Don't propagate errors.

### Q: Should I translate the CHANGELOG?

**A:** No. CHANGELOG stays in English only. It's a technical document for maintainers.

### Q: How do I handle version numbers and dates?

**A:** Keep version numbers in English format (`v1.0.0`). Dates can be localized:
- English: `2026-05-12` or `May 12, 2026`
- Italian: `12 maggio 2026`
- Japanese: `2026年5月12日`
- Spanish: `12 de mayo de 2026`

### Q: What about the language switcher order?

**A:** Follow the standard order shown in [Step 3](#step-3-update-all-language-switchers-critical). New languages go at the end.

### Q: Can I add a language that's not on the list?

**A:** Yes! Follow this guide and submit a PR. We welcome all languages.

### Q: Who reviews translation PRs?

**A:** Ideally a native speaker or fluent reviewer. If no native reviewer is available, maintainers will check structure and merge based on community feedback after ~7 days.

### Q: What if I only want to translate the README, not the UI dictionary?

**A:** That's perfectly fine! README-only translations are valuable. You can add the UI dictionary later, or another contributor can add it.

### Q: How do I know if my translation is good enough?

**A:** Ask yourself:
- Would a native speaker understand this naturally?
- Does it sound like it was written in this language, not translated?
- Are technical terms used correctly?
- Would I be comfortable showing this to my colleagues?

If yes to all, it's good enough!

### Q: Can I update an existing translation that has errors?

**A:** Yes! Submit a PR with title `fix(i18n): improve [Language] translation` and explain what you fixed in the description.

---

## 🆘 Getting Help

- **Questions?** Open a [GitHub Discussion](https://github.com/nexu-io/open-design/discussions)
- **Found an issue?** Open a [GitHub Issue](https://github.com/nexu-io/open-design/issues)
- **Want to chat?** Join our [Discord](https://discord.gg/qhbcCH8Am4)
- **Need a review?** Tag `@nexu-io/maintainers` in your PR

---

## 🎯 Open Questions

Genuinely undecided — flagged so contributors know they're live design discussions:

- **Source-of-truth drift CI.** A `pnpm i18n:diff` script that compares each locale's keys to `en.ts` and warns (not fails) when a locale exceeds the 20-key drift threshold. Tracked as a follow-up after this doc lands.
- **README freshness signal.** A small badge or front-matter timestamp on each `README.<code>.md` could help readers gauge how current a translation is.
- **Native-speaker review window.** Whether `~7 days` is too short for smaller language communities — adjust if real data shows otherwise.

If you have an opinion on any of the above, open an issue or comment on [#195](https://github.com/nexu-io/open-design/issues/195).

---

## 🚧 Deferred Decisions

These items are **decided to defer** — the team has agreed not to act on them now, with rough triggers for revisiting:

- **Translation memory tooling** (Crowdin / Weblate / Lingui). Re-evaluate once the project hits ~12-15 active locales **or** when contributors start visibly duplicating effort across PRs.
- **README template-driven generation** (e.g. [NRG](https://github.com/nanolaba/readme-generator), custom `.src.md` build scripts, All Contributors-style tooling). Re-evaluate once the project hits ≥15 locales **or** README structural edits become more frequent than monthly. Discussion in [#195](https://github.com/nexu-io/open-design/issues/195): template-driven generation solves the "update line 27 in 10 README variants" brittleness, but forces a shared structure that today's locale variants intentionally diverge from (e.g. `README.zh-TW.md`'s "上手體驗" section, the pt-BR / pt-PT precedent for content-level — not just translation-level — differences). Worth revisiting once locale voice is more settled or the manual-update cost grows.

---

## 🙏 Credits

Thank you to all our translation contributors! 🌍

Every translation makes Open Design accessible to more developers worldwide.

**Current contributors:**
- See [Contributors](https://github.com/nexu-io/open-design/graphs/contributors) for the full list

---

**Ready to contribute?** Pick a language, follow the [Quick Start](#-quick-start-adding-your-language-in-5-steps), and submit your PR. We can't wait to see Open Design in your language! 🚀
