# Extending i18n (UI translations) > 🇫🇷 [Version française](extending-i18n.md) Picarones is bilingual French / English by default (Sprint 17). This guide covers: 1. Adding a new key (label, button text, error message). 2. Adding a third language. ## Architecture Two JSON files store all UI strings: - `picarones/report/i18n/fr.json` — French (canonical) - `picarones/report/i18n/en.json` — English Both files **must have the exact same set of keys** — any drift fails `tests/report/test_a11y_level_aa.py::test_i18n_fr_en_have_same_keys`. ## Adding a key 1. Add the key + value to `fr.json` (canonical): ```json "your_new_key": "Texte en français" ``` 2. Add the same key to `en.json` with the English value: ```json "your_new_key": "Text in English" ``` 3. Use it in templates via `data-i18n="your_new_key"`: ```html ``` The fallback text inside the tag is shown if the key is missing in the active locale (defensive). The JS swaps it at load time. ## Locale formatting (Sprint A7) The key `locale` carries the BCP-47 code: - `fr.json`: `"locale": "fr-FR"` - `en.json`: `"locale": "en-GB"` Used by `fmtNum()` / `fmtInt()` in `_app.js` for `toLocaleString` (thousands separators, decimals). For a third language, choose an appropriate BCP-47 code (e.g. `de-DE`, `es-ES`, `it-IT`). ## Adding a third language 1. Copy `fr.json` to `.json` (e.g. `de.json`) and translate every value. Keep the keys identical. 2. Add the language to `picarones/web/state.SUPPORTED_LANGS`: ```python SUPPORTED_LANGS = frozenset({"fr", "en", "de"}) ``` 3. Add the same key in `picarones/report/glossary/.yaml` with translated definitions (cf. [`extending-glossary.en.md`](extending-glossary.en.md)). 4. Add `` to the narrative templates: `picarones/measurements/narrative/templates/.yaml`. 5. Add the language switcher entry in the report header (if any). ## Tests ```bash pytest tests/report/test_a11y_level_aa.py::test_i18n_fr_en_have_same_keys pytest tests/report/test_sprint17_jinja_refactor.py ``` The first ensures key parity. The second verifies that the templates compile with the new locale. ## Pitfalls - **Don't hardcode strings** in templates or JS — always use the i18n mechanism, even for "simple" labels. The Sprint A6 scan found 11 hardcoded FR fallbacks; we don't want regressions. - **Don't translate file paths or function names** — they are language-neutral identifiers. - **Test in both languages** — generate a report with `--lang fr` AND `--lang en` and visually inspect.