For more than a century people have been
uttering the platitude that the world is getting smaller all the
time. That's nonsense: it's getting bigger. Although most computer
users are still able to work with English-only applications, even
speakers of really obscure languages, like
Limbu, own computers and would like some applications in their own
language.
An open-source effort like KDE offers
more-or-less complete translations of the entire desktop,
including all applications in dozens of languages. And, for a
consideration, you can get a version of Windows in your own
language, too, even if that language is Basque.
Of course, there are other aspects to the
internationalization of an application, like date and number
formats, currency, keyboard, preferred dialog layout and so on.
Some of these aspects are handled by Qt - like reversing the
dialog layout if the current script is right-to-left. Others, like
the date and number formats are handled by Python's
locale module - which is alas severely
underdocumented.
Translating screen texts
The first task is to surround all
translatable text with the method self.tr()
- every QObject - derived class has that
method. You don't have to do that manually with designs you have
generated with the Designer module or
Qt Designer. However, for Kalam,
it's a fair bit of work - I'll only show a fragment here:
# fragment from kalamapp.py
...
def initActions(self):
self.actions = {}
self.actions["fileNew"] = \
QAction(self.tr("New"),
QIconSet(QPixmap(filenew)),
self.tr("&New"),
QAccel.stringToKey(self.tr("CTRL+N",
"File|New"))
self)
self.connect(self.actions["fileNew"],
SIGNAL("activated()"),
self.slotFileNew)
self.actions["fileOpen"] = \
QAction(self.tr("Open"),
QIconSet(QPixmap(fileopen)),
self.tr("&Open"),
QAccel.stringToKey(self.tr("CTRL+O",
"File|Open")),
self)
self.connect(self.actions["fileOpen"],
SIGNAL("activated()"),
self.slotFileOpen)
...
You must not only mark all text that will
appear on screen, but also all accelerator keys, otherwise
translators won't be able to translate them. The extra argument
to tr() gives the translator some extra
context.
The tr() serves two
purposes: at first, it used as a recognition point for a small
utility that extracts the strings to create message catalogs -
files full of translatable text that you can send your Limbu
speaking friends to translate for you.
Secondly, when you run the application,
tr() looks in a message database to find
the right string. This is a very fast operation, so you don't
have to worry about performance loss.
After you've marked all translatable
strings, you can use a utility to generate translatable message
files. Qt's utility—either lupdate or
findtr—can only work with strings marked with
tr(), and only with double-quoted
strings.
There is a significant, though quite
esoteric, difference between the way Qt2 and Qt3 handle the
tr(). This means that when you use a
version of PyQt designed to work with Qt 2, the
tr() doesn't work out of the box. You
need to add a tr() to all your classes
that calls qApp.translate(). This is what
is done in the current Kalam code,
because I wrote and developed the book using PyQt 2.5.
Another important difference: in Qt 3, you can also use
trUtf8(), if the source text is in the
utf-8 encoding. That means that if your translators produce
utf-8 encoded files, instead of plain two-byte Unicode text,
you should use this function, instead of
tr(). With PyQt 3 for Qt 3,
trUtf8*() will be used automatically by
pyuic.
You can also tell
pyuic to use another function instead
of tr() - for instance, the Python
pygettext.py default _(). If you do that,
with the command:
pyuic -tr _ frmsettings.ui
there will be one important difference: by
default, the translation function tr() has
class-local scope, i.e. it is prefixed with
self. But a custom translation function has
global scope - exactly what you need for the Python
implementation of gettext.
So, you can either do:
boud@calcifer:~/doc/pyqt/ch19/kalam > pygettext.py --keyword=tr kalamapp.py
Which creates a file called
messages.pot, or:
boud@calcifer:~/doc/pyqt/ch19/kalam > findtr kalamapp.py
The resulting files are almost identical -
except for the minor detail of order. You should make a copy of
these files for every language you need a translation for, and
send them to your translators. They can use any editor, or a
specialised application like KBabel
to translate the text, and send it back in the form of a
translated .pot file.
The result can be compiled to
.mo files using the
msgfmt.py utility which should hide
somewhere in you Python installation.
Finally, you can use these message catalog
by loading it and installing a global function
_(). (That should have been the function
you used to mark your strings):
import gettext
gettext.install('kalam')
Or for message catalogs in the Unicode
encoding:
import gettext
gettext.install('kalam', '/usr/share/locale', unicode=1)
Here, the path should point to a locale directory where all
message files can be found.
If you are working with Qt 3.0, you can
also use a new tool: Qt Linguist.
This extracts the messages to a special, xml-based, format, and
you can create message catalogs with a nice GUI frontend.
To use Qt
Linguist, you need to make a Qt project file
containing the following text:
SOURCES = configtest.py \
dlgfindreplace.py \
dlgsettings.py \
docmanager.py \
docmanagertest.py \
edmund.py \
frmfindreplace.py \
frmsettings.py \
kalamapp.py \
kalamconfig.py \
kalamdoc.py \
kalamview.py \
macromanager.py \
macromanagertest.py \
main.py \
resources.py \
sitecustomize.py \
startup.py
TRANSLATIONS = kalam_nl.ts
And run the following command:
boud@calcifer:~/doc/pyqt/ch19/kalam > lupdate kalam.pro
After spewing out a lot of warnings (this
tool expects C++, not python) a file in xml format is created
which you can edit with an editor or with
Qt Linguist.
If the translator is finished, he or she
can choose "release" in the menubar and generate a
.qm message catalog.
Using this catalog in your application is
a simple matter of installing the appropriate translator:
Example 25-1. Installing the translator
#!/usr/bin/env python
"""
main.py - application starter
copyright: (C) 2001, Boudewijn Rempt
email: boud@rempt.xs4all.nl
"""
import sys, locale
from qt import *
from kalamapp import KalamApp
from kalamdoc import KalamDoc
from kalamview import KalamView
import kalamconfig
from resources import TRUE, FALSE
def main(args):
app=QApplication(args)
translator = QTranslator(app)
translator.load("kalam_" + locale.getlocale()[0] + ".qm",
kalamconfig.get("libdir","."))
app.installTranslator(translator)
kalam = KalamApp()
app.setMainWidget(kalam)
kalam.show()
if len(args) > 1:
for arg in args[1:]:
document=KalamDoc()
document.open(arg)
kalam.docManager.addDocument(document, KalamView)
app.exec_loop()
if __name__=="__main__":
main(sys.argv)
Two remarks: note how we use the
locale module to determine the language of
the user. This returns a tuple containing a language code and a
character set that correspond the user locale, as set by the
operating system: ['en_US', 'ISO8859-1']. If
you always use the language code as the second part for your
filename, then Qt will be able to determine which translation
file to load.
Note also that the location of that
message file is determined by a configuration option. Standard
Unix .mo files tend to go into
/usr/share/locale/, but there is no
corresponding standard for Qt .qm messages,
and you might as well put those in the application installation
directory. Where that is, will be determined in the next
chapter.