Internationalization

Please send any fixes or suggestions to peter@majorsilence.com or leave a comment at http://www.majorsilence.com/pygtk_book.
Must install intltool package on linux systems to provide the tools and scripts that are needed to extract the needed information from the python scripts and the programs glade files.
  • gettext.bindtextdomain(domain, localedir) - Bind the text to main to the locale directory that is specified. Where the binary .mo files are looked for.
  • gettext.textdomain(domain) - Sets the current global domain to the domain argument. If domain is none then the current global domain is returned.
  • gettext.translation(domain, localedir, languages, class, fallback, codeset) - Set the domain and the locale directory. All this chapter will be interested in is the first two arguments domain and localedir.
  • gettext.install(domain, localedir, unicode, codeset, names) - Install the function _() in the python builtin namespace so that it may be used easily from any python module within a program.

10.1 Python/PyGTK Translation

To start off here is a very small program that has been setup for localization.
import pygtk, gtk
pygtk.require("2.0")
import locale, gettext
APP="translation-example"
DIR="po"
locale.setlocale(locale.LC_ALL, ’’)
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
lang = gettext.translation(APP, DIR)
_ = lang.gettext
gettext.install(APP, DIR)
To start off the variable APP is set to “translation-example” and is used to set the domain for the translation.
class TranslationExample(object):
def __init__(self):
self.label_1 = gtk.Label( _("Hello World!") )
label_2 = gtk.Label( _("Still in the HBox") )
button = gtk.Button( _("Click Me") )
button.connect("clicked", self.on_button_clicked, 
_("Anything can go here") )
vbox = gtk.VBox()
vbox.pack_start(self.label_1) vbox.pack_start(label_2) 
vbox.pack_start(button)
win = gtk.Window()
win.connect("destroy", lambda wid: gtk.main_quit())
win.add(vbox)
win.show_all()
def on_button_clicked(self, widget, data=None):  
self.label_1.set_text( _("Hello ") + str(data) )
if __name__ == "__main__":
TranslationExample()
gtk.main() 
For more indepth coverage of gettext visit http://docs.python.org/library/gettext.html. To download the tools from windows get them from the gnu site ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.13.1.bin.woe32.zip.
Now use the gettext command tool to extract the needed strings from all the python files and create the translation-example.pot file.
gettext --language=Python --keyword=_ --keyword=N_
--output=translation-example.pot translation-example.py
Now for each language that will be available for the application a .po file must be created. So if Canadian English is the language is to be used:
msginit --input=translation-example.pot --locale=en_CA
Will output a en_CA.po file. American English would be:
msginit --input=translation-example.pot --locale=en_US 
Will output an en_US.po file. German would be:
msginit --input=translation-example.pot --locale=de_DE
This of course would output de.po.
Finally the .po files must be edited and the localized language put into their proper places. Just make sure that when the .po files are created that the charset is set to utf-8.
# SOME DESCRIPTIVE TITLE. 
# Copyright (C) YEAR THE PACKAGE’S COPYRIGHT HOLDER 
# This file is distributed under the same license as the PACKAGE package. 
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. 
# 
#, fuzzy 
msgid "" 
msgstr "" 
"Project-Id-Version: PACKAGE VERSION\n" 
"Report-Msgid-Bugs-To: \n" 
"POT-Creation-Date: 2009-02-17 16:01-0330\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"
#: translation-example.py:20 
msgid "Hello "
msgstr ""
#: translation-example.py:23
msgid "Hello World!"
msgstr ""
#: translation-example.py:24
msgid "Still in the HBox"
msgstr ""
#: translation-example.py:25
msgid "Click Me"
msgstr ""
#: translation-example.py:29
msgid "Anything can go here"
msgstr ""
Now what you need to do is edit the .po files so that the empty msgstr have the translated text. So what this means is that:
#: translation-example.py:20 
msgid "Hello "
msgstr ""
Would become in German:
#: translation-example.py:20 
msgid "Hello "
msgstr "Guten Tag"
Once all the strings are translated then the .po file must be converted into a binary .mo file and placed in its proper folder. So the en_CA.po file would be converted into translation-example.mo and placed in the folder ./po/en_CA/LC_MESSAGES/.
msgfmt --output-file=translation-example.mo en_CA.po
Now copy the translation-example.mo file to the folder ./po/en_CA/LC_MESSAGES/. To test the the translated copy do the following:
LANG=lang python myapp.py
So to test translation-example.py with german that would become:
LANG=de_DE.UTF-8 python translation-example.py
It should be noted that on some systems that the .UTF-8 part is not needed.

