Minimal example of translating an application with python gettext
, including packaging and language switching at runtime
Illustrates gettext
with a simple command line application that repeatedly prompts you to enter a language code, then displays a message translated to your language. A sample language is provided. Entering the language code ll
will display the "translated" message fσσ вαя вαz
. Entering any other value will fall back to the default message foo bar baz
.
Project structure:
python-gettext-demo/
|-- a/
| |-- __init__.py # installs languages
| |-- b.py # module with translated strings
| |-- c.py # module with translated strings
| |-- interface.py # entrypoint
| |-- locales/ # locale directory with structure that gettext expects
| |-- ll/ # translation data for language 'll'
| |-- LC_MESSAGES # gettext expects this directory to exist
| |-- messages.po # human-readable message catalog for language 'll'
|-- pyproject.toml # tells pip to install babel before installing this package
|-- setup.py # includes message catalog in wheel distribution
gettext
uses non-python files, so you need to specially include them in the packaged application.
$ git clone https://github.com/emlys/python-gettext-demo.git
$ cd python-gettext-demo
$ pip install .
$ demo_gettext
Enter your language: ll
foo bar baz in your language: fσσ вαя вαz
Enter your language: en
foo bar baz in your language: foo bar baz
$ git clone https://github.com/emlys/python-gettext-demo.git
$ cd python-gettext-demo
$ pip install pyinstaller
$ pyinstaller \ # include compiled message catalog in executable bundle
--add-data \ # you can also add this data with a hook or spec file
a/locales/ll/LC_MESSAGES/messages.mo:a/locales/ll/LC_MESSAGES \
a/interface.py
$ dist/interface/interface
Enter your language: ll
foo bar baz in your language: fσσ вαя вαz
Enter your language: en
foo bar baz in your language: foo bar baz
The _(...)
function translates strings. The definition of _
depends on which language is installed: calling gettext.install
updates the definition of _
. So to translate to a language, you need to be sure that every use of _
is evaluated after installing the desired language.
If a program only needs to execute in one language per run (such as a command line tool that takes a --language
argument) it's easy enough to guarantee this. Translated strings might be defined in places that complicate the order of execution. Module-level strings are evaluated at import time. Class attributes are evaluated when an object is instantiated. There's lots of ways that a translated string can get evaluated before the right language is installed. For a single language, you can rearrange things to guarantee the right evaluation order.
Switching between multiple languages during execution (like in a server module, or a loop like in this example) is tricky because there's no getting around having to re-evaluate the translated strings. Some options are:
- use
importlib.reload
to re-evaluate module-level variables - use lazy strings like this one from Flask-Babel
- define strings within functions
None of the options are perfect and you have to design around the constraint of controlling when strings are evaluated. See this stackoverflow post for details.