Skip to content

ADR-017 — Paraglide-js for i18n, locale overlay architecture for content

Status · Accepted Date · 2026-04-28 TA anchor · §stack · §contracts

Context

Orrery targets a global audience. English (US) is the initial locale but the architecture must support translation of both UI chrome and all mission/editorial content. The i18n architecture must not require modifying existing mission JSON files to add a new language.

Decision

Two-part i18n architecture:

1. UI chrome — Paraglide-js. Compile-time i18n for all UI strings (nav labels, button text, HUD labels, tab names, status badges, error messages). Strings live in src/lib/i18n/en-US.json. Each language adds a new file. Paraglide tree-shakes unused locale strings at build time — no runtime overhead for the active locale.

2. Mission and editorial content — locale overlay files. Base mission files (data/missions/mars/curiosity.json) contain only language-neutral data: dates, delta-v, agency, links, orbital elements. Language-specific content lives in separate overlay files:

data/i18n/en-US/missions/mars/curiosity.json   ← name, description, first, type, events[].note
data/i18n/fr/missions/mars/curiosity.json
data/i18n/es/missions/mars/curiosity.json

The data client merges base + locale overlay at fetch time. Missing translations fall back to en-US.

Rationale

Paraglide is the SvelteKit-native i18n library — it integrates directly with SvelteKit's routing and compiles translations at build time. The locale overlay architecture keeps base data files clean and language-independent: adding a new language means adding new files, not modifying the 28 existing mission files. A translator can work entirely in data/i18n/[locale]/ without touching any JavaScript or base data.

Alternatives considered

  • svelte-i18n — runtime i18n; larger bundle; less type-safe; rejected.
  • Embedded language keys in mission JSON ("description_en", "description_fr") — simpler structure; requires editing all 28 files per new language; rejected.
  • Single locale file per language — one big fr.json with all content; hard to maintain; rejected.

Consequences

Positive: adding a language requires zero code changes; translators work in JSON files; UI strings tree-shaken at build; base mission data is language-independent. Negative: data client merge step adds a small amount of complexity; locale overlay files must be kept in sync with base files when mission data changes.

Implementation notes

src/lib/i18n/ for UI strings. data/i18n/[locale]/ for content overlays. Data client gains a locale parameter, defaults to en-US, falls back to en-US for missing translations. SvelteKit layout reads locale from URL prefix (/fr/explore) or browser Accept-Language header.

Orrery — architecture documentation · MIT · No tracking