Skip to content
Apothem

Internationalization (i18n)

How the Apothem documentation site declares its locale cohort, emits hreflang alternate-language links, and renders right-to-left across twelve languages.

This page documents how the Apothem documentation site handles multiple languages: the locale cohort it declares, the hreflang mechanism it emits, its right-to-left rendering, and how each language's pages are wired into routing.

Decision summary

  • Platform: Next.js App Router + Fumadocs. The site is built from site/. The locale cohort is declared in site/lib/i18n.ts, and the alternate-language link mechanism lives in site/lib/hreflang.ts.
  • Locale cohort: twelve languages. The Modern Dev Cohort of twelve locales is the canonical source of truth, with English as the source locale and each of the eleven targets machine-seeded from the English page set; per-locale human review is in progress, and fallbackLanguage: 'en' covers any page a locale has not yet authored.
  • Per-locale routing: enabled across the cohort. Every locale is wired into routing and reachable at its own URL prefix (/es/, /zh-cn/, …); English is served at the site root.
  • hreflang mechanism: active. Every page emits a <link rel="alternate"> set covering each locale plus an x-default fallback, so search engines see the full alternate-language cluster. The links are resolved per-file from the filesystem, so a page is advertised in a locale only when that locale has it.
  • Right-to-left rendering: active. The shared stylesheets use logical CSS properties throughout, so Arabic pages render correctly under dir="rtl".
  • Translation review: tracked per page. Machine-seeded locale pages are served and carry machineTranslated: true in their frontmatter until a human reviews them; the flag is review-tracking metadata and the page does not display it. English is the fallback for any page a locale lacks. Having 80% of a locale's pages reviewed is a review target, not a gate on publishing.
  • Translation freshness: tracked per page. Each locale page records the English text it was translated from. When the English page changes later, the translated page shows a one-line note linking the English page, which stays authoritative.

The locale cohort

The Modern Dev Cohort declares twelve locales. English is served at the site root; the eleven targets are each machine-seeded from the English page set at their own URL prefix, with fallbackLanguage: 'en' covering any not-yet-authored page.

CodePathLanguageDirection
en/EnglishLTR
zh-CN/zh-cn/Simplified ChineseLTR
es/es/SpanishLTR
pt-BR/pt-br/Brazilian PortugueseLTR
fr/fr/FrenchLTR
de/de/GermanLTR
ja/ja/JapaneseLTR
ko/ko/KoreanLTR
ru/ru/RussianLTR
id/id/IndonesianLTR
ar/ar/ArabicRTL
hi/hi/HindiLTR

The cohort is the single source of truth; amendments to it route through a deliberate decision, never a silent edit.

How hreflang works

site/lib/hreflang.ts builds the alternates.languages map that Next.js renders into each page's <head>. Its construction honors one rule: a locale's alternate-language link is emitted only when that locale has authored that page, resolved per-file from the filesystem (site/lib/translated-pages.ts). A fully-translated page therefore emits the complete cluster:

<link rel="alternate" hreflang="en" href="https://apothem.ahmedgad.com/..." />
<link rel="alternate" hreflang="es" href="https://apothem.ahmedgad.com/es/..." />
<link rel="alternate" hreflang="ar" href="https://apothem.ahmedgad.com/ar/..." dir="rtl" />
<!-- … one per cohort locale … -->
<link rel="alternate" hreflang="x-default" href="https://apothem.ahmedgad.com/..." />

The page's canonical link is always its own URL — it never points across locales.

Right-to-left rendering

Arabic is the cohort's right-to-left locale, and the site renders it correctly:

  • Logical CSS properties only. The shared stylesheets and components use logical properties (margin-inline-start, padding-inline-end, text-align: start, inset-inline-start) rather than physical ones (margin-left, text-align: left). Logical properties flip automatically under dir="rtl", so the same stylesheet serves both directions correctly.
  • Direction attribute. Arabic pages set dir="rtl" on the <html> element alongside lang="ar".
  • Bidirectional content. Mixed-direction strings — for example an English command inside an Arabic paragraph — are wrapped in <bdi> so the embedded left-to-right run renders correctly within the right-to-left flow.

Adding or updating a locale

The cohort and routing are driven by site/lib/i18n.ts:

  1. Author the page under content/docs/<locale>/ mirroring the English source.
  2. Record the English text it translates: from site/, run node scripts/translation-sources.mjs --stamp <locale>/<path>.mdx, which writes the English page's hash into the locale page's sourceHash frontmatter.
  3. Confirm the locale is listed in ROUTED_LOCALES (all twelve cohort locales are routed today).
  4. Its hreflang link and routes resolve automatically from the filesystem.

Stale translations

A locale page's sourceHash is the hash of the English page it was translated from, covering the English frontmatter and prose but not the generated tables the reference generator rewrites in every language at once. At build time the site hashes the current English page; when the two differ, the translated page shows a note that the English page has changed since the translation was made, with a link to it. Run node scripts/translation-sources.mjs from site/ to list the stale pages per locale, then refresh each translation and stamp it as in step 2 above. A page a locale has not translated at all is served from the English source, marked as English text.

English remains the source of truth; each locale page mirrors its English counterpart's structure, links, and code samples verbatim.

On this page