Software localization

Pyramid I18n Tutorial From Scratch

Learn everything you need to know about Pyramid i18n with Jinja2, and automate the generation of locale files in your Pyramid application.
Software localization blog category featured image | Phrase

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.

User interface | Phrase

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.

User interface in the French locale | Phrase

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: