Locales
How the programme is translated, published, and served in more than one language, and which locales are done. See locales-by-priority.md for the order of work, and ../locales-for-global-sharing-with-svelte/ for the general guidance this project follows.
Status
| Locale | Name | Direction | Status | Notes |
|---|---|---|---|---|
| en-001 | English | ltr | Done | Source language |
| cy-001 | Cymraeg (y byd) | ltr | Not started | Next in priority order; translation stopped on 2026-10-07 |
| cy-gb | Cymraeg | ltr | Not started | To start as a copy of cy-001, for Welsh readers in Great Britain |
Any AI-generated translation needs a native speaker review; ../../tasks.md tracks each review.
Implementation status
This file is the contract for adding locales. Today only part of it is built:
| Part | Status |
|---|---|
| Locale codes, directory names, peer ids | Built: scripts/locales.py places, relinks, copies, and checks translations in locales/<code>/ |
Website at /<locale>/<slug>/, en-001 only, with lang and dir from the BCP 47 tag |
Built |
Website serving more than one locale: translated documents, untranslated fallback, alternates and hreflang, the picker switching to the same page |
Not built |
| Home page redirect by browser language, and the stored picker choice | Not built: / always goes to /en-001/ |
Interface strings for another locale (chrome.ts, page messages, assessment.ts) |
Not built: strings are English only, and assessment.ts does not exist yet |
Localised instruments (instruments/locales/<code>/, build_instrument.py --locale) |
Not built |
Build the parts marked "Not built" before publishing the first translated locale.
Locale codes
A locale code is <language>-<region>, in lowercase: a two- or three-letter ISO 639 language code, a hyphen, then a two-letter ISO 3166 region code or the three-digit UN M49 code 001 for the world. For example cy-gb (Welsh in Great Britain) and cy-001 (Welsh for readers anywhere).
- Prefer the world locale,
<language>-001. A regional locale, such ascy-gb, may start as a copy of its world locale, kept in step with it, so that readers in that region get a matching URL and language tag. - Never use a bare language code, such as
en, as a locale code, a directory name, or a URL path. - The region code must be a real ISO 3166 code for a place where the language is spoken.
- The default locale is
en-001.
A locale code is not the same as a language tag. Each locale also has a BCP 47 tag, in LOCALE_META in manual-testing-to-automatic-testing.github.io/src/lib/i18n/locales.ts, for <html lang>, hreflang, and screen readers: en, cy, cy-GB. Use the tag, not the code, wherever a browser or search engine reads the language.
Directory names
All locale directories use the format <language>-<region>. A bare language code, such as en/, is not allowed.
| Directory | Holds | Written by |
|---|---|---|
locales/<code>/ |
The translated Markdown documents, with translated folder names (source of truth for that locale) | Translators, through scripts/locales.py |
instruments/locales/<code>/ |
The capability self-assessment for each track, in that locale | scripts/build_instrument.py, from the roles-skills reference's locale |
manual-testing-to-automatic-testing.github.io/content/locales/<code>/ |
Copies of the above, for the website | bin/sync |
scripts/locales.py check rejects any other name.
Documents and peer ids
English documents are the monorepo's own Markdown files, at their own paths, such as plan.md and materials/gates/calibration-guide.md.
Each other locale's version of a document is a directory in locales/<code>/, with a translated name, holding:
index.md: the translationREADME.md: a symlink toindex.md.locale-peer-id: a 32-character lowercase hexadecimal id and a newline.
The peer id is the MD5 of doc: followed by the English document's path, for example doc:materials/gates/calibration-guide.md. It is identical in every locale's version of the same document, whatever its slug, and it is how the website resolves "this page, in locale X". The translation of the root README.md is locales/<code>/index.md, with its own peer id file in locales/<code>/.
Translated folder names are slugs made from the translated titles, lower case, with hyphens, keeping accented and non-Latin letters. Ids that are codes stay as they are in every locale: track ids (band-5-quality-assurance), module ids (m4), and item ids (Part A item 1).
Working with translations
# Place a translation: writes locales/<code>/<dir>/index.md, README.md, and
# .locale-peer-id, and rewrites relative links from English paths to this
# locale's paths (or to the English document where there is no translation).
python3 scripts/locales.py place cy-001 materials/gates/calibration-guide.md deunyddiau/gatiau/canllaw-graddnodi translation.md
# Start a regional locale as a copy of its world locale.
python3 scripts/locales.py copy cy-001 cy-gb
# Check every locale: names, files, peer ids, links, and what is missing.
python3 scripts/locales.py check
Write a translation with its links exactly as they are in the English source; place rewrites them.
What is translated
- Translated: every document's prose, headings, tables, and lists; the website's interface strings; the band outlines, job evaluation factors, health care skills, and reference responsibilities in the self-assessment, from the roles-skills reference's translation.
- Never translated: quotations from the UK GDaD PCF (role and role level statements, UK GDaD PCF skill names and level descriptions), which stay in English as quotations; product and tool names (Selenium, Mocha, Node.js, HAPI FHIR, GitHub); code, commands, file names, and paths; ids (
Band 5 quality assurance,Learning outcome 4,Evidence 16,Decision 7,Part A item 17); and the rating codes in exported TSV files (Meets,Partly,Not yet), so thatscripts/capability_index.pyreads every locale's export.
URLs
The URL carries the locale, as in testingexamples.github.io.
- Every page is under
/<code>/, including the default:/en-001/spec/,/cy-001/manyleb/. - A document's slug is its directory in
locales/<code>/. A document with no translation yet is served at its English slug under the locale, with its English text, marked as English (lang="en") and with a notice. - The website's own pages use fixed slugs in every locale:
self-assessment/<track>/andsearch/, with translated content. - A bare-language path such as
/cy/or/en/is a 404.
Home page redirect
On a visit to the bare home page, /, the site goes to the published locale that best matches the browser's languages (navigator.languages):
- The exact locale code, such as
cy-GBto/cy-gb/. - The language's world locale, such as
cyto/cy-001/. - Any locale of that language.
The first language that matches wins. If none matches, the site goes to /en-001/. Once a reader chooses a language with the picker, the choice is stored in localStorage under manual-testing-to-automatic-testing.locale-chosen, and the home page goes there instead. The matching is browserLocale in src/lib/i18n/locales.ts.
Language picker
- The picker lists every locale in
LOCALES, labelled each in its own language. - It reflects the URL, and navigates to the same page in the chosen locale, using the page's alternates; a page with no peer in that locale goes to that locale's home page.
- It gets a detached target element, so it never writes its raw code to
<html lang>;hooks.server.tsand the layout setlanganddirfrom the BCP 47 tag. - Every page declares its alternates with
<link rel="alternate" hreflang>, plusx-defaultforen-001.
Interface strings
src/lib/i18n/chrome.ts holds the interface strings for the header, footer, pickers, and banner; each page holds its own strings, keyed by locale; and src/lib/components/SelfAssessment.svelte takes its strings from src/lib/i18n/assessment.ts. Each is a typed object per locale, so a missing key fails pnpm check.
Right-to-left locales
A locale with dir: 'rtl' in LOCALE_META gets dir="rtl" on <html>. Check every page layout, table, and picker in a browser before publishing the first one.
Adding a locale
- Translate serially, one locale at a time, without subagents.
- Translate every document into
locales/<code>/withscripts/locales.py place, untilscripts/locales.py checkreports nothing missing. - Build its instruments:
python3 scripts/build_instrument.py --reference <roles-skills locale reference.json> --locale <code> --out instruments/locales/<code>. - Add the code to
LOCALESinsrc/lib/i18n/locale-codes.jsand toLOCALE_META, and its strings tochrome.ts,assessment.ts, and each page's messages. - Run
manual-testing-to-automatic-testing.github.io/bin/syncandmake check. - Check pages in a browser: the locale's home page, a document, search, a self-assessment, and the picker.
- Update this file's status table,
README.md, andtasks.md. - Commit, push, publish with
make github-pages, watch the deploy, and verify the live site. - Stop, and ask before starting the next locale.
View this document on GitHub spec/locales/index.md