# i18n How internationalisation works in padelnomics: locale files, language routing, template usage, and how to add new keys or languages. --- ## Architecture ``` src/padelnomics/ ├── i18n.py # Loader + get_translations() + get_calc_item_names() ├── locales/ │ ├── en.json # 1222 keys, flat key→value │ └── de.json # 1222 keys, must match en.json exactly └── app.py # registers tformat filter + t/lang in template context ``` Both JSON files are loaded at import time. A key-parity assertion runs on startup — mismatched keys crash the process immediately rather than serving partially translated pages. --- ## Language routing Lang-prefixed blueprints (`public`, `planner`, `directory`, `leads`, `suppliers`, `content`) all mount under `//`. The `` segment is validated in `before_request`; unsupported values get a 404. Blueprints without a lang prefix (`auth`, `dashboard`, `billing`, `admin`) detect language from a cookie (`lang`) → `Accept-Language` header → fallback `"en"`. `g.lang` is set on every request and used by `get_translations(g.lang)` to populate the `t` context variable available in all templates. --- ## Template usage ### Basic lookup ```jinja2 {{ t.nav_planner }} {{ t.landing_faq_q1 }} ``` `t` is a plain dict; `t.key` and `t["key"]` are equivalent in Jinja2. ### Parameterised strings Use the `tformat` filter for strings with runtime values. The JSON value uses `{named_placeholder}` syntax: ```json "dir_results_count": "Showing {shown} of {total} suppliers" "dir_results_count" (de): "{shown} von {total} Anbietern" ``` ```jinja2 {{ t.dir_results_count | tformat(shown=suppliers|length, total=total_count) }} ``` ### JS-embedded strings Server-render translation values into `