How to internationalize your application

In this howto, you will learn how to internationalize your code, extract translatable strings and translate your application into an other language.

In this example, a project HelloWorld was created with grokproject HelloWorld.

Internationalizing Strings in Python Code

You need to use a MessageFactory that marks all the strings you want to be able to translate:

__init__.py
from zope.i18nmessageid import MessageFactory
HelloWorldMessageFactory = MessageFactory('helloworld')

app.py
from HelloWorld import HelloWorldMessageFactory as _

Now to internationalize your strings in Python code, change:

message = u'Hello World'

to:

message = _(u'Hello World')

You will have in your generated POT:

msgid "Hello World"
msgstr ""

or you can define both a msgid and default:

message = _(u'hello_msg', default=u'Hello World')

You will have in your generated POT:

#. Default: "Hello World"
msgid "hello_msg"
msgstr ""

If you need to insert some data into your message you can accomplish this, too

who = u'World'
message = _(u'hello_msg', default=u'Hello ${who}', mapping={ 'who' : who })

Example

In your code you will have:

app.py

class Index(grok.View):
    def update(self):
        self.message = _(u'Hello World')

And your templates:

app_templates/index.pt

<html>
    <head>
    </head>
    <body>
        <p tal:content="view/message" />
    </body>
</html>

To internationalize text in a page template, you add the i18n:domain attribute to the html-tag. Then you mark each tag who’s text you want to translate with the attribute i18n:translate:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
    xmlns:tal="http://xml.zope.org/namespaces/tal"
    xmlns:i18n="http://xml.zope.org/namespaces/i18n"
    lang="en"
    i18n:domain="helloworld">
<head>
    <title>My project</title>
</head>
<body>
  <a tal:attributes="href python: view.url('creategame')"
     i18n:translate="link_create_new_game"
     >Create new game</a>
  <a tal:attributes="href python: view.url('createcontact')"
     i18n:translate=""
     >Create new contact</a>
</body>
</html>

You will have in your generated POT:

#. Default: "Create new game"
msgid "link_create_new_game"
msgstr ""

msgid "Create new contact"
msgstr ""

Using Zope 3 style translation domains

Setting the locales directory

To activate translations you need to let Grok know what translation domain you are using and where the translation files are. src/helloworld/configure.zcml should look like this:

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:grok="http://namespaces.zope.org/grok"
           xmlns:i18n="http://namespaces.zope.org/i18n">
  <include package="grok" />
  <includeDependencies package="." />
  <grok:grok package="." />
  <i18n:registerTranslations directory="locales" />
</configure>

Extracting strings with z3c.recipe.i18n:i18n

Grokproject has created two files in your HelloWorld/bin-folder called: i18nextract and i18nmerge.

1 – Building the POT file

In your project directory, to extract all internationalizable strings:

./bin/i18nextract

This creates the src/helloworld/locales directory and generates src/helloworld/locales/helloworld.pot

2 – Creating new translation

Begin to translate into french:

mkdir -p src/helloworld/locales/fr/LC_MESSAGES/
msginit -i src/helloworld/locales/helloworld.pot -o src/helloworld/locales/fr/LC_MESSAGES/helloworld.po

translate src/helloworld/locales/fr/LC_MESSAGES/HelloWorld.po with poEdit, KBabel...

NOTE: If you use an editor such as poEdit, it will generates your compiled .mo translation files when saving, this saves you having to compile them manually.

To use default text strings that you have put in your PageTemplates and python-code you need to create a translation for your default language and generate the .mo file, BUT you don’t translate any of the strings.

http://markmail.org/message/ozesj6jxqlbugtql

3 – Updating existing translation

To update all PO files, in your buildout directory:

./bin/i18nextract
./bin/i18nmergeall

4 – Generating MO files

To finish, you have to generate a MO file for each PO file:

cd locales/fr/LC_MESSAGES/
msgfmt -o helloworld.mo helloworld.po

If you want to generate all mo file, you can use this command:

for po in `find . -name "*.po"` ; do msgfmt -o `dirname $po`/`basename $po .po`.mo $po; done

As mentioned previously, this is done automatically by poEdit when you save changes.

Restart your server and see the translation in french.

There is a new feature in zope.i18n 3.5.0, the changelog says: “Feature: Added optional automatic compilation of mo files from po files. You need to depend on the zope.i18n [compile] extra and set an environment variable called zope_i18n_compile_mo_files to any True value to enable this option.”

How Does the Server Know What Language is Used?

Grok/Zope3 uses the Accept-Language header of the HTTP-request to determine what language to present to the user. This is set by the browser and in Macosx you change this in System Preferences / International / Language. In order to test your translations you probably need to change there and restart the browser.

So the HTTP request comes with a field for the user’s preferred language, set by the browser.

You can retrieve the user’s preferred languages like

import grok
from zope.i18n.interfaces import IUserPreferredLanguages

class ShowLanguageView(grok.View):
    grok.context(grok.Application)

    def render(self):
        return str(IUserPreferredLanguages(self.request).getPreferredLanguages())

If you want to override these languages in your application, e.g. by setting it to a fixed value or by fetching the user’s preferred language from some storage, you can do it like this:

import grok
from zope.publisher.interfaces.http import IHTTPRequest
from zope.i18n.interfaces import IUserPreferredLanguages

class PreferredLangugageAdapter(grok.Adapter):
    grok.context(IHTTPRequest)
    grok.implements(IUserPreferredLanguages)

    def getPreferredLanguages(self):
        # example: fix language to German
        #  The sequence is sorted in order of quality, with the most preferred
        #  languages first.
        return ['de-de', 'de']

        # If you have a custom principal object where you save the user's pre-
        #  ferred language on, you could also do something like this:
        #
        # request = self.context
        # return [ request.principal.preferred_language, 'en-us', 'en' ]

Now your application will use the best fitting language computed from the values returned by this adapter.

To see how you can internationalize and localize your page templates, please the corresponding “Internationalization” chapter.

For more information be sure to check out the zope.i18n package: http://pypi.python.org/pypi/zope.i18n .