# 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 ```bash # Copy and translate cp README.md README.it.md # Edit 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)` (if that file exists; otherwise keep `QUICKSTART.md` target) **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 `README.it.md` (bold your language) 2. **Every existing `README.*.md` file** (add your language as a link) Find the line that looks like this (around line 30): ```html
English · Español · ... · Italiano
``` **Files to update:** `README.md`, `README.ar.md`, `README.de.md`, `README.es.md`, `README.fr.md`, `README.ja-JP.md`, `README.ko.md`, `README.pt-BR.md`, `README.ru.md`, `README.tr.md`, `README.uk.md`, `README.zh-CN.md`, `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 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**: Root documentation (`README.{lang}.md`) - **UI Dict**: Web interface strings (`apps/web/src/i18n/locales/{lang}.ts`) - **Core Docs**: `QUICKSTART.{lang}.md`, `CONTRIBUTING.{lang}.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/) - **Root READMEs**: Beside [`README.md`](README.md) in project root - **Core docs**: Beside [`QUICKSTART.md`](QUICKSTART.md) and [`CONTRIBUTING.md`](CONTRIBUTING.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. Root README language switchers cover every locale that has a root README; 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[]` 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 = {
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 `` 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/.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 = {
en,
de,
fr,
it, // Add your locale here
// ...
};
```
5. **Translate the root README:**
- Copy `README.md` to `README..md`
- 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
- Update internal links: `[Quickstart](QUICKSTART.md)` → `[Guida rapida](QUICKSTART.it.md)` (if that file exists)
6. **Update the language switcher in EVERY root README** (line ~30 of each `README*.md`):
- Match the order used in the English README
- Include the same set everywhere
- Bold the current language: `Italiano`
- Link to other languages: `Italiano`
**Standard order:**
```html
English · Español · Português (Brasil) · Deutsch · Français · 简体中文 · 繁體中文 · 한국어 · 日本語 · العربية · Русский · Українська · Türkçe · Italiano
```
7. **(Optional) Translate core docs:**
- Copy `QUICKSTART.md` → `QUICKSTART..md`
- Copy `CONTRIBUTING.md` → `CONTRIBUTING..md`
- Follow existing examples: `QUICKSTART.fr.md`, `CONTRIBUTING.pt-BR.md`, `CONTRIBUTING.ja-JP.md`
- 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)` (if that file exists; otherwise keep `QUICKSTART.md` target)
**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
```
**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 `` 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
grep -o 'README\.[a-z-]*\.md' README.it.md | sort -u
grep -o 'QUICKSTART\.[a-z-]*\.md' README.it.md | sort -u
grep -o 'CONTRIBUTING\.[a-z-]*\.md' README.it.md | sort -u
```
All linked files should exist in the repository. If a translated file doesn't exist yet, link to the English version.
### 3. Language Switcher Audit
Verify the language switcher in your new file:
- ✅ Lists all supported languages (13+)
- ✅ Current language is bolded: `Italiano`
- ✅ All other languages are links: `Italiano`
- ✅ 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] README.[lang].md
- [ ] QUICKSTART.[lang].md (optional)
- [ ] 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
- [x] README.ar.md
- [x] README.de.md
- [x] README.es.md
- [x] README.fr.md
- [x] README.ja-JP.md
- [x] README.ko.md
- [x] README.pt-BR.md
- [x] README.ru.md
- [x] README.tr.md
- [x] README.uk.md
- [x] README.zh-CN.md
- [x] 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`.
- **Root README:** Keep root README language switchers in sync across all root `README*.md` files. Check badge counts, Quickstart links, supported agent lists, and release/download links against `README.md` during a refresh.
- **Core docs:** Keep translated `QUICKSTART.*.md` and `CONTRIBUTING.*.md` aligned with their English source 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] README.it.md (complete)
- [ ] QUICKSTART.it.md (planned)
- [ ] 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 `` attribute is never set and the UI renders LTR regardless of language. See the [detailed steps](#adding-a-new-locale) under step 2.
```markdown
Open Design هو البديل مفتوح المصدر لـ Claude Design
```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..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! 🚀