Quick start for contributors: This guide helps you add a new language translation to OpenDesign 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. 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.
New to translation contributions? Start here. This checklist covers the 80% case.
Pick a standard code:
- Two-letter for most languages:
de,fr,sv,vi - Regional variants when needed:
pt-BR,zh-CN,zh-TW,es-ES - Use hyphens, not underscores:
zh-CN✅ notzh_CN❌
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).
# Copy and translate
cp README.md docs/i18n/README.sv.md
# Edit docs/i18n/README.sv.md in your editorWhat to translate:
- ✅ All text, headings, descriptions
- ✅ Alt text:
alt="OpenDesign banner" - ✅ Link text:
[Quickstart](../../QUICKSTART.md)→[Snabbstart](QUICKSTART.sv.md)(paths are relative todocs/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: "OpenDesign", "Claude Code"
- ❌ Technical terms: CLI, API, BYOK, daemon
This is the most commonly forgotten step. You must update the language switcher in:
- Your new
docs/i18n/README.sv.md(bold your language) - Every existing README — the English
README.mdin the repo root and everydocs/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— boldEnglish; link translations with thedocs/i18n/prefix:<p align="center"><b>English</b> · <a href="docs/i18n/README.es.md">Español</a> · ... · <a href="docs/i18n/README.sv.md">Svenska</a></p>
- Translated
docs/i18n/README.xx.md— bold your own language; link English with../../README.mdand the other translations as sibling filenames:<p align="center"><a href="../../README.md">English</a> · <a href="README.es.md">Español</a> · ... · <b>Svenska</b></p>
Files to update: README.md at the repository root and every file returned by git ls-files 'docs/i18n/README.*.md'. Do not maintain a copied filename list here; pnpm i18n:check verifies that every switcher has the same set.
Create apps/web/src/i18n/locales/sv.ts by copying en.ts, then translate every value while preserving every key and placeholder. Locale dictionaries are complete, explicit Dict implementations; do not use ...en to hide missing keys.
Note: The
Dicttype andapps/web/tests/i18n/locales.test.tsenforce alignment withen.ts, including placeholder names. The runtime's English fallback is defensive compatibility behavior, not permission to ship a partial dictionary.
Then register it in apps/web/src/i18n/index.tsx and apps/web/src/i18n/types.ts (see detailed steps below).
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.sv).toBe('Svenska');). Run pnpm --filter @open-design/web test to verify.
# Type check
pnpm typecheck
# Run i18n checks
pnpm i18n:check
# Visual check: open your docs/i18n/README.sv.md in GitHub preview
# Verify all links work, images load, language switcher displays correctlyPR title: feat(i18n): add Swedish translation
PR checklist:
- README translated
- Language switcher updated in ALL existing READMEs
- UI dictionary added (if applicable)
- All links tested
-
pnpm i18n:checkpasses
OpenDesign 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 rootREADME.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 rootQUICKSTART.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.
- UI dictionaries:
apps/web/src/i18n/locales/ - English sources:
README.md,QUICKSTART.md,CONTRIBUTING.md,MAINTAINERS.mdstay in the project root - Translated docs:
docs/i18n/holds everyREADME.{lang}.md,QUICKSTART.{lang}.md,CONTRIBUTING.{lang}.md, andMAINTAINERS.{lang}.md - Display metadata:
apps/web/src/i18n/content*.ts(optional, for gallery/examples)
The LOCALES array in 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.
For UI dictionary + README translation:
-
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-BRand a hypotheticalpt-PTwould coexist as separate locales — the same precedent applies toen-US/en-GBif a contributor wants to maintain both. -
Update
apps/web/src/i18n/types.ts:- Extend the
Localeunion with your code - Append your code to the
LOCALESarray - Add a
LOCALE_LABEL[<code>]entry — use the native name of the language (Svenska,日本語, notsv,ja)
export type Locale = 'en' | 'de' | 'fr' | 'sv' | /* ... */; export const LOCALES: Locale[] = ['en', 'de', 'fr', 'sv', /* ... */]; export const LOCALE_LABEL: Record<Locale, string> = { en: 'English', de: 'Deutsch', fr: 'Français', sv: 'Svenska', // ... };
Then update test fixtures: In
apps/web/tests/i18n/locales.test.ts, add your locale to theEXPECTED_LOCALESarray and add aLOCALE_LABELassertion:const EXPECTED_LOCALES = ['en', 'id', 'de', /* ... */, 'sv', /* ... */]; // In the test body: expect(LOCALE_LABEL.sv).toBe('Svenska');
If your locale is RTL (Arabic, Hebrew, Persian, Urdu, etc.): also append your code to
RTL_LOCALESinapps/web/src/i18n/index.tsx. This array controls thedir="rtl"attribute on<html>at runtime — without it the web UI renders LTR regardless of language. The current list is:const RTL_LOCALES: Locale[] = ['ar', 'fa'];
- Extend the
-
Create the dictionary at
apps/web/src/i18n/locales/<code>.ts:- Copy from
en.tsand translate the values - Declare every key explicitly; keys and placeholder names must match
en.tsexactly - Do not spread
eninto a partial locale. Typecheck and the locale test are intended to expose omissions at review time - The translator still has an English fallback as a defensive runtime boundary, but maintained dictionaries must not depend on it
- Copy from
-
Register your dictionary in
apps/web/src/i18n/index.tsx:import { sv } from './locales/sv'; // ... const DICTS: Record<Locale, Dict> = { en, de, fr, sv, // Add your locale here // ... };
-
Translate the README:
- Copy
README.mdtodocs/i18n/README.<code>.md(translations live underdocs/i18n/; the EnglishREADME.mdis 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.mdwith UI localeja, orREADME.es.mdwith UI localees-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 EnglishREADME.md, etc.) need a../../prefix; links to a sibling translated core doc stay as a bare filename. Example:[Quickstart](../../QUICKSTART.md)→[Snabbstart](QUICKSTART.sv.md)if that translation exists, else[Snabbstart](../../QUICKSTART.md)
- Copy
-
Update the language switcher in EVERY README — the root
README.mdand everydocs/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>Svenska</b> - The link form differs by file location (see below)
English root
README.md— boldEnglish, link translations with thedocs/i18n/prefix. Copy the current switcher fromREADME.md, append the newdocs/i18n/README.<code>.mdlink, and keep English bold. The checked-in switcher is the source to copy; do not copy a hard-coded language list from this guide.Translated
docs/i18n/README.<code>.md— link English with../../README.md, the other translations as sibling filenames, bold your own. Copy a current translated switcher, add the new locale, and bold the new language.pnpm i18n:checkvalidates the result. -
(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 indocs/i18n/stay as bare sibling filenames - Update links from the translated README to the translated core docs
- Copy
-
(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
-
Run checks:
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
What to translate:
- ✅ All prose text, headings, descriptions
- ✅ Alt text in images:
alt="OpenDesign banner"→alt="Banner di OpenDesign" - ✅ Badge labels where appropriate:
discord-join→discord-unisciti - ✅ Code comments in examples (if instructional)
- ✅ Link text:
[Quickstart](../../QUICKSTART.md)→[Snabbstart](QUICKSTART.sv.md)(sibling translation indocs/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: "OpenDesign", "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:
OpenDesign è 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 section for specific glossaries
Some badges in the README can be localized by changing the badge URL:
<!-- English -->
<a href="https://discord.gg/mHAjSMV6gz"><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/mHAjSMV6gz"><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)
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:
- OpenDesign, 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 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 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 |
|---|---|
| OpenDesign | OpenDesign |
| 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 promptsl’API,un endpoint REST,un flux SSEla CLI locale,un serveur MCP
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é |
SkillstaysSkillwhen it names the OpenDesign/Claude skill format. Translate only generic prose such as "ability" or "capability" ascapacité.forkstaysforkwhen it names the OpenDesign conversation-fork feature or related product/CLI wording. Translate Git branches asbranche, but do not rewrite the product action itself as a branch.Design Systemmay stayDesign Systemwhen referring to the product registry/object name. In explanatory prose,système de designis also acceptable when it improves readability.CraftstaysCraftwhen it refers to the repository'scraft/extension point or the matching UI label. Do not translate that feature name as a generic polish/finishing pass.SOTA HarnessandHarnessstay in English when they name the OpenDesign product/runtime harness concept or matching marketing label.- Motion-design jargon such as
motion,timing,easing,fallback, andtimelinemay stay in English for compact UI labels or agent-workflow prompts where those terms are the design-domain vocabulary. runtimestaysruntimeas a noun. Labels like "execution mode" can still usemode d’exécution.sourcecan staysourcefor provenance labels, but translate ordinary "data source" assource de données.- Do not translate command output or examples that users should see exactly in their terminal.
- Do not translate copy-paste-safe parser tokens or operators inside UI input
hints. Keep literals such as
kind,limit,scale,selector,columns,maxWidth, andgapexactly when users may paste them into a field.
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 and is meant as a starting point, not a rulebook.
Tooling: OpenCC with s2twp.json handles most core terms automatically. The idiomatic table below is where human review pays off.
| 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 | 移动端 | 行動版 |
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) | 出圈 | 爆紅 |
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.
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 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 (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.*.mdfiles - The web UI requires manual registration: append your locale code to
RTL_LOCALESinapps/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:
OpenDesign هو البديل مفتوح المصدر لـ Claude DesignOther 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.
Before submitting your PR, verify:
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.)
Check all internal links point to existing files:
# Example: verify Swedish links (translations live in docs/i18n/)
grep -o 'README\.[a-z-]*\.md' docs/i18n/README.sv.md | sort -u
grep -o 'QUICKSTART\.[a-z-]*\.md' docs/i18n/README.sv.md | sort -u
grep -o 'CONTRIBUTING\.[a-z-]*\.md' docs/i18n/README.sv.md | sort -uAll 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 ../../.
Verify the language switcher in your new file:
- ✅ Lists all supported languages
- ✅ Current language is bolded:
<b>Svenska</b> - ✅ All other languages are links (sibling
<a href="README.es.md">from adocs/i18n/file;docs/i18n/-prefixed from the rootREADME.md) - ✅ Links use correct file names (e.g.,
README.ja-JP.mdnotREADME.ja.md) - ✅ Order matches the standard order
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
# 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 testAll checks must pass before submitting your PR.
feat(i18n): add [Language] translation
Examples:
feat(i18n): add Swedish translationfeat(i18n): add Vietnamese translation
## Summary
Adds [Language] translation for OpenDesign 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] Every existing translation returned by `git ls-files 'docs/i18n/README.*.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` passesNative-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.
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:
- Check the English version's recent commits
- Update the translated sections that changed
- 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
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.tsfirst, then add the same explicit keys and matching placeholders to every locale dictionary. Do not use...ento make an incomplete dictionary appear complete. - README: Keep language switchers in sync across the root
README.mdand everydocs/i18n/README.*.md. Check badge counts, Quickstart links, supported agent lists, and release/download links against the EnglishREADME.mdduring a refresh. - Core docs: Keep translated
docs/i18n/QUICKSTART.*.mdanddocs/i18n/CONTRIBUTING.*.mdaligned with their English source (QUICKSTART.md,CONTRIBUTING.mdin root) when the locale owns those docs. - Display metadata: Update
apps/web/src/i18n/content*.tsalongsidecontent.tswhen that locale maintains display metadata.
P0 check (hard-fail in CI):
pnpm i18n:checkThis 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.
The locale test verifies exact key and placeholder parity across every registered dictionary. Individual values still require human language review; structural parity is not proof of translation quality.
When the English README gains new sections, locale maintainers may refresh prose in a focused follow-up. UI dictionary keys are different: every registered locale must add every new key in the same change so typecheck and apps/web/tests/i18n/locales.test.ts continue to pass. The runtime fallback is a defensive boundary, not the maintenance policy.
Keep refresh PRs focused: one locale per PR, no mixed feature work.
- Missing or extra UI keys and placeholder mismatches are hard failures, not a stale-status threshold.
- English values accidentally copied into a locale are semantic translation defects. Fix them when found and add a focused assertion for high-risk surfaces when useful.
- README and core-doc prose can still drift independently because those documents are maintained manually. Compare the locale with its English source and use code/history evidence before changing technical claims.
It's okay to translate only README initially. Add QUICKSTART and CONTRIBUTING later when you have time.
Mark partial translations in your PR:
## Translation Status
- [x] docs/i18n/README.sv.md (complete)
- [ ] docs/i18n/QUICKSTART.sv.md (planned)
- [ ] docs/i18n/CONTRIBUTING.sv.md (planned)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.
A: Yes, if they're instructional. No, if they're part of actual code output.
# English
pnpm tools-dev # Start the development server
# Italian
pnpm tools-dev # Avvia il server di sviluppoA: No. Keep terminal output in English as it appears in the actual software.
# Keep this in English
$ pnpm tools-dev
Starting daemon on port 17456...
Web server running at http://localhost:17573A: Use the English term with a brief explanation in parentheses on first use:
OpenDesign è un'alternativa open-source (codice aperto) a Claude Design.After the first use, you can use just the English term.
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 auto-detects supported OS/browser languages and preserves an explicit user selection. Directionality is registered separately: append a new RTL locale to RTL_LOCALES in apps/web/src/i18n/index.tsx (currently ['ar', 'fa']) so <html dir="rtl"> is set. See the detailed steps under step 2.
<!-- README: Arabic text flows RTL automatically -->
OpenDesign هو البديل مفتوح المصدر لـ 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/mHAjSMV6gz)
- **Need a review?** Tag `@nexu-io/maintainers` in your PR
---
## 🎯 Open Questions
Genuinely undecided — flagged so contributors know they're live design discussions:
- **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 remain deferred despite the repository now carrying 19 UI locales; crossing the old locale-count triggers did not itself adopt a tool or generation contract. Either change needs an explicit maintainer decision:
- **Translation memory tooling** (Crowdin / Weblate / Lingui).
- **README template-driven generation** (e.g. [NRG](https://github.com/nanolaba/readme-generator), custom `.src.md` build scripts, All Contributors-style tooling). Discussion in [#195](https://github.com/nexu-io/open-design/issues/195) captures the tradeoff between switcher maintenance and locale-specific structure.
---
## 🙏 Credits
Thank you to all our translation contributors! 🌍
Every translation makes OpenDesign 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 OpenDesign in your language! 🚀