ADR-031 — i18n language list and rollout waves
Status · Accepted Date · 2026-05-04 RFC anchor ·
docs/rfc/RFC-010.md(closed by this ADR + ADR-032 + ADR-033) Supersedes · — (extends ADR-017's locale-overlay architecture)
Context
ADR-017 locked the i18n architecture (Paraglide-js for UI strings + locale-overlay JSON for editorial content) but left the language selection and rollout sequencing open. RFC-010 explored eleven candidate languages by unioning "world top-N speakers" with "active space-faring nations" and proposed a priority order; this ADR closes that question.
Decision
Eleven supported locales, in this priority order:
| # | Language | Code | Script | Direction | Wave |
|---|---|---|---|---|---|
| 1 | Spanish | es | Latin | LTR | 1 |
| 2 | Mandarin (Simplified) | zh-CN | CJK | LTR | 2 |
| 3 | French | fr | Latin | LTR | 1 |
| 4 | Japanese | ja | CJK + Hiragana/Katakana | LTR | 2 |
| 5 | German | de | Latin | LTR | 1 |
| 6 | Hindi | hi | Devanagari | LTR | 3 |
| 7 | Portuguese (BR) | pt-BR | Latin | LTR | 1 |
| 8 | Arabic | ar | Arabic | RTL | 3 |
| 9 | Korean | ko | Hangul | LTR | 2 |
| 10 | Russian | ru | Cyrillic | LTR | 3 |
| 11 | Italian | it | Latin | LTR | 1 |
Wave structure groups by script-system risk, not priority:
- Wave 1 — Latin script. Zero new infrastructure (existing fonts cover Latin extended). Languages: Spanish, French, German, Portuguese (BR), Italian.
- Wave 2 — CJK. Requires CJK font loading strategy (deferred to a follow-up ADR scheduled with Wave 2). Languages: Mandarin, Japanese, Korean.
- Wave 3 — non-Latin / non-CJK. Mixed script concerns: Devanagari (font only), Cyrillic (font verification), Arabic (RTL CSS audit — deferred to a follow-up ADR scheduled with Wave 3). Languages: Hindi, Arabic, Russian.
Scheduling: v0.3.x ships Spanish only (proof-of-concept; tracked in #17 + foundation in #33 + glossary in #34). Remaining Wave 1 languages move to PLANNED but unscheduled (umbrella tracker #36). Wave 2 (#37) and Wave 3 (#38) are gated on the script-specific follow-up ADRs and remain backlog.
Rationale
Priority order optimises for reach × STEM-audience-engagement × implementation-effort:
- Spanish first — largest new reach for lowest effort; strong Latin-American + Spanish STEM communities; existing infra suffices.
- Mandarin second — China is a Tier-1 Mars/Moon player (CNSA Tianwen, CMSA crewed missions); 1.1B speakers; the CJK font work pays off across three languages (Wave 2).
- The wave-by-script grouping decouples priority from infra readiness: high-priority languages from different waves can be scheduled independently as their gating ADRs land.
The eleven-language list is bounded — RFC-010 considered Hebrew, Dutch, and others but excluded them on the priority×reach criterion; this ADR doesn't preclude future additions but makes them an explicit ADR superseding event.
Alternatives considered
- Ship all eleven at once — maximises reach in one milestone; risk is uneven quality across languages and a long blocking critical path on the slowest gating ADR; rejected.
- Ship only Wave 1 ever — simplifies infra forever but excludes ~3B speakers (CJK + Hindi + Arabic + Russian); rejected on reach grounds.
- Pure community-contribution model with no priority list — open
translations/and let contributors fill any language; rejected because cold-start needs a defined first language and a glossary, and quality-control without a priority order leaves orphan locales.
Consequences
Positive: clear priority order means each language ticket has unambiguous "next up" status; wave grouping isolates infra work; v0.3.x scope stays tight (Spanish only); future language additions are an explicit ADR-supersession event, not implicit drift.
Negative: every Wave 2 / Wave 3 language is gated on a not-yet-written follow-up ADR (CJK font strategy; RTL CSS strategy); umbrella trackers (#36 / #37 / #38) need active maintenance as per-language tickets spawn off them.
Implementation notes
src/lib/locale.ts(created by #33) maintains the supported-locale set; this ADR is the source of truth for what "supported" means.<LocalePicker>(created by #33) sources the dropdown options from the same set.- Per-language tickets get filed off the relevant umbrella tracker (#36/#37/#38) when each is scheduled, modelled on #17.