Skip to content

Internationalization

Aglaïa is localized with Qt Linguist (PySide6), not gettext. English (en_US) and French (fr_FR) ship today.

Layout

aglaia/i18n/
__init__.py install_translator(app, lang_pref) — loads a .qm at startup
aglaia_en_US.ts source catalogue (Qt Linguist XML)
aglaia_fr_FR.ts source catalogue
qm/ compiled .qm binaries (generated; loaded at runtime)

User-facing strings are wrapped in self.tr("…") (or QCoreApplication.translate). The active locale comes from the language config key (KEY_LANGUAGE: "" = follow QLocale.system(), else "en_US" / "fr_FR"), applied before the first widget is built.

Workflow

  1. Extract / update after wrapping new strings:

    Terminal window
    scripts/i18n_extract.sh # pyside6-lupdate → updates the .ts catalogues
  2. Translate — edit the .ts files in Qt Linguist (pyside6-linguist) or by hand; fill each <translation> and drop the type="unfinished".

  3. Compile to the binaries the runtime loads:

    Terminal window
    scripts/i18n_compile.sh # pyside6-lrelease → aglaia/i18n/qm/*.qm

To add a locale, add its .ts to scripts/i18n_extract.sh’s TS_FILES, translate, compile, and add it to SUPPORTED_LOCALES in aglaia/i18n/__init__.py so Settings offers it.

Shipping them

A compiled catalogue has to be in three lists, and they are maintained separately:

listwhat it feeds
scripts/i18n_compile.shgenerates aglaia/i18n/qm/*.qm
pyproject.toml [tool.setuptools.package-data]the wheel (pip install aglaia)
Aglaia.spec datasthe frozen macOS app

This paragraph used to assert the third one and be wrong: the .qm files were in the wheel but not in the spec, so the packaged app had no app catalogue at all. install_translator loaded nothing, every string fell back to the en-US source, and Settings still said Français. Nothing reported it, because QTranslator.load() returning False was not checked (#170).

Two guards now. install_translator logs the miss, naming the locale and the directory it looked in — but only for a locale we actually ship or the user explicitly chose, so following a system locale we do not translate stays silent. And tests/test_i18n_catalogues.py fails if a locale in SUPPORTED_LOCALES has no .ts or .qm, if either ship list stops naming i18n/qm, or if French has an unfinished string — it is the project’s own second language, so a gap there is an oversight, not a backlog.

That guard sits in tests/, not tests/gui/, and reads SUPPORTED_LOCALES with ast rather than importing aglaia.i18n: that module imports PySide6, which the CI test job does not install, and a guard that skips on CI does not guard the thing CI builds.