Plone翻译机制及在Plone3和Plone4的不同

Plone翻译机制及在Plone3和Plone4的不同
[align=center] Plone翻译机制及在Plone3和Plone4的不同 [/align]
[align=left]内容
[/align]
  • i18n 和 locales对比
  • po文件的名称和文件头格式
  • 在GenericSetup xml 文件中的域名
  • 在产品包中组合运用i18n 和 locales
  • i18ndude
  • Gotchas
  • 翻译profile文件的标题和描述
  • 为已存在的域追加翻译
  • 覆盖已有的翻译
  • 限制翻译机制载入的语言
  • 编译翻译文件
  • 结论
i18n 和 locales对比
当用自己的翻译域创建包时,我们应该用i18n目录还是locales目录?其实两种方式在Plone3和Plone4中都可运用,但i18n是较老的技术,将在Plone5以后不在支持。因此我们应优先选择locales目录技术,在configure.zcml中注册,如下:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:five="http://namespaces.zope.org/five" xmlns:i18n="http://namespaces.zope.org/i18n"> <five:registerPackage package="." initialize=".initialize" /> <i18n:registerTranslations directory="locales" /> </configure> 注意这里的名称“locales”没有什么特别之处,事实上这个名称可以是任意的目录名称,采用locales只是惯例。你也可将该名称定为"Quack",即:
<i18n:registerTranslations directory="Quack" />

Headers and name of po file
在locales目录,翻译机制并不关心头文件,po文件的头甚至可以是虚拟的无意义的什么,例如:
"Language-Code: en\n" "Language-Name: English\n" "Domain: non.existing\n"locales下的po 文件存放格式必须精准匹配“locales/languagecode/LC_MESSAGES/domainname.po”,例如,如果我要给my315ok.portlet.flash包添加一个中文的翻译包,那么该po文件存放路径应是“locales/zh/LC_MESSAGES/my315ok.portlet.flash.po“。注意当中的域名my315ok.portlet.flash是大小写敏感的,必须和你的产品包的名称完全一致。

在GenericSetup xml 文件中的域名
在GenericSetup 的配置文件中有一系列的 xml文件。这些配置文件怎样被翻译?

  • [align=left]actions.xml:用自己的域,如下例:[/align]
  • <object name="ducktest" meta_type="CMF Action" i18n:domain="collective.ducks"> <property name="title" i18n:translate="">Duck Test</property> <property name="description" i18n:translate="">Action: test a duck</property> ... </object>
  • [align=left]catalog.xml:无须翻译[/align]
  • [align=left]componentregistry.xml:无须翻译[/align]
  • [align=left]contenttyperegistry.xml: 无须翻译[/align]
  • [align=left]controlpanel.xml: 用自己的域,如下例:[/align]
  • <?xml version="1.0"?> <object name="portal_controlpanel" meta_type="Plone Control Panel Tool" i18n:domain="collective.ducks" xmlns:i18n="http://xml.zope.org/namespaces/i18n"> <configlet title="Duck Configuration Panel" action_id="Duck" appId="Duck" category="Products" condition_expr="" icon_expr="string:$portal_url/duck_icon.png" url_expr="string:${portal_url}/prefs_install_products_form" visible="True" i18n:attributes="title"> <permission>Manage portal</permission> </configlet> </object>
  • [align=left]cssregistry.xml:无须翻译[/align]
  • [align=left]diff_tool.xml:无须翻译[/align]
  • [align=left]factorytool.xml:无须翻译[/align]
  • [align=left]jsregistry.xml:无须翻译[/align]
  • [align=left]kssregistry.xml:无须翻译[/align]
  • [align=left]mailhost.xml:无须翻译[/align]
  • [align=left]memberdata_properties.xml:无须翻译[/align]
  • [align=left]metadata.xml:无须翻译[/align]
  • [align=left]portal_atct.xml: 用 plone 域.
  • [/align]
  • [align=left]portlets.xml: 用 plone 域.[/align]
  • [align=left]properties.xml:无须翻译[/align]
  • [align=left]propertiestool.xml:无须翻译[/align]
  • [align=left]rolemap.xml:无须翻译[/align]
  • [align=left]skins.xml:无须翻译[/align]
  • [align=left]toolset.xml:无须翻译[/align]
  • [align=left]types: 用自己产品包名称空间作为域[/align]
  • [align=left]viewlets.xml:无须翻译[/align]
  • [align=left]workflows: 用自己产品包名称空间作为域[/align]
在产品包中组合运用i18n 和 locales
在你的产品包中,产品包名称空间的域放在locales目录下,Plone域(给Plone域追加的翻译)应放在i18n目录下。

i18ndude 是Plone翻译机制一个很有用的工具。下例是一个采用i18ndude 来为Plone产品包自动创建翻译包的脚本程序:
#! /bin/sh I18NDOMAIN="collective.ducks" # 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
Gotchas

