Translation workflow
The example project uses GNU gettext catalog files:
locale/messages.potis the template extracted from the source code.locale/<language>/LC_MESSAGES/messages.pocontains translations for a language.messages.mois the compiled catalog used by the app.
The gettext format is supported by many online translation platforms, including Crowdin and Transifex. You can also edit .po files with dedicated software such as Poedit.
For a more detailed guide to translating apps with gettext, see Lokalise's tutorial.
The example defines Poe the Poet tasks for the repetitive steps. Run them from the example/ directory. These tasks are especially useful when maintaining several languages: they update or compile all existing language catalogs in one go, instead of requiring you to repeat commands for each language.
Before running the tasks, install GNU gettext and Poe the Poet. If Poe is not installed yet, install it with:
uv tool install poethepoet
These are the task definitions used by example/pyproject.toml:
[tool.poe.tasks]
run = "streamlit run app.py"
xgettext.shell = "xgettext -d messages -o ./locale/messages.pot *.py --from-code=UTF-8 --no-location"
msgfmt = "find ./locale -name \\*.po -execdir msgfmt messages.po -o messages.mo \\;"
msgmerge.shell = "find locale -name '*.po' -exec msgmerge -U {} locale/messages.pot \\; && find locale -name '*.po~' -delete"
xgettext creates one shared template from the Python files. msgmerge updates every existing .po catalog from that template and removes backup files; msgfmt compiles every .po catalog into a .mo file. These commands are included with GNU gettext, while Poe is only a task runner.
1. Mark text for translation
Wrap user-facing strings in gettext (usually imported as _) or use ngettext for singular and plural forms. For example:
st.write(_("Squirrel application"))
ngettext("One squirrel", "%(num)d squirrels", count)
2. Extract messages
After adding or changing translatable strings, generate the message template:
poe xgettext
This runs xgettext over the Python files in example/ and updates locale/messages.pot.
3. Update and translate catalogs
Merge the latest template into every existing .po catalog:
poe msgmerge
Open each locale/<language>/LC_MESSAGES/messages.po file and translate the entries marked msgstr. Keep placeholders such as %(num)d unchanged. For plural messages, translate each msgstr[n] form required by that language.
When msgmerge updates a catalog, it may mark an entry as fuzzy if the source text changed but resembles an older entry. The existing translation is carried over as a suggestion, but may no longer be accurate. Review it and remove the fuzzy flag; msgfmt skips fuzzy entries, so they will not be available in the app.
The task updates existing catalogs; adding a new language requires creating its .po catalog first.
4. Compile and check
Compile the translated catalogs into .mo files:
poe msgfmt
Then run the example app to check the translations:
poe run
Re-run poe xgettext and poe msgmerge whenever the source strings change, then translate new or changed entries and compile the catalogs again.