Software localization
Pyramid I18n Tutorial From Scratch
Pyramid is a lightweight web framework that goes in massively for flexibility and automation. One of its main advantages is that it does not force you to use specific modules. Instead, it bundles with several options for you to choose from.
Although having extra options allows for greater flexibility, maintaining and documenting them is a lot harder now. The documentation can be a nightmare if you are a new user without any prior experience.
As Pyramid's internationalization and localization docs cover only the basics – leaving out, for example, how to implement i18n if you are using a template engine – new users can get lost easily. This Pyramid I18n tutorial seeks to fill that gap.
The Basics of Pyramid i18n
The internationalization process in Pyramid mainly relies on translation strings (Unicode objects containing extra information related to translation). Instead of creating each of them individually in your Pyramid application, you should use GNU gettext – part of the Pyramid translation services. For your information, GNU gettext makes use of the following files internally:
- Portable Object Template (.pot) – contains a list of message identifiers that are extracted from the source code of your projects (e.g. HTML files); it serves as the base template when creating .po files for each individual locale,
- Portable Object (.po) – a translation file meant for human translators; each locale will have its own PO file,
- Machine Object (.mo) – a machine-readable binary file that is converted from a .po file and used by GNU gettext internally for internationalization and localization.
The translation workflow usually goes as follows:
- Create template files with translation markers; the syntax and formatting will depend on the template engine used,
- Extract message identifiers from template files and generate a POT file,
- Create a PO file for each supported locale based on the POT file,
- Translate the content in the PO files,
- Compile the PO files into MO files,
- Serve your Pyramid application via the template engine.
At the time of writing, Pyramid comes with support for three different template engines:
- Chameleon
- Jinja2
- Mako
Setup
This tutorial will walk you through the process of internationalizing your Pyramid application easily using Jinja2. Let's get it started!
Installing Python Modules
Make sure you have created a new virtual environment. As soon as you activate it, and install the following modules:
- pyramid
- pyramid_jinja2
- Babel
pip install pyramid pip install pyramid_jinja pip install Babel
Creating a Jinja2 Pyramid Project
Next, run the following command to create a new Jinja2-based Pyramid project. It will generate all the required files and folders for you. I am using myproject as the root folder for this tutorial. Modify it accordingly.
pcreate -s pyramid_jinja2_starter myproject
Once you are done with it, change your working directory to the root folder of your new project.
cd myproject
Installing Additional Modules for Development
By default, it will include its own scaffold to set up your development files. Continue the setup by running either
python setup.py develop
or
pip install -e .
It will install additional packages for serving your application automatically, based on the configuration in setup.py.
Starting the Pyramid Application
Next, run the following command to start your web application.
pserve development.ini
Head over to the following URL to access your Pyramid application.
http://localhost:6543/
You should see the following user interface.
Implementation
Modifying Jinja2 Templates
Find the Jinaj2 template file at the following directory myproject/myproject/templates/mytemplate.jinja2. Open it up and you should notice that 'Welcome to' is marked as translatable based on Jinja2 i18n syntax.
{% trans %}Welcome to{% endtrans %}
Let us add a new paragraph below it. I am going to use the following code:
<p>{% trans %}Phrase is the place where localization teams come together to release translations in the most streamlined way possible.{% endtrans %}</p>
Extracting a Message from Jinja2 Templates
Save the file once you are happy with it, and go back to your terminal. Run the following command to extract translatable strings from your Jinja2 template.
python setup.py extract_messages
It will write PO template file to your project. If your project is named myproject, you can find the file at myproject/myproject/locale/myproject.pot.
Initializing Message Catalogs
The next step is to initialize the message catalog files, based on the language supported by your application. Let us say our application is going to support the following languages:
- German
- Spanish
- French
Run the following command to complete the initialization:
python setup.py init_catalog -l es python setup.py init_catalog -l fr python setup.py init_catalog -l de
It will create new folders using the language code that you have specified inside the locale folder. Each folder contains a subfolder called LC_MESSAGES, containing the following files:
- myproject.po,
- myproject.mo.
The initialization process is meant to be done only once. Subsequently, you have to run the update catalog command instead after each message extraction as follows:
python setup.py extract_messages python setup.py update_catalog
Adding Translations
Let us have a look at the PO files and start editing them. It is highly recommended to use a specialized tool, a translation management tool like Phrase, to streamline the translation process. All the translatable strings in your Jinja2 template will be converted into msgid and msgstr pairs. The latter, by default, is an empty string, where you can fill in your translations. Here is an example for the German language.
#: myproject/templates/mytemplate.jinja2:41 msgid "Welcome to" msgstr "Willkommen zum" #: myproject/templates/mytemplate.jinja2:42 msgid "" "Phrase is the place where localization teams come together to release " "translations in the most streamlined way possible." msgstr "" "Phrase ist der Ort, an dem Lokalisierungsteams zusammenkommen, um ihre " "Übersetzungen einfacher denn je zu releasen."
Compiling Message Catalogs
Once you have completed the translation, run the following command to re-compile the PO files into MO files.
python setup.py compile_catalog
If you happen to see the following output
catalog myproject/locale/de/LC_MESSAGES/myproject.po is marked as fuzzy, skipping
it means that you should review your PO files and check for items or comments marked with the fuzzy keyword. It is simply a call for revision by the translators. A translator may mark a specific translation as 'fuzzy' as a reminder that the translation needs to be revisited later on and before compiling to an MO file. You can simply remove the keyword or force re-compile using an -f flag.
python setup.py compile_catalog -f
It will generate its corresponding MO files in the same folder.
Starting the Pyramid Server
Start your application normally with
pserve development.ini
and open the following URL in your browser:
http://localhost:6543/
For German translation, head over to the following URL
http://localhost:6543/?_LOCALE_=de
Handling Pluralization
Jinja2 i18n has built-in support for pluralization via the following syntax:
{% trans %} {{ day }} day left. {% pluralize %} {{ day}} days left. {% endtrans %}
Add the code above to your mytemplate.jinja2 file. We are going to pass a variable called day to Jinja2, and it will localize it for us based on the value. To do so, open up myproject/myproject/views.py and modify the function inside it as follows:
def my_view(request): return {'project': 'myproject', 'day': 14}
Run the following commands to update your message catalogs:
python setup.py extract_messages python setup.py update_catalog
Next, update your PO files with your translations. The pluralized translatable string will have the following syntax instead:
- msgid
- msgid_plural
- mgstr[0]
- msgstr[1]
You should place your singular translation in msgstr[0] while plural translation should be under msgstr[1]. Have a look at the following example for the fr locale.
#: myproject/templates/mytemplate.jinja2:44 #, python-format msgid "" "\n" " %(day)s day left.\n" " " msgid_plural "" "\n" " %(day)s days left.\n" " " msgstr[0] "%(day)s jour restant." msgstr[1] "%(day)s jours restant."
Lastly, all you need to do is to compile your message catalogs.
python setup.py compile_catalog -f
Restart your application and you should see the following user interface for the fr locale.
Retrieving an Active Locale
You can easily obtain the active locale associated with a request by using the built-in pyramid.request.Request.locale_name(). Modify the function inside myproject/myproject/views.py as follows:
def my_view(request): locale_name = request.locale_name return {'project': 'myproject', 'day': 14}
When you print the locale_name variable, you should get the corresponding en, de, or fr locale.
Identifying Supported Locales
It is by design that any Pyramid application must always know which language should be translatable, regardless of the translation files present in the disk. The reasons behind it are as follows:
- Some translations might exist on disk but are incomplete,
- Translations might exist for a specific language but not for all the domains.
The best solution is to pre-define the supported language in your development.ini file. Add a new entry to app:main. We define the key as available_languages and each supported locale is separated by whitespace.
[app:main] use = egg:myproject available_languages = fr de en # ... rest of the settings
Then, head over to myproject/myproject/views.py and add a new import at the top of the file.
from pyramid.settings import aslist
Modify your my_view functions as follows:
def my_view(request): languages = aslist(request.registry.settings['available_languages']) return {'project': 'myproject', 'day': 14}
When you print the language variables, you should get a list.
['fr', 'de', 'en']
Setting a Fallback Locale
By default, the fallback locale is en. You can easily configure it in the development.ini file. Simply modify default_locale_name setting to your desired locale.
[app:main] use = egg:myproject # ... rest of the settings pyramid.default_locale_name = en
Formatting Numbers
The easiest method for number formatting is via the Babel module as Pyramid itself does not come with support for number formatting based on different locales. Add the following import at the top of your views.py file.
from babel.core import Locale from babel.numbers import format_decimal
Modify the content of your my_view function.
def my_view(request): # get request locale locale_name = request.locale_name # construct a new Babel Locale object based on request locale locale = Locale(locale_name) # format number by using format_decimal function result = format_decimal(1.2345, locale=locale) return {'project': 'myproject', 'day': 14}
Format Datetime
Likewise, you can use Babel to a specific datetime format, based on the current locale. Add the following import statement:
import datetime from babel.dates import format_date
Call format_date function to format the date.
def my_view(request): # get request locale locale_name = request.locale_name # construct a new Babel Locale object based on request locale locale = Locale(locale_name) # format date by using format_date function result = format_date(datetime.datetime.now(), locale=locale) return {'project': 'myproject', 'day': 14}
Concluding Our Pyramid i18n Tutorial
To sum things up, you can easily internationalize your Pyramid application using Jinja2 Pyramid i18n. It comes with built-in templates that generate locale files automatically for you.
If you want to speed up your i18n process, give Phrase a try. The leanest, most reliable translation management platform on the market comes with a 14-day free trial and will equip you with everything you need to:
- Build production-ready integrations with your development workflow,
- Invite as many users as you wish to collaborate on your projects,
- Edit and convert localization files online with more context for higher translation quality.
Last but not least, make sure you check out the following articles if you want to know even more about internationalization and localization in Python: