Internationalization (i18n) For Developers
Introduction
Or, "A guide to cultural understanding (and avoiding invasion from those barbarian European hordes)".
Plone's internationalization relies on essentially two things:
- developers identifying those phrases in their product that need translation
- a translator creating a special translation file for each language
This guide covers the first part, and is aimed at general product developers. After reading this, you should be able to create or modify products so that it will be easy for translation team members to create the translations.
Getting started:
Let's consider a very simple page template:
<html>
<body>
<p>Welcome to Plone.</p>
<img src="plone.gif" alt="Plone Icon" />
<p>There have been over
<span tal:content="here/download_count">100,000</span>
downloads of Plone.
</p>
<p>Please visit
<a href="about">About Plone</a>
for more information.
</p>
</body>
</html>
There are several things here we need to translate. Understanding these different needs will help you immensely in learning how to make your ZPTs translateable.
- "Welcome to Plone" should be translated directly to a different language.
- The alt tag text for the image, "Plone Icon"
- The phrase "There have been over 100,000 downloads of Plone." However, in this case, we want to make sure that the number is still calculated dynamically. One important thing is that the structure of this sentence may vary: in English, the sentence structure is as above; in other languages, the number may come at the start, the end, or other places in the sentence.
- The phrase "Please visit About Plone for more information.", however, we want to make sure that the "About Plone" remains a link, and that this can be translated separately. In addition, like the point immediately above, the position of the link name in the actual sentence may be different in other languages.
- In some languages, the sentence "There have been over ${number} downloads of Plone" will have different translations depending on the value of ${number}. This case is not currently supported by Plone and Zope.
Case One: "Welcome to Plone"
In this case, we want to tell the translation team that this is a single concept to translate, without any text in it that must be treated specially or dynamically calculated.
To do this, we merely need to use the i18n:translate attribute with the value "XXX" in the enclosing <P> tag.
For our example:
<p i18n:translate="XXX">Welcome to Plone.</p>
Now, when the translation team uses their tools (e.g. i18ndude) to extract all the msgids from the page templates, they can conveniently look-up the messages that need to be given a message id.
Ultimately, the translation team will choose a message identifer (msgid), effectively replacing "XXX" to uniquely identify this phrase, and that will become the new value of the i18n:translate attribute.
Case Two: The Alt Text of the Image
In this case, we need to let the team know that it's the value of an attribute, not the contents of the tag itself, that needs translating.
To do this, we'll use the i18n:attributes attribute.
This attribute should list all of the tag's attributes that contain text that must be translated. If you have more than one attribute, you should separate them with semicolons.
For our example:
<img src="plone.gif" alt="Plone Icon" i18n:attributes="alt" />
An example with two attributes would be:
<img src="plone.gif" alt="Plone Icon" title="Plone Icon Title"
i18n:attributes="alt; title">
In Zope 2.7 and later, an additional feature has been added that will allow the translation team to identify not just the actual attribute to translate, but also note what the msgid for that attribute will be. If this feature is used, it is done as a semicolon-separated list, such as:
<img src="plone.gif" alt="Plone Icon" title="Plone Icon Title"
i18n:attributes="alt plone-icon; title plone-icon-title">
meaning that we have two dynamic attributes: alt, with msgid "plone-icon", and title, with msgid "plone-icon-title".
This doesn't really matter if you're creating a new template, as you shouldn't be trying to assign msgids anyway (that's the job of the translation team); however, if you're editing a template that's already been handled by the translation team, this will help you understand what you're seeing.
Case Three: Dynamic content
Our third case was:
<p>There have been over
<span tal:content="here/download_count">100,000</span>
downloads of Plone.
</p>
Here, we'll use the i18n:name attribute. This gives us a way to identify that part of a longer piece to be translated should be represented as an individual piece that can be moved around.
For example, we want our translators to see our text as something like:
There have been ${count} downloads of Plone.
So that they can translate that, moving the actual count elsewhere in the sentence, as needed.
So, using i18n:name, we give a name to the dynamically calculated part:
<p i18n:translate="">There have been over
<span tal:content="here/download_count"
i18n:name="count">100,000</span>
downloads of Plone.
</p>
Note that the 'name' does not need to have any relationship to the TAL expression that calculates it. Also, the 'name' is only relevant in the context of the enclosing tag (the paragraph tag in our case), so it only has to be unique within that tag. Therefore, you can assign these, and don't have to let the i18n team do that for you.
A case where there are more than one replaceable:
<p i18n:translate="">My name is
<span tal:content="here/first" i18n:name="first">first</span>
<span tal:content="here/last" i18n:name="last">last</span>
</p>
The translation team will see this as:
My name is ${first} ${last}
which is exactly what we want.
Case Four: Combining Ideas
In case four, we are combining ideas from all of our previous cases.
We have:
<p>Please visit
<a href="about">About Plone</a>
for more information.
</p>
We want the sentence to come to the translators as:
Please visit ${about-plone} for more information.
But, unlike our dynamic content case, above, we want this internal phrase, "About Plone", to be translated as well.
To do this, we'll put an i18n:name attribute on the link tag, and put an i18n:translate tag around it, so that translators can see that it requires translation itself. So:
<p i18n:translate="">Please visit
<span i18n:name="about-plone">
<a href="about" i18n:translate="">
About Plone</a>
</span>
for more information.
</p>
To add in our second case, if we had an attribute of the link tag (such as 'title') that needed to be translated, we could add that, too:
<p i18n:translate="">Please visit
<span i18n:name="about-plone">
<a href="about" i18n:translate=""
i18n:attributes="title" title="Go to About Page">
About Plone</a>
</span>
for more information.
</p>
Tips and Pitfalls
Don't try to be too smart.
It's possible to do something like:
<p i18n:translate="">
Cart has <tal:block replace="number">#</tal:block>
book<tal:block condition=
"python: number <> 1">s</tal:block>.</p>
to dynamically output "book" or "books" depending on whether one or more than one book is in the cart.
Such tricks make the work of the documentation team very difficult, since some languages do not have a consistent way of pluralizing words, and many do not use the common English-style case of added a letter or two at the end.
It is better to simply say:
<p i18n:translate="">
Cart has <tal:block replace="number">#</tal:block> books.
</p>
Never mix i18n:name and i18n:translate attributes at same tag
If you want to reuse some translations, you should separate the i18n:name and i18n:translate attributes. Do not say:
<p i18n:translate="text_long">
Bar
<a href="" i18n:name="foo"
i18n:translate="text_used_other_places">
Foo
</a>
Baz
</p>
Instead, you should say:
<p i18n:translate="text_long">
Bar
<span i18n:name="foo">
<a href="" i18n:translate="text_used_other_places">Foo</a>
</span>
Baz
</p>
Include everything in the tag
Include the entire phrase or sentence in the tag that has i18n:translate, including punctuation. Do not say:
<span i18n:translate="">The participants</span>:
(note the trailing colon). As many languages do not use colons to end a declarative introduction, this would be rendered inappropriately.
Instead, simply say:
<span i18n:translate="">The participants:</span>
Which allows the translation team to make this decision.
Never pass back HTML from Python Scripts
Returning rendered HTML from a PythonScript (or other non-ZPT generator) makes it essentially impossible for the translation team to do the right thing with your page.
Instead, do what you should be doing anyway: use ZPT for markup, and use scripts to generate data structures for markup by ZPT (this is also easier to debug, and leads to generally better concepts and code reusability).

Author: