How to Translate a Python Project With Gettext the Easy Way

  • Published: August 29, 2018

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:

graph TD main.py-->|xgettext|base.pot base.pot-->|cp base.pot base.po|base.po base.po-->|msgfmt -o base.mo base|base.mo
  1. Wrap strings to translate with the gettext function in source code. Example:
gettext("An approximation")
  1. 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 "" 
  1. 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" 
  1. 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:

graph TD main.py-->|xgettext|base.pot base.pot-->|msgmerge --update base.po base.pot|base.mo
  1. Generate a new .pot template with new strings with xgettext.
  2. Merging the existing .po file containing our translations with the above template with msgmerge. 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

Uruguay
Marcelo Canina
I'm Marcelo Canina, a developer from Uruguay. I build websites and web-based applications from the ground up and share what I learn here.
comments powered by Disqus
Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 ·