How to Translate a Python Project With Gettext the Easy Way
Overview
This is a description of the workflow to follow for translating small Python apps.
gettext is a very versatile program used to provide internationalization (I18N) and localization (L10N) in many programming languages.
To be able to internationalize our apps, we should understand its core concepts.
How gettext works
Creating translations for the first time
A typical workflow when using gettext is as follow:
- Wrap strings to translate with the gettext function in source code. Example:
gettext("An approximation")
Run
xgettext
to extract translation strings from source code and generate a.pot
**(Portable Object Template) ** file.This file will hold the translation strings and empty translations. Example:
#: generator.py:111
msgid "An approximation"
msgstr ""
- Copy
.pot
template to a language specific.po
file, and translate its messages. Example for English to Spanish:
#: generator.py:111
msgid "An approximation"
msgstr "Una aproximaciΓ³n"
- Compile the above
.po
file to a special.mo
file to be used by our application.
Updating existing translations
When already having translation files and we change or add some strings for translating we need to:
- Generate a new
.pot
template with new strings withxgettext
. - Merging the existing
.po
file containing our translations with the above template withmsgmerge
. Example:
msgmerge --update locales/es/LC_MESSAGES/base.po locales/base.pot`
Project skeleton for translations
We will generate a new Python project to apply the above concepts.
In this project we print a localized Hello World message in all the available languages (e.g.: Spanish and English).
Let’s say we have a single python file main.py
with the following
content:
import gettext
def main(argv):
for lang in ['es', 'en']:
# set current language
lang_translations = gettext.translation('base', localedir='locales', languages=[lang])
lang_translations.install()
# define _ shortcut for translations
_ = lang_translations.gettext
# mark a string translatable
print(_("Hello World"))
Now run mkdir -p locales/{en,es}/LC_MESSAGES
to create the following directory structure:
locales/
βββ en
βΒ Β βββ LC_MESSAGES
βββ es
βββ LC_MESSAGES
And we can start generating pot
, po
, and mo
files.
xgettext -d base -o locales/base.pot main.py
cp locales/base.pot locales/en/LC_MESSAGES/base.po
cp locales/base.pot locales/es/LC_MESSAGES/base.po
The tree structure should look like:
.
βββ locales
βΒ Β βββ base.pot
βΒ Β βββ en
βΒ Β βΒ Β βββ LC_MESSAGES
βΒ Β βββ base.po
βΒ Β βββ es
βΒ Β βββ LC_MESSAGES
βΒ Β βββ base.po
βββ main.py
Edit es/LC_MESSAGES/base.po
and then generate the translations that will be used by the app:
msgfmt -o locales/en/LC_MESSAGES/base.mo locales/en/LC_MESSAGES/base
msgfmt -o locales/es/LC_MESSAGES/base.mo locales/es/LC_MESSAGES/base
Now our app should print the hello world message in both languages:
$ python3.6 main.py
Hola Mundo
Hello World
Updating translations
After we have generated the basic translation files, we modify the
existing message and mark another string to be translated at main.py
print(_("Hello World!!!!"))
print(_("Good Bye"))
Now we need to generate the template again:
xgettext -d base -o locales/base.pot main.py
But this time, instead of copying the template to their corresponding
.po
file, we use the command msgmerge
to add new translatable
strings and detect changes to existing ones:
msgmerge --update locales/es/LC_MESSAGES/base.po locales/base.pot
The above command updates the base.po
for Spanish, now it looks like:
...
#: main.py:11
#, fuzzy
msgid "Hello World!!!!"
msgstr "Hola Mundo"
#: main.py:12
msgid "Good Bye"
msgstr ""
It shows the new Good Bye message for translating, keeps the translation of Hello World and a special note, look at the fuzzy keyword, that means that the original string has changed, in this case from “Hello World” to “Hello World!!!!” so you can update the translation.
#: main.py:11
msgid "Hello World!!!!"
msgstr "Hola Mundo"
#: main.py:12
msgid "Good Bye"
msgstr "Chau"
Finally, after updating translations, generate the .mo
file with
msgfmt
msgfmt -o locales/es/LC_MESSAGES/base.mo locales/es/LC_MESSAGES/base
And check results:
$ python3.6 main.py
Hola Mundo!!!!
Chau
Hello World!!!!
Good Bye
Repo
The above code can be find at the following Github repo with a special
Makefile
file containing recipes for each step: https://github.com/marcanuy/python-i18n-skel
References
- https://docs.python.org/3/library/gettext.html
- https://docs.python.org/3/library/gettext.html#changing-languages-on-the-fly
- https://www.gnu.org/software/gettext/manual/html_node/msgmerge-Invocation.html
- https://en.wikipedia.org/wiki/Gettext
- Solve Selenium WebDriverException executable needs to be in PATH error messageApril 24, 2020
- Pip upgrade all packages at once with a one-liner command January 31, 2019
- Test Files Creating a Temporal Directory in Python UnittestsSeptember 2, 2018
- How to Translate a Python Project With Gettext the Easy Way
Behave Testing
Django webframework
- August 1, 2023
- How to create a reusable Django app and distribute it with PIP or publish to pypi.orgJune 29, 2021
- How To Serve Multiple Django Applications with uWSGI and Nginx in Ubuntu 20.04October 26, 2020
- How to add favicon to Django in 4 stepsSeptember 3, 2020
- Categories in Django with BreadcrumbsAugust 30, 2020
- How To Migrate From SQLite To PostgreSQL In Django In 3 stepsAugust 28, 2020
- Practical guide to internationalize a Django app in 5 steps.August 24, 2020
- Disable new users singup when using Django's allauth packageSeptember 3, 2019
- How to add ads.txt to Django as requested by Google AdsenseAugust 30, 2019
- Have multiple submit buttons for the same Django formJuly 2, 2019
- Better Testing with Page Object Design in DjangoMay 1, 2019
- Generating slugs automatically in Django without packages - Two easy and solid approachesFebruary 14, 2019
- How to set up Django tests to use a free PostgreSQL database in HerokuFebruary 13, 2019
- Dynamically adding forms to a Django FormSet with an add button using jQueryFebruary 6, 2019
- Use of Django's static templatetag in css file to set a background imageFebruary 1, 2019
- Activate Django's manage.py commands completion in Bash in 2 stepsJanuary 29, 2019
- Sending Emails with Django using SendGrid in 3 easy stepsJanuary 9, 2019
- Adding Users to Your Django Project With A Custom User ModelSeptember 21, 2018
- Setting Up A Factory For One To Many Relationships In FactoryboyApril 17, 2018
- Generate UML class diagrams from django modelsMarch 24, 2018
- Set Up Ubuntu To Serve A Django Website Step By StepJuly 3, 2017
- Django Project Directory StructureJuly 16, 2016
- How to Have Different Django Settings for Development and Production, and environment isolationJune 10, 2016
- Django OverviewJune 2, 2016
Django Forms
- Adding a Cancel button in Django class-based views, editing views and formsJuly 15, 2019
- Using Django Model Primary Key in Custom Forms THE RIGHT WAYJuly 13, 2019
- Django formset handling with class based views, custom errors and validationJuly 4, 2019
- How To Use Bootstrap 4 In Django FormsMay 25, 2018
- Understanding Django FormsApril 30, 2018
- How To Create A Form In DjangoJuly 29, 2016
Flask web microframework
- Understanding Flask's context conceptJanuary 26, 2017
- Avoid Using Flask Instance Folder When Deploying To HerokuJanuary 24, 2017
- Managing Environment Configuration Variables In Flask With DotenvJanuary 24, 2017
- Organize A Flask Project To Handle Production And Development Environments EffectivelyJanuary 11, 2017
- An Overview Of Flask Main Concepts And How It WorksDecember 31, 2016
Python Language
- Python Tools To Write Better CodeNovember 7, 2017
- Python Language Main Concepts And SummaryJune 11, 2017
- Python notesMay 30, 2016
Python Language Concepts
- Understanding How Python Packages Modules And Imports WorkJanuary 2, 2017
- Python Language Basic ConceptsJune 14, 2016
Python Environment
- Understanding Python 3 virtual environments different approachesJanuary 15, 2019
- Python Projects Isolation Using Virtual EnvironmentsJune 10, 2016
Articles
Subcategories
Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 . Terms and Policy.
Powered by SimpleIT Hugo Theme
·