Phase 8: i18n Foundation - Research
Researched: 2026-02-19 Domain: Python i18n (python-i18n + YAML), NiceGUI reactive UI, AG Grid locale, ReportLab PDF i18n Confidence: HIGH
Summary
Phase 8 introduces the internationalization foundation for StorePredict: a t() helper backed by YAML locale files, a FR/EN language toggle in the header, and propagation of the chosen locale into every surface that renders user-visible text — NiceGUI pages, the AG Grid table, and the PDF report. French is the primary locale; English is the fallback. All UI strings must go through t() — hardcoded text in the UI layer is a lint violation.
Key Findings
t() Helper Pattern
python-i18n with YAML extras provides the t() function. Wrap it in a project-level helper that reads app.storage.tab['locale'] so callers need no locale argument.
# src/store_predict/i18n/__init__.py
import i18n as _i18n
from nicegui import app
_i18n.set("file_format", "yaml")
_i18n.set("filename_format", "{locale}.{format}")
_i18n.set("load_path", [str(Path(__file__).parent / "locales")])
_i18n.set("fallback", "en")
def t(key: str, **kwargs: object) -> str:
locale = app.storage.tab.get("locale", "fr")
return _i18n.t(key, locale=locale, **kwargs)
Placeholder syntax uses %{name}: "upload.loaded_notify: Fichier chargé — %{count} VMs".
Language Toggle with Page Reload
The safest strategy for NiceGUI is a full page reload after writing the locale to app.storage.tab. Partial reactive updates are unreliable for headers and AG Grid columns built at construction time.
def _toggle_locale() -> None:
current = app.storage.tab.get("locale", "fr")
app.storage.tab["locale"] = "en" if current == "fr" else "fr"
ui.navigate.reload()
AG Grid Locale via localeText
AG Grid column headerName values are Python strings set at construction time — they are translated on page build. Built-in AG Grid UI text (pagination, filters, "No Rows") is set via the localeText grid option loaded from a CDN-hosted AG_GRID_LOCALE_FR object.
ui.run_javascript("""
fetch('https://cdn.jsdelivr.net/...ag-grid-locale-fr.js')
.then(r => r.text()).then(code => eval(code))
.then(() => { myGrid.api.setGridOption('localeText', agGridLocaleFR); });
""")
PDF Locale Propagation
Pass locale explicitly to generate_report_pdf(). At function entry, call i18n.set("locale", locale) before any t() call. PDF generation is synchronous — no coroutine interleaving risk.
def generate_report_pdf(summary, project_name, locale="fr") -> bytes:
_i18n.set("locale", locale)
# All t() calls now use the explicit locale
YAML Key Hierarchy
Namespace keys by feature area to prevent collisions. Both locale files must be kept in sync — a missing key falls back to English, not a KeyError.
# fr.yaml
upload:
title: "Téléversement du fichier"
drop_label: "Déposez votre fichier ici"
loaded_notify: "Fichier chargé — %{count} VMs"
report:
download_pdf: "Télécharger le rapport PDF"
Module Import: i18n vs i18n.config
The python-i18n package installs as the i18n top-level module. Import it as import i18n as _i18n to avoid shadowing the project's own store_predict.i18n package at the call sites that need both.
Anti-Patterns
- Calling
t()from pipeline modules: Pipeline code (ingestion.py,classification.py) has no NiceGUI context. Use hardcoded English strings forIngestionErrormessages; the UI layer displays them viaui.notify(str(exc)). - Using a loop variable named
t: It shadows thet()import. Usewt,row, or any other name in loops. - Building AG Grid column defs before locale is set: Column
headerNamestrings are baked into the grid spec at construction time. Always build column defs inside the page function (which runs per-request with the correct locale), not at module load time.
Dependencies
| Package | Version | Notes |
|---|---|---|
| python-i18n | >=0.3.9 | Core library |
| python-i18n[YAML] | same | Required for YAML locale files |
No AG Grid or PDF library changes needed — locale is passed at call time.