Internationalisation¶
AdaptiveLearner runs in 8 languages. Five are first-class (DE, EN, ES, FR, EL) with native translations. Three (PT, TR, JA) ship as English passthroughs — they have all the keys but the values are still English, waiting for native speakers to contribute translations.
Where strings live¶
Backend¶
YAML catalogs in backend/config/i18n/{lang}.yaml — one file
per language. The reference catalog is EN; every other catalog
must have the same key tree.
# backend/config/i18n/en.yaml
common:
save: Save
cancel: Cancel
settings:
title: Settings
section_language: Language
...
The backend exposes GET /api/i18n/{lang} which returns the
whole catalog as JSON. The frontend's useI18n hook calls it
at first paint and caches the result.
Frontend fallbacks¶
frontend/src/i18n/fallbacks.ts carries an inline subset of
strings hard-coded in the frontend bundle. These show on
first paint before the backend catalog has loaded — and as
a permanent fallback if the catalog endpoint returns 5xx.
The fallback resolution chain in useI18n:
- Backend catalog (live strings from
/api/i18n/{lang}). Walk dot-notation path. - Hardcoded frontend fallbacks (first-paint resilience).
- Caller-supplied fallback string.
- The key itself.
In-app help / docs site¶
docs/help/_meta.yaml declares the navigation tree with DE +
EN titles per entry. The in-app help panel reads it directly;
scripts/generate_mkdocs_nav.py regenerates the
mkdocs.yml nav block from the same source.
Help-page Markdown lives in docs/help/{lang}/... — one
folder per language. mkdocs-static-i18n's docs_structure: folder
mode handles the resolution.
Adding a new language¶
- Backend catalog: copy
backend/config/i18n/en.yamltobackend/config/i18n/{newlang}.yaml. Translate every value (or leave as English placeholders for a passthrough). - Frontend constants: add the language code to
SUPPORTED_LANGUAGESinfrontend/src/lib/constants.ts. - Frontend fallbacks: add a
{newlang}: {...}block tofrontend/src/i18n/fallbacks.tswith the small subset of strings the first-paint code path uses. - Assessment questions: edit
plugins/adaptive-learner-plugin-assessment/adaptive_learner_assessment/questions.py— add atext_{newlang}field to everyQUESTIONSentry and every answer. Re-export the JSON:poetry run python plugins/.../questions.py --export-json frontend/src/data/assessment-questions.json(no such CLI exists yet; the script inscripts/would need a small addition). - Session prompts: edit
plugins/adaptive-learner-plugin-session/adaptive_learner_session/prompts.pyto add a third language key to every (method, step) cell. Re-export the JSON the same way. - Docs site: update
mkdocs.yml'splugins.i18n.languagesblock and adddocs/help/{newlang}/directory mirroring the EN structure.
Parity tests¶
backend/tests/test_i18n_parity.py walks every catalog and
asserts that:
- Every language has every key the reference (EN) has.
- No language has an extra key the reference doesn't have.
This catches the most common drift bug: someone adds a key in
EN and forgets to add it in the other 7 files. Run
make test-backend after any catalog change.
Plurals¶
The current catalogs don't use ICU plural syntax. The few counts we render (session count, streak days) get hard-coded form selection in TS:
ICU plural strings via mkdocs-i18n or a runtime ICU library is on the deferred list.
RTL languages¶
Arabic, Hebrew, Persian etc. would need bidirectional layout support in the CSS. The current theme assumes LTR; adding RTL is on the deferred list.
When NOT to add a string¶
If a string only appears in developer-facing surfaces (error
messages thrown by AdaptiveLearnerError, log lines, test
fixture data), leave it English. The i18n machinery is for
end-user-visible strings only.
The frontend's console.warn and console.error calls also
stay English — they're for developers reading the browser
console, not end users.