open-design/TRANSLATIONS.md
Zakaria a46764fb1b
Some checks failed
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
first-commit
2026-05-04 14:58:14 -04:00

10 KiB

Translations

Status: living document. Maintainers refine this as the project's i18n needs evolve. Contributions welcome.

For general contribution flow, see CONTRIBUTING.md. The "Localization maintenance" section there documents what gets translated (UI chrome, core docs, display metadata) and what does not (skills, design systems, prompt bodies). This file covers how to add and maintain a locale.

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.

Maintained locales

UI dictionary lives in apps/web/src/i18n/locales/. README translations live at the repo root. The LOCALES array in apps/web/src/i18n/types.ts is the authoritative list for the UI dict; the language switcher in the READMEs covers any locale that has a translated README, dict or no dict.

Code Language UI dict README Status
en English en.ts (source) README.md active
ar العربية ar.ts active
de Deutsch de.ts README.de.md active
es-ES Español (España) es-ES.ts active
fa فارسی fa.ts active
hu Magyar hu.ts active
ja 日本語 ja.ts README.ja-JP.md active
ko 한국어 ko.ts README.ko.md active
pl Polski pl.ts active
pt-BR Português (Brasil) pt-BR.ts active
ru Русский ru.ts README.ru.md active
zh-CN 简体中文 zh-CN.ts README.zh-CN.md active
zh-TW 繁體中文 zh-TW.ts README.zh-TW.md active
fr Français fr.ts README.fr.md active
uk Українська uk.ts README.uk.md active

A locale may ship a UI dict, a README, or both. The two surfaces are independent — adding one without the other is a normal contribution. The English locale is the source of truth; missing UI keys fall back to English at runtime. Locales may also add translated core docs or display-only metadata in apps/web/src/i18n/content*.ts; keep those companion surfaces aligned with the locale's active scope.

Adding a new locale

  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) 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:

    • extend the Locale union
    • append your code to LOCALES
    • add a LOCALE_LABEL[<code>] entry — use the native name of the language (Deutsch, 日本語, not de, ja)
  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.

  4. Register your dictionary in apps/web/src/i18n/index.tsx — both the import and the map entry:

    import { fr } from './locales/fr';
    // ...
    const DICTS: Record<Locale, Dict> = {
      // ...existing entries
      fr,
    };
    
  5. (Optional) Translate the README — copy README.md to README.<code>.md. Use OpenCC s2twp.json for zh-CN ↔ zh-TW; use your judgment elsewhere.

  6. Update the language switcher in every existing README (line ~27 of each README*.md). Match the order used in the existing switcher line and append your new locale at the end. The set of switcher links is the union of UI-dict locales and README-only locales — it is not identical to LOCALES. The constraint is simply that every switcher across all READMEs must list the same set in the same order, otherwise readers hit dead ends when jumping between languages.

  7. Run pnpm typecheck to confirm the union and DICTS map agree.

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 Deferred decisions), 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 maintained-locales table.

  2. Drop a frontmatter comment at the top of the locale's .ts file:

    // ⚠️ 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.

Regional terminology

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.

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 and is meant as a starting point, not a rulebook.

Core terms

Easy mappings — most appear in OpenCC's s2twp.json and require no human judgment:

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

Mappings that 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) 出圈 爆紅

Tooling: OpenCC with s2twp.json handles roughly the Core terms automatically. The Idiomatic table is where the human review pays off — start there when adapting an existing zh-CN translation.

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.

Native-speaker review

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.

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.

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.