I've
blogged a few times about
ClapTraps, the ingenious
free software puzzle game written in
PyGame and
Python by
testpilotmonkey. The
last time I blogged about it, I mentioned I was planning to add i18n (internationalisation) and a bit of l10n (localisation) to the game.
Claptraps in British English
In terms of internationalisation, what I wanted to do was to make the game multi-lingual so that on my daughters' computer, where they use Hungarian rather than English, they would be able to play the game in Hungarian.
I had been under the mistaken impression that all I needed to do to prepare a Python script for i18n was to import the
gettext library and then surround all translatable strings with the function
_().
It turned out to be a little bit more complicated than that. The reason was that I wanted ClapTraps to stay self contained in a single folder rather than force the user to install it.
ClapTraps' self-contained folder structure
Normally when
GNU/Linux programs are installed on a computer, the compiled translation scripts are copied to a central location for access by all users of the computer. On
Fedora GNU/Linux, that location is
/usr/share/locale. Python always expects the compiled translation files to be stored there. However, I wanted my translations to remain in the ClapTraps folder.
It would have taken me quite some time to work out what to do if
Mark Mruss had not already solved all the problems for me in his
excellent blog post on translating Python/PyGTK programs. There's a fantastic snippet of code there called "Translation stuff" that magically does it all!
The next step, now I had the Python script prepared, was to generate a POT (Portable Object Template) file. The POT file is the template file from which individual translations can be prepared.
The POT file for ClapTraps
It contains a header, then a list which shows the line a translatable string appears in, the id of the translatable string, and a space for the translation.
I found one problem when first creating my POT file was that it only had five strings in it! The reason was that testpilotmonkey had used single quotes for most of his strings but
xgettext, the tool you use to create a POT file, only recognises double quotes.
From my POT file, I used the
msginit tool to create two PO (Portable Object) files - one for en_GB (British English), the other for hu (Hungarian). Now came the really hard part - the Hungarian translation! I did this myself, but it took me three days until I was happy. The main problem was my difficulty with the imperative mood in Hungarian. Fortunately the excellent website
HungarianReference.com helped enormously. My Hungarian is still a bit dodgy, but I like to think that adds to the charm.
Finished PO file with Hungarian translation
Now all I needed to do was compile my PO files in MO files and I could see if it worked. To test the game I used the LANG variable from the command language.
To force the game to run in Hungarian whilst running Fedora 13 in British English I typed
LANG=hu python claptraps and....
Magyar and testpilotmonkey - a tricky combination!
Success! That was a lovely feeling. Now the next challenge was a bit of l10n. The problem I faced was that ClapTraps regularly asks the user to press Y for Yes or N for No. But in Hungarian that should be I for Igen or N for Nem. So I needed some way of changing the keys that you need to press to suit the current language.
This turned out to be quite easy - first I had to import the
locale library. Next, I just needed to ask the
locale class for the initial letters used for "yes" and "no" in a language like this:
# Make lists of "yes" and "no" keys for current language
locale.setlocale(locale.LC_ALL, '')
yes_keys=list(locale.nl_langinfo(locale.YESEXPR)[2:-3]);
no_keys=list(locale.nl_langinfo(locale.NOEXPR)[2:-3]);
The
nl_langinfo function returns a regular expression string with the acceptable keys for Yes or No for the current locale. For English it would be:
^[yY].*
^[nN].*
For Hungarian it would be:
^[IiyY].*
^[nN].*
Note that Y is also acceptable for Hungarian. I used the Python slice operator to slice off the bits I didn't want and then bunged the result in a variable. So now, when I want the user to press yes or no I simply do this:
while(1):
wait_event = pygame.event.wait()
if wait_event.type == pygame.KEYDOWN:
if pygame.key.name(wait_event.key) in yes_keys:
return False
elif pygame.key.name(wait_event.key) in no_keys:
program_quit = False
break
else:
pass
And that solves it. Since I made these changes to ClapTraps my daughters have both had hours of fun out of the game. It's a tribute to the genius of testpilotmonkey - and
Richard Stallman. For, without the
GPL, I wouldn't have been free to make these changes which allowed my children to enjoy this fantastic piece of software.