Locales for major projects with SvelteKit
Translate content into multiple locales.
How this site supports multiple locales end to end: content, web routing, UI chrome, and bugs.
Read languages via file locales.tsv. It lists ISO 639-1 language codes with
their endonyms; a locale code adds a region to one of them, as
<language>-<region>, such as cy-gb or zh-001 (see
../locales/index.md). Never use a bare
language code such as en as a locale code, directory, or URL path.
Locale code priority order: see
../locales/locales-by-priority.md,
starting en-001, cy-001, zh-001, hi-001, es-001, fr-001, ar-001.
In this project
This guidance is general. In this project:
- The full locales spec, with the status of every locale, is
../locales/index.md. - English documents are the monorepo's own Markdown files, at their own paths.
Each other locale's documents are in
locales/<code>/, with translated folder names, anindex.md, aREADME.mdsymlink, and a.locale-peer-idin every document directory.scripts/locales.pyplaces, copies, and checks them. - Locale codes and labels are in
manual-testing-to-automatic-testing.github.io/src/lib/i18n/locale-codes.js(LOCALES,DEFAULT_LOCALE) andsrc/lib/i18n/locales.ts(LOCALE_META,LOCALE_LABELS,localeHref,browserLocale). The picker lists locales inLOCALESorder. - UI chrome strings are in
src/lib/i18n/chrome.ts, one typed message object per locale, so a missing key is a type error. Page strings are in each page's own messages, keyed by locale, as in testingexamples.github.io. - The URL carries the locale: every page is under
/<code>/, includingen-001, as in testingexamples.github.io. The bare home page,/, sends a visitor to the locale matching the browser's languages, unless they have chosen one with the picker.
.locale-peer.id file
.locale-peer-id file is a byte-identical 32-character hexadecimal lowercase
number then newline, across every locale's version of "the same" topic,
regardless of slug.
.locale-peer-id id is how the project resolves "this page, in locale X".
Guidance
en-us: consistent American spelling; fix any stray en-gb forms (organisation→organization, licence→license, programme→program, cancelled→canceled, analogue→analog).
en-gb: the -ize/-ise family (optimise, realise, organise, prioritise, utilise, etc.), -or/-our (colour, behaviour, favour, labour, neighbours), -er/-re (centre, theatre for the metaphorical sense), -ense/-ce (defence, licence), doubled-L forms (modelled, labelled, cancelled, enrol/enrolment), analogue, programme, and math→maths.
en-gb-oxendict: use en-gb then revert just the -ise family back to Oxford -ize spelling (optimize, realise→realize, organise→organize, etc.), while correctly keeping -yse forms (analyse/analysable) unchanged, since Oxford style never uses -yze, and keeping all other British forms (colour, centre, defence, licence, programme, maths, modelled) intact.
Guard against corruption
Keep proper nouns unconverted. Example: "Hospital Readmissions Reduction Program" (a real United States federal program name).
Verify
For each locale subdirectory:
- File exists:
index.md - Symlink exists:
README.md - Locale peer id tracking file exists:
.locale-peer-id
Then:
- Fix any broken internal links
- Fix any residual wrong-dialect spellings
- Update
./spec/locales/index.md
Content structure (book side)
Each locale is locales/<code>/ in the book repo, containing:
locales/<code>/topics/<slug>/index.md+.locale-peer-id— one per topic.README.mdis a symlink toindex.md.locales/<code>/index.md+.locale-peer-id+README.mdsymlink — the locale's own translated README (site home/contents page source). Every locale gets this file scaffolded (matching the topic-file pattern) even before it has a translation; it starts empty.
Slugs
Slugs are per-locale, not shared.** Translated locales rename topic directories to native-script/accented slugs.
Example: es-001 año-de-vida-ajustado-por-calidad, ur-001 صحت-ایڈجسٹڈ-متوقع-زندگی.
Nothing in the site assumes slugs match across locales.
Locale picker (labels + ordering)
- Labels live in
locales.js'sLOCALE_LABELS, one entry per code, in that language (e.g.'fr-001': 'Français (Monde)'). Falls back to the raw code vialocaleLabel()if a code has no label yet. - Header
PickerBarorder comes fromcontent.js'slocales()(sorted by code) — the-001suffix happens to sort before any letter-starting regional suffix, so variants already come first there. - Home page's locale list (
+page.server.js) sorts explicitly: default locale first, then grouped by language name (label text before the(), with the-001/World variant sorted before its regional siblings within each group, then alphabetically by label. This does NOT fall out of alphabetical-by-label sort on its own (e.g. "España" < "Mundo") — it needs the explicit-001check.
Bug fixes (regression watch-list)
Bug: ASCII-only \w regexes broke every non-Latin/non-accented slug
Bug: matched topic slugs with [\w.-]+ (ASCII word chars only). Any locale with
an accented or native-script slug (Spanish, French, Russian, Chinese, Arabic,
Welsh, Hindi, Bengali, Portuguese, Indonesian, Urdu) silently failed peer-id
resolution and cross-topic links.
Fix by widening the slug capture group to [^/]+.
Bug: Every locale's home/contents page showed canonical English content
Bug: code and content always read a single top-level /README.md for title,
intro, "New here?" picks, part headings, and blurbs — only topic links were
ever localized.
Fix: populate the previously-empty locales/<code>/index.md per locale.
Bug: Link extraction was hardcoded to literal English phrase
Bug: link silently found nothing once the README was translated.
Fix: extract all links from the whole pre-## intro block instead of
regex-matching the English sentence.
Bug: UI chrome was hardcoded English in the .svelte templates
Bug: nav labels, subtitles, page titles, intros, breadcrumbs, topic position, pagination, picker/share labels.
Fix: add i18n.js and threading ui(locale) through every locale-scoped route
and +layout.svelte.
Bug: header/footer brand wordmark stayed English
Bug: wordmark came only from the root (locale-agnostic) +layout.server.js,
which deliberately never picks a locale.
Fix: have locales/[locale]/+layout.server.js supply this locale's own title,
which overrides the root layout's canonical one via SvelteKit's merged
page.data on any route under /locales/<locale>/ — the root picker and
/about/ (no locale in the URL) correctly keep the canonical English title.
View this document on GitHub spec/locales-for-global-sharing-with-svelte/index.md