Use Babel to translate your python package

I use egg-containing buildouts for all my plone packages. Since i18ndude does not extract msgids from zcml files, i tried Babel and succeeded.

Problem

You want to translate titles in Plone’s diplay menu by adding browser:menuItem in your zcml file as shown here.

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:plone="http://namespaces.plone.org/plone"
    xmlns:i18n="http://namespaces.zope.org/i18n"
    i18n:domain="my.package">

    <browser:page
        for="plone.folder.interfaces.IOrderableFolder"
        name="a_new_view"
                class=".demo_view.DemoView"
                permission="zope2.View"
                template="templates/demo_view.pt"
                />

    <!-- Entry in display menu -->
    <browser:menuItem
        for="plone.folder.interfaces.IOrderableFolder"
        menu="plone_displayviews"
                title="A new view"
                action="@@a_new_view"
                description="I want a view with translated title and description"
                i18n:attributes="title; description"
                />

</configure>

i18ndude does not extract message ids from zcml files, so i tried a diffent solution using Babel.

Update your setup.py file

First of all you should tune your setup.py file as shown below. Just replace my.package in all code-snippets with your package name. You need to add an additional requirement to lingua.

setup(name='my.package',
      version=version,
      ...
      install_requires=[
          ...
          'lingua==1.6',  # lingua 2.0 has a new api
      ],
      extras_require={
          'test': [
              'plone.app.testing [robot]',
          ],
      },
      message_extractors={
          'src': [
              ("**.py", "lingua_python", None),
              ("**.pt", "lingua_xml", None),
              ('**.zcml', "lingua_xml", None),
              ('**.xml', "lingua_xml", None),
          ],
      },
      entry_points="""
      # -*- Entry points: -*-
      [z3c.autoinclude.plugin]
      target = plone
      """,
      )

Configure buildout

You’ll need to update your egg-contained buildout.cfg to generate a new interpreter which includes Babel and a small script - i’ll call it update_translations - which does all the magic.

[buildout]
# ...
develop = .
parts =
    # ...
    babelpy
    update_translations

# ...

[babelpy]
recipe = zc.recipe.egg
eggs =
    setuptools
    Babel
    lingua==1.6
interpreter = babelpy

[update_translations]
recipe = collective.recipe.template
output = ${buildout:directory}/bin/update_translations
input = inline:
    #!/bin/bash
    # XXX: Update your package name and path here
    # --------------------------------------------
    DOMAIN="my.package"
    BASE_PATH=${buildout:directory}/src/my/package
    # --------------------------------------------
    # No modifications below
    ${buildout:directory}/bin/${babelpy:interpreter} ${buildout:directory}/setup.py extract_messages \
        -o $BASE_PATH/locales/$DOMAIN.pot \
        -w 79

    # sync all locales, create if they do not exist
    for PO_FILE in `find $BASE_PATH/locales -maxdepth 1 -mindepth 1 -type d \
         | grep -v .svn \
         | sed -e "s/.*locales\/\(.*\)$/\1/"`; do
        if [ ! -f $BASE_PATH/locales/$PO_FILE/LC_MESSAGES/$DOMAIN.po ]; then
            echo "Create $BASE_PATH/locales/$PO_FILE/LC_MESSAGES/$DOMAIN.po"
            touch $BASE_PATH/locales/$PO_FILE/LC_MESSAGES/$DOMAIN.po
        fi
        ${buildout:directory}/bin/${babelpy:interpreter} \
            ${buildout:directory}/setup.py update_catalog \
            -l $PO_FILE -i $BASE_PATH/locales/$DOMAIN.pot \
            -o $BASE_PATH/locales/$PO_FILE/LC_MESSAGES/$DOMAIN.po
    done
mode = 755

Now re-run buildout:

$ bin/buildout

Extract msgids and sync translations

After buildout succeeded you should have two new executeables in your bin/ directory. You can ignore bin/babelpy it will just be used by your generated script.

$ bin/update_translations
running extract_messages
extracting messages from src/my/__init__.py
extracting messages from src/my/package/__init__.py
extracting messages from src/my/package/configure.zcml
extracting messages from src/my/package/testing.py
extracting messages from src/my/package/browser/__init__.py
extracting messages from src/my/package/browser/configure.zcml
extracting messages from src/my/package/browser/demo_view.py
extracting messages from src/my/package/browser/templates/demo_view.pt
extracting messages from src/my/package/browser/templates/demo_viewlet.pt
extracting messages from src/my/package/content/__init__.py
extracting messages from src/my/package/content/configure.zcml
extracting messages from src/my/package/content/demo.py
extracting messages from src/my/package/profiles/default/metadata.xml
extracting messages from src/my/package/profiles/default/propertiestool.xml
extracting messages from src/my/package/profiles/default/types.xml
extracting messages from src/my/package/profiles/default/types/my.package.xml
extracting messages from src/my/package/tests/__init__.py
extracting messages from src/my/package/tests/test_demo.py
writing PO template file to /home/daniel/workspace/my.package/src/my/package/locales/my.package.pot

If you create multiple language folders in your src/my/package/locales directory, all languages will be updated automatically. Even if there is no .po file in there it will be created for you and you’ll see following additional lines when calling update_translations (for example having de and fr as additional languages):

running update_catalog
updating catalog '/home/daniel/workspace/my.package/src/my/package/locales/de/LC_MESSAGES/my.package.po' based on '/Users/daniel/Workspace/my.package/src/my/package/locales/my.package.pot'
running update_catalog
updating catalog '/home/daniel/workspace/my.package/src/my/package/locales/fr/LC_MESSAGES/my.package.po' based on '/Users/daniel/Workspace/my.package/src/my/package/locales/my.package.pot'

Update *.zcml files and include locales folder

Don’t forget to include your locales folder in configure.zcml file.

<configure
    xmlns:i18n="http://namespaces.zope.org/i18n"
    xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
    i18n:domain="my.package">

    <i18n:registerTranslations directory="locales" />

</configure>

Important: You’ll have to use i18n:domain in your zcml files instead of i18n_domain otherwise Babel will not extract your zcml message ids.

Note

Version note: Because there were a lot of changes in lingua >= 2.0, this tutorial ist for lingua < 2.0 only.

Comments

comments powered by Disqus