Internationalization

There are several issues to consider when making applications suitable for users who speak a language that is different from the one used originally. The largest and most obvious issue is that all user-visible strings must be translated into the target language—this includes not only the strings used for menu options and dialog buttons, but also tooltips, status tips, and any other online help. In addition, we must perform other localizations, such as making sure that numbers use the appropriate decimal marker and thousands symbol, that time and date formats are correct, and that paper sizes and systems of measurement are right. For example, English is spoken by most American and British people, but the two cultures have different date format conventions, different currencies, different standard paper sizes, and different systems of measurement.

Unicode strings

Text files

Thanks to the use of Unicode, any character used by just about any human language can be displayed. We saw near the beginning of the book that any unicode character can be included in unicode or QString strings using the unicode escape character and the target character's hexadecimal code point, or using the unichr() function. As for reading and writing text files containing Unicode, we can use Python's codecs.open() function, or PyQt's QTextStream as we saw in an earlier chapter.

When it comes to some aspects of localization we can use QString, QDate, and QDateTime. For example, assuming n is a number, QString("%L1").arg(n) will QString produce a QString with thousands and decimal separators suitable to the cur-■ ai-g() rent locale. Both QDate and QDateTime have toString() methods that can accept 402« either a custom format, or a predefined format such as Qt.SystemLocaleDate (Qt.LocalDate in older code), or Qt.ISODate, which is "universal". In addition, the QLocale class provides many methods for returning localized QStrings, and a few methods for extracting numbers from localized QStrings. It also has methods that return locale-specific characters, such as the character to use as a negative sign, a percentage symbol, and so on.

Most of the work involved with internationalizing an application is concerned with translation, so it is this topic that we will focus on for the rest of the section.

To help translate applications, PyQt provides a tool chain of three tools: py-lupdate4, lrelease, and Qt Linguist. For these tools to be useful, every user-visible string must be specially marked. This is easily achieved by using the QObject.tr() method, which is inherited by all QWidget subclasses, including all dialogs and main windows. For example, instead of writing QString("&Save"), we write self.tr("&Save"). The text passed to tr() should be ASCII; if characters outside the ASCII range are required, use trUtf8() instead.

For each string marked for translation, the translation tools are provided with a pair of strings: a "context" string (the class name), and the marked string itself. The purpose of the context is to help human translators identify which window the string to translate is shown in, since different translations might be needed in different windows in some languages.

For strings that need translating but are not inside classes, we must use the QApplication.translate() method, and supply the context string ourselves. For example, in a main() function we might translate the application's name like this: QApplication.translate("main", "Gradgrind"). Here, the context is "main", and the string to translate is "Gradgrind".

Unfortunately, the context used by self.tr() can be different from that used by C++/Qt's tr() method, because PyQt determines the context dynamically, whereas C++ does so at compile time.* This may matter if translation files are being shared between C++/Qt and PyQt applications. It can also be an issue if forms are subclassed. If this is ever a problem, the solution is simply to replace each single-argument self.tr() call with a two-argument QApplica-tion.translate() call, explicitly giving the correct context as the first argument, and the string to be translated as the second argument.

Once all of an application's user-visible strings are suitably marked, we must slightly change the way the application starts up so that it reads in the translated strings for the locale in which it is run.

★See the PyQt pyqt4ref.html documentation, under "Differences Between PyQt and Qt".

Here is how an internationalized application is created.

1. Create the application using QObject.tr() or QApplication.translate() for all user-visible strings.

2. Modify the application to read in the locale-specific .qm (Qt message) files at start-up if they are available.

3. Create a .pro file that lists the application's .ui (Qt Designer) files, its .py and .pyw source files, and the .ts (translation source) file that it will use.

4. Run pylupdate4 to create the .ts file.

5. Ask the translator to translate the .ts file's strings using Qt Linguist.

6. Run lrelease to convert the updated .ts file (that contains the translations) to a .qm file.

And here is how such an application is maintained.

1. Update the application, making sure that all user-visible strings use QObject.tr() or QApplication.translate().

2. Update the .pro file if necessary—for example, adding any new .ui or .py files that have been added to the application.

3. Run pylupdate4 to update the .ts file with any new strings.

4. Ask the translator to translate any new strings in the .ts file.

5. Run lrelease to convert the .ts file to a .qm file.

We will cover all of the preceding steps, starting with the use of QObject.tr(), using extracts from the translation-aware version of the Image Changer application in the chap17 directory.

fileNewAction = self.createAction(self.tr("&New..."), self.fileNew, QKeySequence.New, "filenew", self.tr("Create an image file"))

The first string marked for translation is the menu option string, New..., and the second is the string used for tooltips and status tips. (The "filenew" string is the name of the icon file without its .png suffix.)

