Internationalization

Cubicweb fully supports the internalization of its content and interface.

Cubicweb’s interface internationalization is based on the translation project GNU gettext.

Cubicweb’ internalization involves two steps:

  • in your Python code and cubicweb-tal templates : mark translatable strings
  • in your instance : handle the translation catalog, edit translations

String internationalization

User defined string

In the Python code and cubicweb-tal templates translatable strings can be marked in one of the following ways :

  • by using the built-in function _:

    class PrimaryView(EntityView):
        """the full view of an non final entity"""
        __regid__ = 'primary'
        title = _('primary')
    
OR
  • by using the equivalent request’s method:

    class NoResultView(View):
        """default view when no result has been found"""
        __regid__ = 'noresult'
    
        def call(self, **kwargs):
            self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
                % self._cw._('No result matching query'))
    

The goal of the built-in function _ is only to mark the translatable strings, it will only return the string to translate itself, but not its translation (it’s actually another name for the unicode builtin).

In the other hand the request’s method self._cw._ is also meant to retrieve the proper translation of translation strings in the requested language.

Finally you can also use the __ attribute of request object to get a translation for a string which should not itself added to the catalog, usually in case where the actual msgid is created by string interpolation

self._cw.__('This %s' % etype)

In this example ._cw.__` is used instead of ._cw._` so we don’t have ‘This %s’ in messages catalogs.

Translations in cubicweb-tal template can also be done with TAL tags i18n:content and i18n:replace.

If you need to add messages on top of those that can be found in the source, you can create a file named i18n/static-messages.pot.

You could put there messages not found in the python sources or overrides for some messages of used cubes.

Generated string

We do not need to mark the translation strings of entities/relations used by a particular instance’s schema as they are generated automatically. String for various actions are also generated.

For exemple the following schema:

class EntityA(EntityType):
    relation_a2b = SubjectRelation('EntityB')

class EntityB(EntityType):
    pass

May generate the following message

add EntityA relation_a2b EntityB subject

This message will be used in views of EntityA for creation of a new EntityB with a preset relation relation_a2b between the current EntityA and the new EntityB. The opposite message

add EntityA relation_a2b EntityB object

Is used for similar creation of an EntityA from a view of EntityB. The title of they respective creation form will be

creating EntityB (EntityA %(linkto)s relation_a2b EntityB)

creating EntityA (EntityA relation_a2b %(linkto)s EntityA)

In the translated string you can use %(linkto)s for reference to the source entity.

Handling the translation catalog

Once the internationalization is done in your code, you need to populate and update the translation catalog. Cubicweb provides the following commands for this purpose:

  • i18ncubicweb updates Cubicweb framework’s translation catalogs. Unless you actually work on the framework itself, you don’t need to use this command.
  • i18ncube updates the translation catalogs of one particular cube (or of all cubes). After this command is executed you must update the translation files .po in the “i18n” directory of your cube. This command will of course not remove existing translations still in use. It will mark unused translation but not remove them.
  • i18ninstance recompiles the translation catalogs of one particular instance (or of all instances) after the translation catalogs of its cubes have been updated. This command is automatically called every time you create or update your instance. The compiled catalogs (.mo) are stored in the i18n/<lang>/LC_MESSAGES of instance where lang is the language identifier (‘en’ or ‘fr’ for exemple).

Example

You have added and/or modified some translation strings in your cube (after creating a new view or modifying the cube’s schema for exemple). To update the translation catalogs you need to do:

  1. cubicweb-ctl i18ncube <cube>
  2. Edit the <cube>/i18n/xxx.po files and add missing translations (empty msgstr)
  3. hg ci -m “updated i18n catalogs”
  4. cubicweb-ctl i18ninstance <myinstance>

Editing po files

Using a PO aware editor

Many tools exist to help maintain .po (PO) files. Common editors or development environment provides modes for these. One can also find dedicated PO files editor, such as poedit.

While usage of such a tool is commendable, PO files are perfectly editable with a (unicode aware) plain text editor. It is also useful to know their structure for troubleshooting purposes.

Structure of a PO file

In this section, we selectively quote passages of the GNU gettext manual chapter on PO files, available there:

http://www.gnu.org/software/hello/manual/gettext/PO-Files.html

One PO file entry has the following schematic structure:

white-space
#  translator-comments
#. extracted-comments
#: reference...
#, flag...
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string

A simple entry can look like this:

#: lib/error.c:116
msgid "Unknown system error"
msgstr "Error desconegut del sistema"

It is also possible to have entries with a context specifier. They look like this:

white-space
#  translator-comments
#. extracted-comments
#: reference...
#, flag...
#| msgctxt previous-context
#| msgid previous-untranslated-string
msgctxt context
msgid untranslated-string
msgstr translated-string

The context serves to disambiguate messages with the same untranslated-string. It is possible to have several entries with the same untranslated-string in a PO file, provided that they each have a different context. Note that an empty context string and an absent msgctxt line do not mean the same thing.

Contexts and CubicWeb

CubicWeb PO files have both non-contextual and contextual msgids.

Contextual entries are automatically used in some cases. For instance, entity.dc_type(), eschema.display_name(req) or display_name(etype, req, form, context) methods/function calls will use them.

It is also possible to explicitly use the with _cw.pgettext(context, msgid).

Specialize translation for an application cube

Every cube has its own translation files. For a specific application cube it can be useful to specialize translations of other cubes. You can either mark those strings for translation using _ in the python code, or add a static-messages.pot file into the i18n directory. This file looks like:

msgid ""
msgstr ""
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgig "expression to be translated"
msgstr ""

Doing this, expression to be translated will be taken into account by the i18ncube command and additional messages will then appear in .po files of the cube.