Quantcast
Channel: Planet Plone - Where Developers And Integrators Write
Viewing all articles
Browse latest Browse all 3535

Four Digits: Translating the Zope backend application

$
0
0

There is no spoon.

Translating Plone modules or applications is not that hard, but you need to know the tricks and not get any unexpected errors on your way. I will show you my experiences and errors on the way to translate a litte widget built in plain Zope.

My situation: Zope, python 2.6, a widget sending mails using TAL templates needed in 3 languages, My freshly installed mac.

Virtualenv and i18ndude

First up, how do we translate templates or python files?  To my knowledge, we need locales and po files. After some googling I'm going to use i18ndude to generate the files for me. It sounds pretty nice because it will scan all templates for i18n tags and create the multiple po files for me.

Virtualenv and my path

As written at: http://www.coactivate.org/projects/opencore/i18n-usage-in-opencore I should not simply install i18ndude without creating some kind of virtual environment.

Important! The reason: You cannot install i18ndude globally, because it will install newer versions of various zope libraries including zope.tal, and zope will break (see this bug: http://plone.org/products/i18ndude/i18ndudetracker/17)

Luckily there is also a nice howto, lets follow it. Because I know I don't have virtualenv yet, I'm installing this first.

easy_install-2.6 virtualenv

The installation is succesful; Continue;

cd ~
mkdir bin
cd bin
mkdir i18ndude-virtualenv
virtualenv i18ndude-virtualenv

ARG! The first error, and it sounded so easy.

Kabz-MacBook:bin maarten$ virtualenv i18ndude-virtualenv
-bash: virtualenv: command not found
Kabz-MacBook:bin maarten$ 

My mac is freshly installed so chances are that there is not even a pyhon path defined in my settings, let's have a look;

cd ~
vi .profile
export PATH=/opt/local/bin:/opt/local/sbin:$PATH:/Users/maarten/bin

As expected, there is no python path here. Let's find it and add it. The command which python2.6 will show me the symlink. This is not the path where the easy_installed packages are going, that's why I need the real path.

Kabz-MacBook:~ maarten$ which python2.6
/opt/local/bin/python2.6

Kabz-MacBook:~ maarten$ ls -ahl /opt/local/bin | grep python2.6 python2.6 -> /opt/local/Library/Frameworks/Python.framework/Versions/2.6/bin/python2.6 cd ~
vi .profile
export PATH=/opt/local/Library/Frameworks/Python.framework/Versions/2.6/bin/python2.6:/opt/local/bin:/opt/local/sbin:$PATH:/Users/maarten/bin

Save the .profile and close the terminal, otherwise our new PATH isn't used. Let's try it again;

cd ~/bin/
virtualenv i18ndude-virtualenv

This works better now! Continue following the command list!

. i18ndude-virtualenv/bin/activate
easy_install-2.6 i18ndude
deactivate
ln -s i18ndude-virtualenv/bin/i18ndude .

Everything is done, and we are now able to do ~/bin/i18ndude, whatever that may be :)

 

Generate PO files

So http://maurits.vanrees.org/weblog/archive/2010/10/i18n-plone-4 ( many thanks again! )

There is a script called rebuild_i18n.sh.

#! /bin/sh

I18NDOMAIN="mypackage.widget"

# Synchronise the templates and scripts with the .pot.
# All on one line normally:
i18ndude rebuild-pot --pot locales/${I18NDOMAIN}.pot \
    --merge locales/manual.pot \
    --create ${I18NDOMAIN} \
   .

# Synchronise the resulting .pot with all .po files
for po in locales/*/LC_MESSAGES/${I18NDOMAIN}.po; do
    i18ndude sync --pot locales/${I18NDOMAIN}.pot $po
done

We have to put this file in the package to translate;
In my case: /opt/widgetbackend/src/mypackage.widget/mypackage/widget/rebuild_i18n.sh

We can run the script; As always, it never works the first time! Error: files missing;

Kabz-MacBook:widget maarten$ ./rebuild_i18n.sh 
Warning: locales/*/LC_MESSAGES/mypackage.widget.po is not a file or is ignored.
Kabz-MacBook:widget maarten$ 

First we need to create the files and all languages that we require; To create the Dutch language file, execute the following commands, which includes creating an empty po file.

cd locales
mkdir nl
cd nl
mkdir LC_MESSAGES
cd LC_MESSAGES
touch mypackage.widget.po 

Finally run the script and get the po files ( this is the outcome when one line is found. )

Kabz-MacBook:widget maarten$ ./rebuild_i18n.sh 
locales/nl/LC_MESSAGES/mypackage.widget.po: 1 added, 0 removed
locales/de/LC_MESSAGES/mypackage.widget.po: 1 added, 0 removed

We've now got ourselves some nice po files.

 

PO files explained

Let's have a look at the generated .po files from i18ndude. They start with information about the author, the package name and the language. We change all this to match our situation. This one I changed a little so it's for the Dutch translation.

