Wednesday, December 24, 2008

Disguising context.translate() as _

Edit (Dec. 2009): I currently think that it's better to define MyProductMessageFactory in __init__.py, and import that as _ in your scripts and filesystem code.
from zope.i18nmessageid import MessageFactory
MyProductMessageFactory = MessageFactory('MyProduct')

In the case of scripts, you'll have to add a security declaration:

from AccessControl.SecurityInfo import ModuleSecurityInfo
security = ModuleSecurityInfo('Products.MyProductMessageFactory')
security.declarePublic('MyProductMessageFactory')

And of course, see also the link that Mikko added: http://worldcookery.com/files/fivei18n/

The outdated manual



To make i18ndude aware of the strings you pass context.translate(), you can wrap these calls in a '_' method like so (example taken from PloneInvite):
def _(message):
""" Fake MessageFactory, allows calling context.translate() as _(u"") in
order to be able to use i18ndude on this script.

We want to use context.translate, but if we do, its input strings aren't
recognized by i18ndude as translatable strings. If we call it as _(u""),
i18ndude will recognize it and create translation strings in the .pot file.
"""
domain = 'PloneInvite'
msgid = message
return context.translate(default=message, domain=domain, msgid=msgid)

Now you can call it as portalmessage = _(u"Sent invitation to %s.") % invite_to_address, and i18ndude will create the appropriate strings in the .pot file.

If you have several scripts in your product which produce messages that should be translated, you might want to consider moving the wrapper method to a separate class. In PloneInvite, i added this to the __init__.py:
# FakeMessageFactory for translating strings in scripts
from AccessControl import ModuleSecurityInfo
from fakemessagefactory import FakeMessageFactory
ModuleSecurityInfo('Products.PloneInvite').declarePublic('FakeMessageFactory')

fakemessagefactory.py reads:
class FakeMessageFactory:

def __init__(self, context, domain):
self.context = context
self.domain = domain

def __call__(self, message):
msgid = message
return self.context.translate(default=message, domain=self.domain, msgid=msgid)

And i call it in the script like this:
from Products.PloneInvite import FakeMessageFactory
_ = FakeMessageFactory(context,'PloneInvite')

portalmessage = _(u"Sent invitation to %s.") % invite_to_address

Translating messages in python controller scripts

For some reason, using Plone's MesageFactory didn't work for me. However, context.translate() does all the work. Call it like so:
context.translate(
msgid = u'some_message',
default = u'Some message',
domain = 'myproductsi18domain')

and make sure you have something like
"Domain: myproductsi18domain\n"

#. Default: "Some message"
#: skins/myproduct/your_controller_script.cpy:18
msgid = "some_message"
msgstr = "Een bericht"

in the relevant file (probably i18n/myproductsi18ndomain-nl.po).

This has the disadvantage that the message strings are not recognized by i18ndude, so i18ndude will not automatically create the relevant strings for you in your .pot file. There's a workaround for this, but that's another blog post.

Tuesday, December 23, 2008

Spreading strings over multiple lines in .po files

It's a bother working with i18n files that have strings wider than your editor screen. But it's possible to cut up these long strings, and put the parts one different lines in your .po file. The string is easier to read and edit this way:

msgstr ""
"${name} invited you to join this site.\n"
"\n"
Click this link to join:\n"
"\n"
"${link}\n"


This is handy in the case of e-mail templates. Of course, the \n's are here to render line breaks in the translated message, the have nothing to do with line breaks in the .po-file.

Thursday, December 11, 2008

Saving prompt space

When using vi's :tabe command (opens a file in another "tab" in the same terminal window), i noticed it shows the path to the file in an abbreviated form: /home/kees/path/to/file becomes /h/k/p/t/file. This saves space, while preserving some information about where you are.

This seems like a useful feature in a prompt as well. For example, when \u@\h \w \$ yields something as long as ~/Documents/manuals/plone/code for the aspeli book/optilux-code-2007-10-21/chapter-09/examples, you're likely to get annoyed after a while.

So now my PS1="\u@\h \$(abbr_path.sh) \$ ", and the script is pwd | perl -p -e 's/([^\/])[^\/]*[\/]/$1\//g'. Let's see how long this setup keeps me happy. I might also replace the regexp with 's/([^\/]{1,3})[^\/]*[\/]/$1\//g' if i want more than one character per directory.