Friday, July 29, 2005

Poor man's i18n

Internationalization (i18n) of a [Python] program appears to be daunting task. After looking on the web, I haven't been able to find a simple tutorial explaining the steps to be taken; the closest that I have found is the wxPython one, on the wxPython wiki. The procedure described appears to be rather involved, even if one only deals with translations of text (and not localization of dates, currencies, etc.), which is what I will focus on.

The first step is the replacement of all strings of the form:
"This needs to be translated"
by the following call (interpreted to be a C macro in Gnu's gettext)
_("This needs to be translated")
which is very simple.

The standard way then requires the use of gettextand results in the creation of ".pot" files (Portable Object Templates) to be copied and translated in ".po" (Portable Object) files by a human translator; these are then converted into ".mo" (Machine Object) files by a compiler. Yet, a few more steps, mostly dealing with directory structures and setting up locales, are needed before one can conclude the process.

I present here a simpler way to proceed which, at a later time, can easily be converted to the standard gettext method as it basically uses the same "_()" notation required by gettext. This was inspired by a comp.lang.python post by Martin v. Löwis to a question I asked almost a year ago.

Consider the following simple (English) program [Note: "." are used to indicate indentation as Blogger appears to eat up leading spaces]
def name():
....print "My name is Andre"

if __name__ == '__main__':
....name()
The French translation of this program would be

# -*- coding: latin-1 -*-
def name():
....print u"Je m'appelle André"

if __name__ == '__main__':
....name()
Without further ado, here's the internationalized version using a poor man's i18n method and demonstrating how one can easily switch between languages:

from translate import _, select

def name():
....print _("My name is Andre")

if __name__ == '__main__':
....name()
....select('fr')
....name()
....select('en')
....name()
The key here is the creation of the simple translate.py module:
__language = 'en'  # leading double underscore to avoid namespace collisions

def select(lang):
....global __language
....__language = lang
....if lang == 'fr':
........global fr
........import fr
def _(message):
....if __language == 'en':
........return message
....elif __language == 'fr':
........return fr.translation[message]
together with the language-specific fr.py module containing a single dictionary whose keys are the original English strings.:
# -*- coding: latin-1 -*-

translation = {
"My name is Andre" : u"Je m'appelle André"
}
That's it! Try it out!

In conclusion, if you want to make your programs translation friendly, all you have to do is:
  1. replace all "strings" by _("strings")
  2. include the statement "from translate import _, select" at the beginning of your program.
  3. create a file named "translate.py" containing the following:
def select(lang):
....pass

def _(message):
....return message
and leave the rest to the international users/programmers; you will have done them a huge favour!