10.2 gtk.glade Translation

Translating a project that makes use of a glade file is easy. It just takes a few extra commands to extract the needed text strings. To start off here is an example program that makes use of the translation-example.glade file (See Figure 10.1↓).
figure images/translations/translations-example.png
Figure 10.1 Glade Translation Project

import pygtk
pygtk.require("2.0")
import gtk, gtk.glade
import locale, gettext
APP="translation-example"
DIR="po-glade"
locale.setlocale(locale.LC_ALL, ’’)
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
lang = gettext.translation(APP, DIR) 
_ = lang.gettext
gettext.install(APP, DIR)
class TranslationExample(object):
def on_button_clicked(self, widget, data=None):
self.label_1.set_text( _("Hello ") + str(data) )
def __init__(self):
self.gladefile = gtk.glade.XML("translation-example.glade")
gtk.glade.bindtextdomain(APP, DIR)
self.gladefile.signal_autoconnect(self)
self.main_window = self.gladefile.get_widget("window1")          
self.main_window.connect("delete_event", lambda wid, we: gtk.main_quit())
self.main_window.show_all()
if __name__ == "__main__":
TranslationExample()
gtk.main() 
Create a translation-example.glade.h file by running intltool-extract on translation-example.glade. This is needed to extract the strings to translate with the gettext command line tool.
intltool-extract --type=gettext/glade translation-example.glade
Now use the xgettext command tool to extract the needed strings from all the python files as well as the translation-example.glade.h header file that was created and create the translation-example.pot file.
xgettext --language=Python --keyword=_ --keyword=N_
--output=translation-example.pot translation-example.py
translation-example.glade.h
Now for each language that will be available for the application a .po file must be created. So if Canadian English is the language is to be used:
msginit --input=translation-example.pot --locale=en_CA
Will output a en_CA.po file. American English would be:
msginit --input=translation-example.pot --locale=en_US 
Will output an en_US.po file. German would be:
msginit --input=translation-example.pot --locale=de_DE
This of course would put de.po.
Finally the .po files must be edited and the localized language put into their proper places. To do this please refer back to 10.1 on page 1↑ as it shows you how to use the msgfmt command and proper way to do the translations.

10.3 gtk.Builder Translation

Translating a project that makes use of a gtk.Builder file is easy. It just takes a few extra commands to extract the needed text strings. To start off here is an example program that makes use of the translation-example.glade file (See Figure 10.1↑). First this file must be translated to a gtk.Builder file using the gtk-builder-convert (See section 2.6 on page 1↑) script.
import pygtk 
pygtk.require("2.0")
import gtk
import locale, gettext
 
APP="translation-example"
DIR="po-glade"
 
locale.setlocale(locale.LC_ALL, ’’)
# This is needed to make gtk.Builder work by specifying the
# translations directory
locale.bindtextdomain(APP, DIR)
 
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
lang = gettext.translation(APP, DIR)
_ = lang.gettext 
gettext.install(APP, DIR)
 
class TranslationExample(object):
  def on_button_clicked(self, widget, data=None):
    self.label_1.set_text( _("Hello ") + str(data) )
  
  def __init__(self):
    self.gladefile = gtk.Builder()
    self.gladefile.set_translation_domain(APP)
    self.gladefile.add_from_file("translation-example.xml")
    self.gladefile.connect_signals(self)
  
    self.main_window = self.gladefile.get_object("window1")
    self.main_window.connect("delete_event", lambda wid, we: gtk.main_quit())
    self.main_window.show_all()
  
if __name__ == "__main__": 
  TranslationExample()
  gtk.main() 

Translating a gtk.Builder xml file uses the exact same commands as translating a glade file, however .glade is replaced with .xml for the file that is being used. So create a translation-example.xml.h file by running intltool-extract on translation-example.xml. This is needed to extract the strings to translate with the gettext command line tool.
intltool-extract --type=gettext/glade translation-example.xml
Now use the xgettext command tool to extract the needed strings from all the python files as well as the translation-example.glade.h header file that was created and create the translation-example.pot file.
xgettext --language=Python --keyword=_ --keyword=N_
    --output=translation-example.pot translation-example.py
    translation-example.xml.h