self.fileMenu = self.menuBar().addMenu(self.tr("&File"))

Menu strings as well as action strings must be translated.

self.statusBar().showMessage(self.tr("Ready"), 5000)

Here we have an initial status message for the user, and again we must use tr().

It is not usually appropriate to translate the strings used as QSettings keys, especially since these strings are not normally visible to the user.

reply = QMessageBox.question(self, self.tr("Image Changer - Unsaved Changes"), self.tr("Save unsaved changes?"), QMessageBox.Yes|QMessageBox.No| QMessageBox.Cancel)

For this message box, we have marked both the window title and the message text for translation. We don't have to worry about translating the buttons in this case because we are using standard buttons and Qt has translations for these.* If we had used our own text we would have had to use tr() on it, like any other user-visible string.

self.tr("Saved %1 in file %2").arg(self.dataname).arg(self.filename)

One way to provide the preceding string is to write:

self.tr("Saved %s in file %s" % (self.dataname, self.filename)) # BAD

This is not recommended. Always use QStrings, and always use QString.arg(); this makes it easier for translators. (The tr() method returns a QString, so we can call any QString method, such as arg(), on its return value.) For example, in some languages the translation would be phrased "Saved in file %2 the data %1". This is no problem using a QString with arg()s, since the translator can change the order of the %ns in the string and the arg() methods will respect this. But swapping one Python string's %s for another will not change anything.

We must use tr() for every user-visible string in hand-coded .pyw and .py files. But for .py files generated from .ui files by pyuic4 we don't need to do anything, since pyuic4 automatically uses QApplication.translate() on all strings anyway. This works even for untranslated applications, because if there is no suitable translation, the original language—for example, English—is used instead.

A PyQt application usually uses PyQt built-in dialogs; for example, the file open dialog, or the file print dialog. These must also be translated, although for several languages translations are already available in the .qm files provided by Trolltech.

Having used tr() throughout, and having located an appropriate Qt translation, we are ready to modify the application's start-up code to load in suitable translation files if they exist.

app = QApplication(sys.argv) locale = QLocale.system().name() qtTranslator = QTranslator() if qtTranslator.load("qt_" + locale, ":/"): app.installTranslator(qtTranslator)

★Trolltech provides translations for some languages, such as French and German, and some unsupported translations to various other languages. These translations are in Qt's (not PyQt's) translations directory; search your filesystem for qt_fr.qm, for example, to find the French translation.

appTranslator = QTranslator()

if appTranslator.load("imagechanger_" + locale, ":/"): app.installTranslator(appTranslator)

app.setOrganizationName("Qtrac Ltd.") app.setOrganizationDomain("qtrac.eu")

app.setApplicationName(app.translate("main", "Image Changer"))

app.setWindowIcon(QIcon(":/icon.png"))

form = MainWindow()

form.show()

The QLocale.system().name() call will return a string such as "en_US" (English, United States), or "fr_CA" (French, Canada), and so on. The QTranslator.load() method takes a file stem and a path. In this case, we have given the path of :/ which is the application's resource file. If the locale were "fr_CA", the file stems would be qt_fr_CA and imagechanger_fr_CA. Given these, PyQt will look for qt_fr_CA.qm, and failing that, for qt_fr.qm, and similarly for imagechang-er_fr_CA.qm, and failing that, for imagechanger_fr.qm. If the locale was "en_US", no .qm files would be found, and therefore none installed—and this is fine, since the application would then fall back to using the original strings which, are in English anyway.

Notice that we had to use QApplication.translate() (written as app.trans-late()), since this code is not inside a QObject subclass's method. With no class name, we chose to use the text "main" for the context; some programmers might prefer to use "global". We are free to use any name we like—the purpose of contexts is purely to help human translators.

We can load only a single translation into a single QTranslator object, but we can add as many translators as we like to the QApplication object. If there are conflicts, that is, if the same string has different translations, the most recently installed translator wins.

Although we have chosen to include our translations in the resource file, there is no obligation to do so; we could just as easily have accessed them from the filesystem.

Here is an extract from the resource.qrc file that we have used:

<qresource>

<file>imagechanger_fr.qm</file>

</qresource>

<qresource>

<file alias="editmenu.html">help/editmenu.html</file> <file alias="filemenu.html">help/filemenu.html</file> <file alias="index.html">help/index.html</file> </qresource> <qresource lang="fr">

<file alias="editmenu.html">help/editmenu_fr.html</file> <file alias="filemenu.html">help/filemenu_fr.html</file> <file alias="index.html">help/index_fr.html</file> </qresource>

