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 inlingua >= 2.0, this tutorial ist for lingua < 2.0 only.