i18ndude 创建pot文件时,有两种处理机制:
  1. 对于template文件和xml文件,查找 i18n:translate,并比较相应的i18n:domain是否和要求创建的域一致,如果一致,则提取该msgid。
  2. 对于python源文件,则简单查找所有用下划线命名的函数,如: _('My translatable string').这个函数通常被这样定义:
from zope.i18nmessageid import MessageFactory _ = MessageFactory('collective.ducks')这里存在一个问题,i18ndude 扫描python源文件时,不会去比照字符串所从属的翻译域,而通常在python源文件中,同时存在plone翻译域和自己的产品包翻译域。这时我们应该区别哪些翻译字符串属于plone域,哪些属于自己的域。采取的办法是,将plone域的字符串翻译函数用新的名称定义(不用“_()")
如下例:
from Products.CMFPlone import PloneMessageFactory as _ message = _(u'One or more items not copyable.')应替换为:
from Products.CMFPlone import PloneMessageFactory as PMF message = PMF(u'One or more items not copyable.')这样i18ndude不再提取 'One or more itemsnot copyable.' ,该字符串的翻译来自系统的plone.app.locales 里。

翻译profile文件的标题和描述
假设有下面的configure.zcml:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:five="http://namespaces.zope.org/five" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" xmlns:i18n="http://namespaces.zope.org/i18n" i18n_domain="collective.ducks"> <five:registerPackage package="." initialize=".initialize" /> <i18n:registerTranslations directory="locales" /> <genericsetup:registerProfile name="default" title="Collect All Ducks" directory="profiles/default" description="Ducks of all nations: unite!" provides="Products.GenericSetup.interfaces.EXTENSION" /> </configure>i18ndude 不能提取 title 和 description, 因此只能人工添加,创建一个 locales/manual.po 文件用下面的内容:
# --- PLEASE EDIT THE LINES BELOW CORRECTLY --- # SOME DESCRIPTIVE TITLE. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. msgid "" msgstr "" "Project-Id-Version: collective.duck 1.0\n" "POT-Creation-Date: 2010-09-01 09:58+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\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: en\n" "Language-Name: English\n" "Preferred-Encodings: utf-8 latin1\n" "Domain: collective.ducks\n" #: Profile title in configure.zcml msgid "Collect All Ducks" msgstr ""#: Profile description in configure.zcml
msgid "Ducks of all nations: unite!"
msgstr ""
重建pot文件并归并manual pot 文件,如下:
i18ndude rebuild-pot --pot locales/collective.ducks.pot --merge locales/manual.pot --create collective.ducks .同步pot文件和po文件:
i18ndude sync --pot locales/collective.ducks.pot locales/nl/LC_MESSAGES/collective.ducks.po翻译你的po文件,重启Zope实例,去到控制面板的“插件安装”,应该就可以看到翻译后的效果。

为已存在的域追加翻译

在Plone 3, plone域的翻译在i18n目录完成。如果创建一个locales翻译机制,里面放入plone域的翻译文件,这将取消系统已有的所有关于Plone域的翻译。这种情况,应该创建i18n翻译机制。
在 Plone 4 ,应该将追加的翻译放到locales目录下。

注意,Plone 4 首先调入所有 locales 目录的翻译,然后才调入i18目录。

覆盖已有的翻译
在Plone 3.3 最好使用 collective.recipe.i18noverrides 来覆盖已有翻译。注意,不能用i18n目录来覆盖locales目录下的翻译。
在Plone 4 实例的i18n目录被完全忽略。可以创建一个plone包用来做翻译覆盖 ,or you add something to an existing package. Let's say you want to override the "You are here:"translation from the bread crumbs (path bar). In Dutch it says "Ubent hier:". We now want it to say: "U bent hier naartoegevlogen:".You locate the plone.po file for your translation and copy it toyour locales directory. In our case we have created a packagecalled customer.translations so copy this file:
plone.app.locales-4.0.0-py2.6.egg/plone/app/locales/locales/nl/LC_MESSAGES/plone.po
to this file:
customer.translations/customer/translations/locales/nl/LC_MESSAGES/plone.po
Then remove whatever translations you do not need to override, and doyour change, so you end up with a po file with some headers and this translation:
#. Default: "You are here:" #: plone.app.layout/plone/app/layout/viewlets/path_bar.pt:6 msgid "you_are_here" msgstr "U bent hiernaartoe gevlogen:"In your buildout.cfg make sure this package is added to the eggs.The tricky thing now is to make sure that the zcml for this package isloaded before the zcml of other packages; that way our translationswin. The way to do this, is to list it as the first (and possiblyonly) item in the zcml option, so something like this:
[instance] ... eggs = Zope2 Plone ${buildout:eggs} customer.translations zcml = customer.translationsWarning: this works in Plone 3 too, but it has a problem. In Plone 3the translations for the plone domain are in an i18n directory.If we add a plone.po file in our locales directory, the effect isthat all other translations from the plone domain are lost. In otherwords: if you override an i18n folder with your own localesfolder, all translations for that domain that are not in your pofile, are lost. So you would have to copy over the complete originalpo file. In that case it is probably easier to use the alreadymentioned collective.recipe.i18noverrides. To be clear: this istrue when using Plone 3.
To summarize this part:
  • If you want to override translations from an i18n directory:
  • If you want to override translations from a locales directory it is the same on Plone 3.3 and 4: copy the relevant part of the pofile to the locales directory of your own package. Copying the entire po file works as well of course, but is not needed. Also you need to make sure the zcml of your package is loaded first. OnPlone 3.3 it seemed less reliable, but that may be because I was meanwhile also combining i18n and locales directories for the same domain, which is not a good idea.
限制翻译机制载入的语言
(Update: includes info from Hanno aboutzope_i18n_allowed_languages.)
To speed up zope startup time and use less memory, you can set anenvironment variable to restrict the languages for which po files areloaded. To restrict them to English, Dutch and German, give this avalue of en, nl, de. The commas are optional. Inbuildout.cfg this would be something like this:
[instance] ... environment-vars = PTS_LANGUAGES en nl de zope_i18n_allowed_languages en nl de zope_i18n_compile_mo_files trueThe zope_i18n_compile_mo_files setting is optional, see the nextsection. But why do we specify the allowed languages twice? Some words fromHanno, who pointed me to zope_i18n_allowed_languages:
[indent]In Plone 3.3 there is only PTS_LANGUAGES and it affects both i18n andlocales folders. In Plone 4 there is alsozope_i18n_allowed_languages. The new one now affects locales foldersand the old one only affects i18n folders. So to get the full memorysaving affect of not loading too many translation files, you need tospecify both on Plone 4. Specifying the new one in Plone 3.3 does nothurt, it just does not do anything.[/indent]
Compiled translation files.po files need to be compiled before Plone can do something withthem. When you compile a domain.po file, you get a domain.mofile. This is the file that is read by the translation machinery whenlooking up a translation for a msgid. To compile a file manually, youneed the msgfmt program:
msgfmt -o domain.mo domain.po
If your package contains po files, Plone 3.3 compiles them whenstarting up your instance. In Plone 4 this is optional. For example,you might want to skip this when you do not need any translations atall in your site. Actually, it is skipped by default. To enable thecompile step, add the following to the instance or zeoclient part ofyour Plone 4 buildout.cfg:
environment-vars = zope_i18n_compile_mo_files trueNote that the value (true in this case) does not matter: therelevant code in zope.i18n simply looks for the existence of thevariable and does not care what its value is.
In Plone 3, any existing mo files in an i18n directory of a packageare ignored. Instead, when the Zope instance starts up, all po filesare compiled automatically and put in a directory likevar/instance/pts.
Also in Plone 3, any po files in a locales directory are compiledinside that same locales directory:locales/nl/LC_MESSAGES/domain.po is compiled to a filelocales/nl/LC_MESSAGES/domain.mo.
In Plone 4, the var/instance/pts directory is not used at all.All po files in an i18n or locales directory are compiled inside thatsame directory.
On startup, Zope (actually the PlacelessTranslationService) may seethat a po file already has an accompanying mo file in the correctdirectory. It then compares the last modification date of the twofiles. If the po file is more recent, then a new mo file is compiled.
Note that you should not put the compiled mo files in subversion (oranother version control system) as they can just be automaticallycreated. Once you release a package to the public, putting the mofiles in the released source could be handy though: if your packagewithout mo files is installed by for example the root user in adirectory where user zope has read access but not write access,then user zope will get an error when starting Plone.plone.app.locales does this correctly. I have not done thismyself yet (and some of my packages probably still contain mo files insubversion) so I will not further comment on how to do this. I dowant to add support for this in zest.releaser or in an optionalsupport package for that.
If you want to do it manually, this shell script does the trick (atleast when you have proper bash, find and msgfmt commandsavailable):
for po in $(find . -path '*/LC_MESSAGES/*.po'); do msgfmt -o ${po/%po/mo} $po; donePlus you will need to make sure mo files are included in the createdsource distribution by creating a MANIFEST.in file next to yoursetup.py file; this works for me:
recursive-include collective *
global-exclude *pyc
Update: Okay, I have just created and releasedhttp://pypi.python.org/pypi/zest.pocompile for this, for use in combination with zest.releaser or as stand-alone command line tool.

结论
  • 开发新的产品包时,用locales机制,创建自己的翻译域。
  • 当给现有的产品包添加翻译时,跟随该包的翻译机制,原来用i18n的就用i18,用locales的就用locales。
  • 当覆盖现有翻译时,plone4应确保你的产品包的zcml,在其他被覆盖的包前被载入,配置zcml顺序来实现;在 Plone 3用三方插件 collective.recipe.i18noverrides.
  • 采用locales机制时,肯定po文件精确匹配locales/languagecode/LC_MESSAGES/domainname.po ,并且注意域名是大小写敏感的。
  • 在下述GenericSetup配置文件中,用自己产品包的名称空间作为域名:
    • actions.xml
    • controlpanel.xml
    • types
  • 在下述GenericSetup配置文件中,用 plone 作为域名:
    • portal_atct.xml
    • portlets.xml
    • workflows
设置