A resource file can have any number of <qresource> tags, although up until now we have only ever used one. If the current locale is "en_US", the main help file will be :/index.html; but if the locale is "fr_CA" or "fr" or any other "fr_*", when we seek to access file :/index.html in code, the file we will actually get is :/index_fr.html.

Pyqt List Grid
Figure 17.2 Qt Linguist

The tool that is used to create and update a .ts (translation source) file is pylupdate4. This program is run from the command line with the name of a .pro file as a parameter. Here is the complete imagechanger.pro file:

FORMS += newimagedlg.ui

SOURCES += helpform.py SOURCES += imagechanger.pyw SOURCES += newimagedlg.py SOURCES += resizedlg.py TRANSLATIONS += imagechanger_fr.ts

The .pro file format is used primarily by C++/Qt programmers, but it makes using pylupdate4 and lrelease easier if we use it for PyQt projects. We care about only three kinds of entries: FORMS for .ui files, SOURCES for .py and .pyw files, and TRANSLATIONs for .ts files. Notice that we do not list .qm files (such as qt_fr.qm); this is because we do not generate the qt_fr.qm file, but simply copy it from the translations directory.

We don't have to use one line per file; instead, we can group files. For example:

FORMS = newimagedlg.ui

SOURCES = helpform.py imagechanger.pyw newimagedlg.py resizedlg.py TRANSLATIONS = imagechanger_fr.ts

Once we have used tr() and translate() in our source code, and created the .pro file, we can run pylupdate4:

C:\pyqt\chap17>pylupdate4 -verbose imagechanger.pro Updating 'imagechanger_fr.ts'...

Found 96 source texts (96 new and 0 already existing)

Using the -verbose option is, of course, optional. The pylupdate4 program creates the .ts file listed in the .pro file if it doesn't exist, and puts into it all the contexts and strings for the strings marked using tr() and translate() that appear in the files listed in the FORMS and SOURCES .pro file entries. If the .ts file already exists, pylupdate4 adds any new contexts and strings that are necessary, leaving intact any translations that have been added in the meantime. Because pylupdate4 is smart, we can run it as often as we like, even if a translator has updated the .ts file by adding or changing translations, without losing any data.

When we are ready to release (or to simply test) the translated application, we can generate a .qm file for the .ts file by running lrelease:

C:\pyqt\chap17>lrelease -verbose imagechanger.pro Updating 'C:/pyqt/chap17/imagechanger_fr.qm'...

Generated 85 translations (81 finished and 4 unfinished) Ignored 11 untranslated source texts

Just like pylupdate4, we can run lrelease as often as we like. We don't need to generate the qt_fr.qm file, because we copied it.

It is possible to avoid using a .pro file entirely, and simply rely on the mkpyqt.py or Make PyQt build tools. To do this, we must run pylupdate4 once on the command line. For example:

C:\pyqt\chap17>pylupdate4 *.py *.pyw -ts imagechanger_fr.ts

From now on we can simply run mkpyqt.py with the -t (translate) option, or run Make PyQt and check the Translate checkbox. With translation switched on, both tools run pylupdate4 followed by lrelease.

The main piece of work left to do is the translation itself. For this, we can give the translator the Qt Linguist application—it is written in C++/Qt and runs on Windows, Mac OS X, and Linux—along with the .ts file, and ask them to enter translations for the strings. The Qt Linguist application (shown in Figure 17.2), is quite easy to use and can help minimize duplication by suggesting similar previously translated phrases. It groups translation strings by contexts (which are normally window class names). This is useful when a string might need to be translated in different ways depending on which form it appears in.

To get started with Qt Linguist, run it, click File^Open, and open a .ts file. Now click one of the +1 symbols in the Context dock window on the left to show the strings in a context, and then click one of the strings. The string will appear in the top-right panel under the "Source text" heading. Click under the "Translation" heading and type in a translation. To confirm that the translation of the string is finished, click the question mark icon in the Context dock window beside the relevant string: Clicking the icon makes it toggle between being a question mark or a tick. Translations that are ticked are "done" and will be put into the .qm file by lrelease.

Was this article helpful?

+2 -1
Tuberminator

Tuberminator

The main focus of this report is to show how to get involved in video marketing on the run, how to rank quickly on YouTube and Google using FREE semi-automatic tools and services. QUICKLY AND FREE. I will show methods and techniques I use to rank my videos, as well as free resources and tools to make video clips, to get backlinks and free traffic.

Get My Free Ebook


Responses

  • JALI
    How to create context menu in ui file pyqt?
    9 years ago
  • monika
    How to generate ts files pyqt4?
    8 years ago
  • Lobelia
    How to use "pylupdate4"?
    8 years ago

Post a comment