Python applications
ai-i18n-tools extracts UI strings from Python and from Jinja, Django, and Qt .ui files, then writes flat JSON keyed by the source-language string, GNU gettext catalogs, or both. sourceLocale can be any language. The runtime helper is a standard-library i18n.py (Python 3.10 or newer). Streamlit, Tkinter, PyQt, PySide, PyWebView, Flask, Pyramid, and FastAPI call that helper. Django uses Django's own gettext.
Adding locales to an existing single-language app: Add i18n to a Python app, including the i18n.py prompt and the Django / gettext prompt. Framework chooser: Python integrations. Function list: Python runtime (i18n.py).
Runnable copies live in examples/python-streamlit, examples/python-flask, examples/python-fastapi, and examples/python-gettext. They are Python projects. The first three use a virtualenv and pip install -r requirements.txt. python-gettext uses only the standard library. They are not npm packages. The release archive ai-i18n-tools-python-examples.zip contains the same four directories. The npm tarball and the standalone executable do not.
Scaffold
ai-i18n-tools init -t ui-python -P cheaperinference
ai-i18n-tools init -t ui-python-gettext -P anthropic
ai-i18n-tools init -t ui-python-flask -P groq
ai-i18n-tools init -t ui-python-django -P ollamaui-python writes flat JSON and i18n.py. ui-python-gettext writes JSON and .po / .mo. ui-python-flask writes gettext catalogs under translations/ and i18n.py. ui-python-django writes locale/<lang>/LC_MESSAGES/django.po and does not write i18n.py.
Also translate the repository README: add --with-readme to the init command above. See Add i18n to your README and Markdown docs.
Scan roots skip node_modules, .git, .venv, venv, env, site-packages, __pycache__, .tox, .nox, .pytest_cache, .mypy_cache, .ruff_cache, and .eggs.
Calls that translate
Look up the catalog entry first, then interpolate.
Portable:
t("Hello, {name}!", name=name)
t("Total: {price:,.2f}", price=price)
_("Hello, %s!") % name
_("Total: {price:,.2f}").format(price=price)On Python 3.14 the helper also accepts a template string:
translate(t"Hello, {name}!")translate is an alias of t. Keyword arguments override values taken from the template string.
Native gettext, still safe because the template is the argument:
_("Hello, %s!") % name
ngettext("1 file", "{count} files", count, count=count)
pgettext("menu", "Open")These are evaluated before any catalog lookup, so the extractor marks them unsafe and migrate-python-i18n rewrites them:
_(f"Hello, {name}!")
_("Hello, %s!" % name)
_("Hello, {name}!".format(name=name))migrate-python-i18n is a dry run until you pass --write. It writes migrate-python-i18n-report.md. Debug fields such as {value=} and nested format specs are reported and left in place.
{% trans %} and {% blocktrans %} become gettext %(name)s placeholders. Jinja strips and collapses whitespace. Django keeps internal whitespace unless the tag says trimmed.
Catalogs
Flat JSON keys are the source-language string, the text in sourceLocale. That language can be any language. A context uses context + U+0004 + that source string. Plural slots stay {md5}_one, {md5}_other, and the other CLDR forms translate-ui already writes. The helper hashes the trimmed singular with MD5 and reads the first 8 hex characters.
Gettext files use msgctxt for context and msgid / msgid_plural for plurals. ui.gettext.compileMo writes the .mo next to the .po. purge-locale deletes that locale's .po and .mo. When flatOutput is false it leaves the flat JSON files alone because they were not written.
t(..., plurals=True) with an English source locale and an explicit plural source skips the model and stores one / other from the source strings.
Locale in each framework
set_locale returns a ContextVar token. Reset it in finally, including FastAPI and Flask middleware, so concurrent requests do not leak a locale.
- Streamlit.
streamlit_language_selector(sidebar=True, key="lang_selector")writesst.session_state.localeandst.query_params["lang"]. Give every widget a fixedkey. Seeexamples/python-streamlit. - FastAPI. Read
?lang=orAccept-Languagein middleware, callset_locale, andreset_localeinfinally. Routes callt()and_(). Seeexamples/python-fastapi. - Flask and Pyramid. Set the locale from the request and inject
_,t,current_locale,ui_languages, anddirectioninto the template context. Seeexamples/python-flask. - Existing gettext. Leave
_(),gettext(), andngettext()in place. Pointui.gettextat the currentlocales/tree and runextractthentranslate-ui. Seeexamples/python-gettext. - Django. Use
gettext,ngettext, and{% blocktrans %}. Init templateui-python-djangomatches Django'slocale/directory anddjangodomain. - Tkinter. Call
t()or_()when creating each widget, then rebuild the visible text afterset_locale. - PyQt / PySide.
self.tr("...")is extracted. A.uifile contributes<string>elements unlessnotr="true". Thecommentattribute becomes gettext context. - PyWebView / PyWebKit. Extract the Python shell with the Python templates above. Extract the HTML page with
data-i18nmarkers or with one HTML document per locale.