Now for each language that will be available for the application a .po file must be created. So if Canadian English is the language is to be used:
msginit --input=translation-example.pot --locale=en_CA
Will output a en_CA.po file. American English would be:
msginit --input=translation-example.pot --locale=en_US 
Will output an en_US.po file. German would be:
msginit --input=translation-example.pot --locale=de_DE
This of course would put de.po.
Finally the .po files must be edited and the localized language put into their proper places. To do this please refer back to 10.1 on page 1↑ as it shows you how to use the msgfmt command and proper way to do the translations.

10.4 Testing Translations

To make sure that the translation is working properly it should be tested. This section will go into a bit more detail on setting this up.
First the language suppport files that the application has been translated into must be installed on the operating system. This section assumes ubuntu is the test system and the examples are geared toward it.
So lets assume the test system is ubuntu and German is the language that is to be tested. The easiest way to make sure that German language support is installed is to install the language-support-de package. This package will install all the german translation packages for the test system. If you wish you do not need to install this meta package, but can hunt down all the individual packages for german support.
Now make sure that the .mo files, in this case translation-example.mo, are copied to each of their respective language folders; Eg ./po/en_CA/LC_MESSAGES/. To test the the translated copy do the following:
LANG=lang python myapp.py
So to test translation-example.py with german that would become:
LANG=de_DE.UTF-8 python translation-example.py
It should be noted that on some systems that the .UTF-8 part is not needed.

10.4.1 Testing on Win32/Win64

From the command line:
SET Lang=de_DE
myapp.py
Another problem on Windows with gtkbuilder is that that it will not be translated in a pygtk application. You have to force it using ctypes [U→] [→U] For more information see https://bugzilla.gnome.org/show_bug.cgi?id=574520 . At least at the time of writting (pygtk 2.16 with gtk 2.16 and 2.18)
After this line of code
gettext.install(APP,localedir=DIR)
You will then try something like this:
try: 
libintl = ctypes.cdll.LoadLibrary("C:\\GTK\\gtk-2.16.6\\bin\\intl.dll") 
libintl.bindtextdomain(APP, DIR) 
except: 
print "Error Loading translations into gtk.builder files" 

10.5 Translation Cheatsheet

Small quick cheetsheet of the commands that are needed to translate.
intltool-extract --type=gettext/glade translation-example.glade
Extract from both glade/builder and python scripts
xgettext --language=Python --keyword=_ --keyword=N_ 
--output=translation-example.pot translation-example.py 
translation-example.glade.h 
Canadian English
msginit --input=translation-example.pot --locale=en_CA
American English
msginit --input=translation-example.pot --locale=en_US 
German
msginit --input=translation-example.pot --locale=de_DE
Change charset in each .po file to “charset=UTF-8” and put in each translation string. Create binary .mo files for each .po file and place them in their proper ./po/LANG/LC_MESSAGES/ folder.
msgfmt --output-file=translation-example.mo en_CA.po 
msgfmt --output-file=translation-example.mo en_US.po 
msgfmt --output-file=translation-example.mo de_DE.po 
Test each language the application using each language that it has been translated into.
LANG=en_CA.UTF-8 python translation-example.py
LANG=en_US.UTF-8 python translation-example.py
LANG=de_DE.UTF-8 python translation-example.py

10.6 Locale Lists

 
To be able to use and test any of these locale languages the language support packages for your linux distrubtion must be installed. On ubuntu these start with language-support and can be found using the synaptic package manager. So for german it would be language-support-de.
Here is a short list of locales [V→] [→V] On my ubuntu system there is a very nice list at /usr/share/i18n/SUPPORTED. This is a big list that does not include long form of the location of the locale. that can be translated to.
 
en_US English, United States of America
en_CA English, Canada
en_AU English, Australian
en_GB English, Great Britain/United Kingdom
es_MX Spanish, Mexico
es_ES, Spanish, Spain
de_DE Germany, German
fr_FR French, France
fr_CA French, Canadian
it_IT Italian, Italy
ru_RU Russian, Russia
pt_BR Portuguese, Brazil
 

10.7 Summary

For more information on this topic please see these sites: