Translating text strings

Internationalization is a process to make your code locale and language aware. Usually this means supplying translation files for text strings used in the code.

Plone internally uses UNIX standard gettext tool to perform i18n.

There are two separate gettext systems. Both use .po <http://www.gnu.org/software/hello/manual/gettext/PO-Files.html> file format to describe translations.

Note that this chapter concerns only code-level translations. Content translations are managed by Products.LinguaPlone add-on product.

zope.i18n

  • Follows gettext best practices
  • Translations are stored in locales folder of your application. Example: i18n/LC_MESSAGES/fi/your.app.po
  • Has zope.i18nmessageid package which is string-like class allowing storing translation domain with translatable text strings easily
  • .po files must be manually converted to .mo binary files every time the translations are updated. See i18ndude.

Plone (at least 3.3) uses only file path and name to search the translation files. Information in the .po file headers is ignored.

Marking translatable strings

Each module declares its own MessageFactory which is a callable and marks strings with translation domain. MessageFactory is declared at main __init__.py file of your package.

from zope.i18nmessageid import MessageFactory

# your.app.package must match domain declaration in .po files
 yourAppMessageFactory = MessageFactory('your.app.package')

You also need to have the following ZCML entry:

<configure
    xmlns:i18n="http://namespaces.zope.org/i18n">

    <i18n:registerTranslations directory="locales" />

</configure>

Marking translatable strings

After the set-up above you can use message factory to mark strings with translation domains. i18ndude translation utilities use underscore _ to mark translatable strings (gettext message ids). Message ids must be unicode strings.

from your.app.package import yourAppMessageFactory as _

my_translatable_text = _(u"My text") # my_text is zope.

The object will still look like a string:

>>> my_translatable_text
u'Test'

But in reality it is zope.i18nmessageid.message.Message object.

>>> my_translatable_text.__class__
<type 'zope.i18nmessageid.message.Message'>
>>> my_translatable_text.domain
'your.app.package'

Automatically translated message ids

Plone will automatically perform translation for message ids which are outputted in page templates.

The following code would translate my_translateable_text to the native language activated for the current page.

<span tal:content="view/my_translateable_text">

Note that since my_translateable_text is zope.i18nmessageid.message.Message containing its own gettext domain information, the i18n:domain attribute in page templates does not affect message ids declared through message factories.

Manually translated message ids

If you need to manipulate translated text outside page templates, you need to perform the final translation manually.

Translation always needs context (i.e. under which site the translation happens), as the active language and other preferences are read from the HTTP request object and site object settings.

Translation can be performed using context.translate() method:

# Translate some text
msgid = _(u"My text") # my_text is zope.

# Use inherited translate() function to get the final text string
translated = self.context.translate(msgid)

# translated is now u"Käännetty teksti" (in Finnish)

Non-python message ids

There are also other message id markers in code outside Python domain

  • ZCML entries have their own mechanism
  • GenericSetup XML have their own mechanism
  • TAL page templates have their own mechanism

Translation string substitution

Translation string substitutions must be used when the final translated message contains variable strings in it.

Plone content classes inherit translate() function which can be used to get the final translated string. It will use the currently activate language. Translation domain will be taken from the msgid object itself, which is string-like zope.i18nmessageid instance.

Message ids are immutable (read-only) objects so you need to always create a new message id if you use different variable substituion mappings.

Python code:

from saariselka.app import appMessageFactory as _

class SomeView(BrowserView):


    def do_stuff(self):

        msgid = _(u"search_results_found_msg", default=u"Found ${results} results", mapping={ u"results" : len(self.contents)})

        # Use inherited translate() function to get the final text string
        translated = self.context.translate(msgid)

        # Show the final result count to the user as a portal status message
        messages = IStatusMessage(self.request)
        messages.addStatusMessage(translated, type="info")

Corresponding .po file entry:

#. Default: "Found ${results} results"
#: ./browser/accommondationsummaryview.py:429
msgid "search_results_found_msg"
msgstr "Löytyi ${results} majoituskohdetta"

For more information, see

PlacessTranslationService

  • Historic, being phased out
  • Stores .po files in i18n folder of your add-on product
  • Used for main “plone” translation catalog
  • Translation files are processed when Plone is restarted. Example: i18n/yourapp-fi.po.

i18ndude

i18ndude is developer command-line utility to manage .po and .mo files.

Usually you build our own shell script wrapper around i18ndude to automatize generation of .mo files of your product .po files.

Examples

Setting up folder structure for Finnish and English

Example:

mkdir locales
mkdir locales/fi
mkdir locales/en
mkdir locales/fi/LC_MESSAGES
mkdir locales/en/LC_MESSAGES

Creating .pot base file

Example:

i18ndude rebuild-pot --pot locales/mydomain.pot --create your.app.package .

Managing .po files

Example shell script to manage i18n files. Change CATALOGNAME to reflect the actual package of your product:

#!/bin/sh
#
# Shell script to manage .po files.
#
# Run this file in the folder main __init__.py of product
#
# E.g. if your product is yourproduct.name
# you run this file in yourproduct.name/yourproduct/name
#
#
# Copyright 2009 Twinapex Research http://www.twinapex.com
#

# Assume the product name is the current folder name
CURRENT_PATH=`pwd`
CATALOGNAME="yourproduct.app"

# List of languages
LANGUAGES="en fi"

# Create locales folder structure for languages
install -d locales
for lang in $LANGUAGES; do
    install -d locales/$lang/LC_MESSAGES
done

# Rebuild .pot
i18ndude rebuild-pot --pot locales/$CATALOGNAME.pot --create $CATALOGNAME .

# Compile po files
for lang in $(find locales -mindepth 1 -maxdepth 1 -type d); do

    if test -d $lang/LC_MESSAGES; then

        PO=$lang/LC_MESSAGES/${CATALOGNAME}.po

        # Create po file if not exists
        touch $PO

        # Sync po file
        echo "Syncing $PO"
        i18ndude sync --pot locales/$CATALOGNAME.pot $PO

        # Compile .po to .mo
        MO=$lang/LC_MESSAGES/${CATALOGNAME}.mo
        echo "Compiling $MO"
        msgfmt -o $MO $lang/LC_MESSAGES/${CATALOGNAME}.po
    fi
done