Files
ResumeSite/docs/spec/I18N_SPEC.md
T
2026-07-03 21:07:25 +02:00

3.8 KiB
Raw Blame History

I18N_SPEC.md

1. Architecture summary

Three i18n concerns, three mechanisms — never mixed:

Concern Mechanism Examples
Routes Astro built-in i18n (defaultLocale en at root, no prefixed) + slug map module /projects/jobtrack/no/prosjekter/jobtrack
UI strings (~60 keys) Typed dictionary modules per locale + t(key) accessor, compile-time checked nav.projects, cv.download, form.error.email, a11y.skipLink
Content (prose, case studies, experience) Content collections with per-locale entries sharing one zod schema projects/en/jobtrack.md + projects/no/jobtrack.md

No runtime i18n library. Locale is resolved from the URL at build time; every page is rendered once per locale into static HTML.

2. Dictionary spec

  • Shape: nested typed object; both locale files implement the same interface → a missing key is a compile error, not a runtime fallback.
  • Keys are semantic (cv.download), never English-sentence keys.
  • Interpolation: simple {param} tokens only; no plural engine (the ~3 plural cases are written out per locale).
  • A11y strings are first-class dictionary entries: skip-link, lang-switch announcement ("Bytt til norsk" labelled in the target language per MICRO_INTERACTIONS), image alt patterns, form validation messages.

3. Content collection localisation

  • Each collection folder splits en/ and no/; entries share the same filename (jobtrack.md) = the join key.
  • One schema validates both locales (DATA_MODEL.md); locale-invariant fields (dates, stack chips, links, image refs, status) live in the EN entry and are inherited by the NO entry at load time (a small content-loader utility merges them) — this is the anti-duplication mechanism: facts exist once, prose exists twice.
  • Section anchors: schema field anchorId per section, per locale (architecture/arkitektur) with the pageId-level mapping used by the language switch to land on the equivalent section (J5).

4. Fallback rules (build-time, never runtime)

  1. Full parity is the shipping rule. CI parity test: every EN content entry has a NO twin and vice versa; every dictionary key present in both.
  2. Temporary asymmetry (a new case study written EN-first): the NO twin must still exist, containing a native-Norwegian summary + link "Full versjon på engelsk" (CONTENT_STRATEGY rule). The schema supports summaryOnly: true for this state; CI allows it but the sitemap/hreflang still emit correctly.
  3. No silent English fallback ever renders on a /no/ page — a Norwegian reader must never hit unexplained English mid-page. (Machine translation is banned outright.)

5. Locale-sensitive rendering rules

  • <html lang>: en-GB / nb-NO per page. Date formatting: 20152023 style is locale-neutral; long dates use locale month names via Intl at build time.
  • Quotes: “ ” (EN) / « » (NO) — enforced in content review, not code.
  • The CV split-button defaults to the current page locale's CV; stored language preference overrides on EN pages only if the user previously chose NO (subtle: preference sets default, page locale is tiebreak).
  • OG/meta per locale (SEO_SPEC). hreflang emitted from the slug map: en, nb, x-default → en.

6. Translation management workflow

  • Author EN and NO in the same PR (content files sit side by side; the parity test enforces it).
  • Norwegian quality gate before launch: native/fluent review pass of all NO content (tracked as a Phase 3 exit criterion; the reviewer reviews markdown files in a PR, which keeps review diffs clean — a deliberate benefit of content-in-git).
  • Glossary (CONTENT_STRATEGY §5.3) lives as a comment header in the NO dictionary file so it's in front of whoever edits translations.
  • No TMS tooling: two locales, one author, git history is the audit trail.