msgid ""
msgstr ""
"Project-Id-Version: mypackage.widget\n"
"POT-Creation-Date: 2011-01-27 12:26+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0\n"
"Language-Code: nl\n"
"Language-Name: Dutch\n"
"Preferred-Encodings: utf-8 latin1\n"
"Domain: mypackage.widget\n"

#: ./browser/templates/forgot_password.pt:3
msgid "Widget forgot my password"
msgstr ""

To translate the line found by i18ndude, we enter the Dutch string at the msgstr;

#: ./browser/templates/forgot_password.pt:3
msgid "Widget forgot my password"
msgstr "Widget wachtwoord vergeten"

That's all, we can do this for all languages now. When we re-run ./rebuild_i18n.sh it will keep the changed lines and will only add new ones found.

Starting Zope

We got some translation files and a simple Zope instance. We need to tell Zope it needs to compile the po files; This can be done by adding the following lines to the instance part of our buildout;

environment-vars =
    zope_i18n_compile_mo_files true

We need to re-run the buildout to set these changes; After that we can start our instance; ./bin instance fg; And let's, once again, get an error like this one:

CRITICAL zope.i18n Unable to compile messages: Python `gettext` library missing

We google and get zero hits, always nice. I did some research and the solution is to install python-gettext:

easy_install-2.6 python-gettext

The instance starts and the mo files are generated; \o/

 

Template

To setup all the templates to be translated, we have to add the i18n domain at the top of the file. In this case we add 'i18n:domain="mypackage.widget".

<body i18n:domain="mypackage.widget">
<p> Hi, <br /</p>
<p> Click the link to open
<a href="" tal:attributes="href options/link">use this link</a>
to view your card.
</p>
</body> 

To translate a single line we can add i18n:translate="".

<p i18n:translate="">Hi, <br /></p>

If we re-run ./rebuild_i18n.sh again, it will find 1 new line and add this to our template. And we are able to start our instance. Let's continue translating;

<p i18n:translate=""> Click the link to open 
<a href="" tal:attributes="href options/link">use this link</a>
to view your card.
</p>

When we start the instance, we get the following error:

2011-01-28 10:44:29 INFO ZServer HTTP server started at Fri Jan 28 10:44:29 2011
	Hostname: 0.0.0.0
	Port: 8080
2011-01-28 10:44:31 WARNING zope.i18n Error while compiling /opt/widgetbackend/src/mypackage.widget/mypackage/widget/locales/de/LC_MESSAGES/mypackage.widget.po
2011-01-28 10:44:31 WARNING zope.i18n Error while compiling /opt/widgetbackend/src/mypackage.widget/mypackage/widget/locales/en/LC_MESSAGES/mypackage.widget.po
2011-01-28 10:44:31 INFO Zope Ready to handle requests

It sounded so easy. Let's view our po files;

#: ./browser/templates/email.pt:37
msgid "Click the link to open <a href="${DYNAMIC_CONTENT}">use this link</a> to view your card."
msgstr ""

We need to setup an i18n:name for the link;

<p i18n:translate="">Click the link to open
<a href="" i18n:name="link" tal:attributes="href options/link"> use this link </a> to view your card.
</p> 

The outcome after re-runnning rebuilt_i18n.sh

#: ./browser/templates/email.pt:37
msgid "Click the link to open ${link} to view your card."
msgstr "" 

The instance starts fine now.

 

Translate Py file

Next up, translate one single string in our ajax.py file, too bad it was there. But it was used to set the subject for the email. The text line in my file;

title = u"widget - forgot my password"

We need to tell i18ndude there is a translateable line in the code.

from zope.i18nmessageid import MessageFactory
from zope.i18n import translate
_ = MessageFactory("mypackage.widget")
title = _(u"widget - forgot my password")

This way we have set up our domain and added the title string to the messagefactory. When running rebuild_i18n.sh again, we get our line in the .po files.

#. Default: "widget - forgot my password"
#: ./browser/ajax.py:133
msgid "widget - forgot my password"
msgstr ""

This is not working ( after restarting the zope serveral times ) because the translating needs to be done by a given ID.  The way to set this up:

title = _(u"forgot-password-message",
            u"widget - forgot my password")

#. Default: "widget - forgot my password"
#: ./browser/ajax.py:133
msgid "forgot-password-message"
msgstr "wachtwoord vergeten"

If it doesn't work; try deleting all the .MO files and restart Zope; Turns out we have to tell the line to be translated.

title = translate(_(u"forgot-password-message",
            u"widget - forgot my password"))

 

Lesson learned?

Some hours of pain and searching the internet for solutions to just a simple task. But I've got a decently translated widget now www.coolwidget.nl. I'm able to re-run the rebuild_i18n.sh and get updated .po files from all the i18n: tags used in multiple languages at once. In the end this saves a lot of time!

Never trust a task to be simple, but in the end when we've got everything documented, enjoy the result! :)


Viewing all articles
Browse latest Browse all 3535

Trending Articles