Chapter 18. Internationalization

Internationalization

In addition to the Latin alphabet used for English and for many European languages, Qt 4 also provides extensive support for the rest of the world’s writing systems:

  • Qt uses Unicode throughout the API and internally. No matter what language we use for the user interface, the application can support all users alike.

  • Qt’s text engine can handle all the major non-Latin writing systems, including Arabic, Chinese, Cyrillic, Hebrew, Japanese, Korean, Thai, and the Indic languages.

  • Qt’s layout engine supports right-to-left layouts for languages such as Arabic and Hebrew.

  • Certain languages require special input methods for entering text. Editor widgets such as QLineEdit and QTextEdit work well with any input method installed on the user’s system.

Often, it isn’t enough to allow users to enter text in their native language; the entire user interface must be translated as well. Qt makes this easy: Simply wrap all user-visible strings with the tr() function (as we have done in earlier chapters) and use Qt’s supporting tools to prepare translation files in the required languages. Qt provides a GUI tool called Qt Linguist for use by translators. Qt Linguist is complemented by two command-line programs, lupdate and lrelease, which are typically run by the application’s developers.

For most applications, a translation file is loaded at startup, based on the user’s locale settings. But in a few cases, it is also necessary for users to be able to switch languages at run-time. This is perfectly possible with Qt, although it does require a bit of extra work. And thanks to Qt’s layout system, the various user interface components will automatically adjust to make room for the translated texts when they are longer than the original texts.

Working with Unicode

Unicode is a character encoding standard that supports most of the world’s writing systems. The original idea behind Unicode is that by using 16 bits for storing characters instead of 8 bits, it would be possible to encode around 65000 characters instead of only 256.[*] Unicode contains ASCII and ISO 8859-1 (Latin-1) as subsets at the same code positions. For example, the character ‘A’ has value 0x41 in ASCII, Latin-1, and Unicode, and the character ‘Ñ’ has value 0xD1 in both Latin-1 and Unicode.

Qt’s QString class stores strings as Unicode. Each character in a QString is a 16-bit QChar rather than an 8-bit char. Here are two ways of setting the first character of a string to ‘A’:

str[0] = 'A';
str[0] = QChar(0x41);

If the source file is encoded in Latin-1, specifying Latin-1 characters is just as easy:

str[0] = 'Ñ';

And if the source file has another encoding, the numeric value works fine:

str[0] = QChar(0xD1);

We can specify any Unicode character by its numeric value. For example, here’s how to specify the Greek capital letter sigma (‘∑’) and the euro currency symbol (‘€’):

str[0] = QChar(0x03A3);
str[0] = QChar(0x20AC);

The numeric values of all the characters supported by Unicode are listed at http://www.unicode.org/standard/. If you rarely need non-Latin-1 Unicode characters, looking up characters online is sufficient; but Qt provides more convenient ways of entering Unicode strings in a Qt program, as we will see later in this section.

Qt 4’s text engine supports the following writing systems on all platforms: Arabic, Chinese, Cyrillic, Greek, Hebrew, Japanese, Korean, Lao, Latin, Thai, and Vietnamese. It also supports all the Unicode 4.1 scripts that don’t require any special processing. In addition, the following writing systems are supported on X11 with Fontconfig and on recent versions of Windows: Bengali, Devanagari, Gujarati, Gurmukhi, Kannada, Khmer, Malayalam, Syriac, Tamil, Telugu, Thaana (Dhivehi), and Tibetan. Finally, Oriya is supported on X11, and Mongolian and Sinhala are supported on Windows XP. Assuming that the proper fonts are installed on the system, Qt can render text using any of these writing systems. And assuming that the proper input methods are installed, users will be able to enter text that uses these writing systems in their Qt applications.

Programming with QChar is slightly different from programming with char. To obtain the numeric value of a QChar, call unicode() on it. To obtain the ASCII or Latin-1 value of a QChar (as a char), call toLatin1(). For non-Latin-1 characters, toLatin1() returns ‘’.

If we know that all the strings in a program are ASCII, we can use standard <cctype> functions such as isalpha(), isdigit(), and isspace() on the return value of toLatin1(). However, it is generally better to use QChar’s member functions for performing these operations, since they will work for any Unicode character. The functions QChar provides include isPrint(), isPunct(), isSpace(), isMark(), isLetter(), isNumber(), isLetterOrNumber(), isDigit(), isSymbol(), isLower(), and isUpper(). For example, here’s one way to test that a character is a digit or an uppercase letter:

if (ch.isDigit() || ch.isUpper())
    ...

The code snippet works for any alphabet that distinguishes between uppercase and lowercase, including Latin, Greek, and Cyrillic.

Once we have a Unicode string, we can use it anywhere in Qt’s API where a QString is expected. It is then Qt’s responsibility to display it properly and to convert it to the relevant encodings when talking to the operating system.

Special care is needed when we read and write text files. Text files can use a variety of encodings, and it is often impossible to guess a text file’s encoding from its contents. By default, QTextStream uses the system’s local 8-bit encoding (available as QTextCodec::codecForLocale()) for both reading and writing. For American and West European locales, this usually means Latin-1.

If we design our own file format and want to be able to read and write arbitrary Unicode characters, we can save the data as Unicode by calling

stream.setCodec("UTF-16");
stream.setGenerateByteOrderMark(true);

before we start writing to the QTextStream. The data will then be saved in UTF-16, a format that requires two bytes per character, and will be prefixed with a special 16-bit value (the Unicode byte order mark, 0xFFFE) identifying that the file is in Unicode and whether the bytes are in little-endian or big-endian order. The UTF-16 format is identical to the memory representation of a QString, so reading and writing Unicode strings in UTF-16 can be very fast. On the other hand, there is an inherent overhead when saving pure ASCII data in UTF-16 format, since it stores two bytes for every character instead of just one.

Other encodings can be specified by calling setCodec() with an appropriate QTextCodec. A QTextCodec is an object that converts between Unicode and a given encoding. Qt uses QTextCodecs in a variety of contexts. Internally, they are used to support fonts, input methods, the clipboard, drag and drop, and file names. But they are also available to us when we write Qt applications.

When reading a text file, QTextStream detects UTF-16 automatically if the file starts with the byte order mark. This behavior can be turned off by calling setAutoDetectUnicode(false). If the data is UTF-16 but can’t be assumed to start with the byte order mark, it is best to call setCodec() with “UTF-16” before reading.

Another encoding that supports the whole of Unicode is UTF-8. Its main advantage over UTF-16 is that it is a superset of ASCII. Any character in the range 0x00 to 0x7F is represented as a single byte. Other characters, including Latin-1 characters above 0x7F, are represented by multi-byte sequences. For text that is mostly ASCII, UTF-8 takes up about half the space consumed by UTF-16. To use UTF-8 with QTextStream, call setCodec() with “UTF-8” as the codec name before reading and writing.

If we always want to read and write Latin-1 regardless of the user’s locale, we can set the “ISO 8859-1” codec on the QTextStream. For example:

QTextStream in(&file);
in.setCodec("ISO 8859-1");

Some file formats specify their encoding in their header. The header is typically plain ASCII to ensure that it is read correctly no matter what encoding is used (assuming that it is a superset of ASCII). The XML file format is an interesting example of this. XML files are normally encoded as UTF-8 or UTF-16. The proper way to read them in is to call setCodec() with “UTF-8”. If the format is UTF-16, QTextStream will automatically detect this and adjust itself. The <?xml?> header of an XML file sometimes contains an encoding argument, for example:

<?xml version="1.0" encoding="EUC-KR"?>

Since QTextStream doesn’t allow us to change the encoding once it has started reading, the right way to respect an explicit encoding is to start reading the file afresh, using the correct codec (obtained from QTextCodec::codecForName()). In the case of XML, we can avoid having to handle the encoding ourselves by using Qt’s XML classes, described in Chapter 16.

Another use of QTextCodecs is to specify the encoding of strings that occur in the source code. Let’s consider, for example, a team of Japanese programmers who are writing an application targeted primarily at Japan’s home market. These programmers are likely to write their source code in a text editor that uses an encoding such as EUC-JP or Shift-JIS. Such an editor allows them to type in Japanese characters seamlessly so that they can write code like this:

QPushButton *button = new QPushButton(tr(Working with Unicode));

By default, Qt interprets arguments to tr() as Latin-1. To change this, call the QTextCodec::setCodecForTr() static function. For example:

QTextCodec::setCodecForTr(QTextCodec::codecForName("EUC-JP"));

This must be done before the first call to tr(). Typically, we would do this in main(), immediately after the QCoreApplication or QApplication object is created.

Other strings specified in the program will still be interpreted as Latin-1 strings. If the programmers want to enter Japanese characters in those as well, they can explicitly convert them to Unicode using a QTextCodec:

QString text = japaneseCodec->toUnicode(Working with Unicode);

Alternatively, they can tell Qt to use a specific codec when converting between const char * and QString by calling QTextCodec::setCodecForCStrings():

QTextCodec::setCodecForCStrings(QTextCodec::codecForName("EUC-JP"));

The techniques described above can be applied to any non-Latin-1 language, including Chinese, Greek, Korean, and Russian.

Here’s a list of the encodings supported by Qt 4.3:

  • Apple Roman

  • Big5

  • Big5-HKSCS

  • EUC-JP

  • EUC-KR

  • GB18030-0

  • IBM 850

  • IBM 866

  • IBM 874

  • ISO 2022-JP

  • ISO 8859-1

  • ISO 8859-2

  • ISO 8859-3

  • ISO 8859-4

  • ISO 8859-5

  • ISO 8859-6

  • ISO 8859-7

  • ISO 8859-8

  • ISO 8859-9

  • ISO 8859-10

  • ISO 8859-13

  • ISO 8859-14

  • ISO 8859-15

  • ISO 8859-16

  • Iscii-Bng

  • Iscii-Dev

  • Iscii-Gjr

  • Iscii-Knd

  • Iscii-Mlm

  • Iscii-Ori

  • Iscii-Pnj

  • Iscii-Tlg

  • Iscii-Tml

  • JIS X 0201

  • JIS X 0208

  • KOI8-R

  • KOI8-U

  • MuleLao-1

  • ROMAN8

  • Shift-JIS

  • TIS-620

  • TSCII

  • UTF-8

  • UTF-16

  • UTF-16BE

  • UTF-16LE

  • Windows-1250

  • Windows-1251

  • Windows-1252

  • Windows-1253

  • Windows-1254

  • Windows-1255

  • Windows-1256

  • Windows-1257

  • Windows-1258

  • WINSAMI2

For all of these, QTextCodec::codecForName() will always return a valid pointer. Other encodings can be supported by subclassing QTextCodec.

Making Applications Translation-Aware

If we want to make our applications available in multiple languages, we must do two things:

  • Make sure that every user-visible string goes through tr().

  • Load a translation (.qm) file at startup.

Neither of these is necessary for applications that will never be translated. However, using tr() requires almost no effort and leaves the door open for doing translations at a later date.

The tr() function is a static function defined in QObject and overridden in every subclass defined with the Q_OBJECT macro. When writing code inside a QObject subclass, we can call tr() without formality. A call to tr() returns a translation if one is available; otherwise, the original text is returned. Inside a non-QObject class, we can either write QObject::tr() with the class prefix or use the Q_DECLARE_TR_FUNCTIONS() macro to add tr() to the class, as we did in Chapter 8 (p. 200).

To prepare translation files, we must run Qt’s lupdate tool. This tool extracts all the string literals that appear in tr() calls and produces translation files that contain all of these strings ready to be translated. The files can then be sent to a translator to have the translations added. This process is explained in the “Translating Applications” section later in this chapter.

A tr() call has the following general syntax:

Context::tr(sourceText, comment)

The Context part is the name of a QObject subclass defined with the Q_OBJECT macro. We don’t need to specify it if we call tr() from a member function of the class in question. The sourceText part is the string literal that needs to be translated. The comment part is optional; it can be used to provide additional information to the translator.

Here are a few examples:

RockyWidget::RockyWidget(QWidget *parent)
    : QWidget(parent)
{
    QString str1 = tr("Letter");
    QString str2 = RockyWidget::tr("Letter");
    QString str3 = SnazzyDialog::tr("Letter");
    QString str4 = SnazzyDialog::tr("Letter", "US paper size");
}

The first two calls to tr() have “RockyWidget” as their context, and the last two calls have “SnazzyDialog”. All four have “Letter” as their source text. The last call also has a comment to help the translator understand the meaning of the source text.

Strings in different contexts (classes) are translated independently of each other. Translators typically work on one context at a time, often with the application running and showing the widget or dialog being translated.

When we call tr() from a global function, we must specify the context explicitly. Any QObject subclass in the application can be used as the context. If none is appropriate, we can always use QObject itself. For example:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    ...
    QPushButton button(QObject::tr("Hello Qt!"));
    button.show();
    return app.exec();
}

In every example so far, the context has been a class name. This is convenient, because we can almost always omit it, but this doesn’t have to be the case. The most general way of translating a string in Qt is to use the QCoreApplication::translate() function, which accepts up to three arguments: the context, the source text, and the optional comment. For example, here’s another way to translate “Hello Qt!”:

QCoreApplication::translate("Global Stuff", "Hello Qt!")

This time, we put the text in the “Global Stuff” context.

The tr() and translate() functions serve a dual purpose: They are markers that lupdate uses to find user-visible strings, and at the same time they are C++ functions that translate text. This has an impact on how we write code. For example, the following will not work:

// WRONG
const char *appName = "OpenDrawer 2D";
QString translated = tr(appName);

The problem here is that lupdate will not be able to extract the “OpenDrawer 2D” string literal, as it doesn’t appear inside a tr() call. This means that the translator will not have the opportunity to translate the string. This issue often arises in conjunction with dynamic strings:

// WRONG
statusBar()->showMessage(tr("Host " + hostName + " found"));

Here, the string we pass to tr() varies depending on the value of hostName, so we can’t reasonably expect tr() to translate it correctly.

The solution is to use QString::arg():

statusBar()->showMessage(tr("Host %1 found").arg(hostName));

Notice how it works: The string literal “Host %1 found” is passed to tr(). Assuming that a French translation file is loaded, tr() would return something like “Hôte %1 trouvé”. Then the “%1” parameter is replaced with the contents of the hostName variable.

Although it is generally inadvisable to call tr() on a variable, it can be made to work. We must use the QT_TR_NOOP() macro to mark the string literals for translation before we assign them to a variable. This is mostly useful for static arrays of strings. For example:

void OrderForm::init()
{
    static const char * const flowers[] = {
        QT_TR_NOOP("Medium Stem Pink Roses"),
        QT_TR_NOOP("One Dozen Boxed Roses"),
        QT_TR_NOOP("Calypso Orchid"),
        QT_TR_NOOP("Dried Red Rose Bouquet"),
        QT_TR_NOOP("Mixed Peonies Bouquet"),
        0
    };

    for (int i = 0; flowers[i]; ++i)
        comboBox->addItem(tr(flowers[i]));
}

The QT_TR_NOOP() macro simply returns its argument. But lupdate will extract all the strings wrapped in QT_TR_NOOP() so that they can be translated. When using the variable later on, we call tr() to perform the translation as usual. Even though we have passed tr() a variable, the translation will still work.

There is also a QT_TRANSLATE_NOOP() macro that works like QT_TR_NOOP() but also takes a context. This macro is useful when initializing variables outside of a class:

static const char * const flowers[] = {
    QT_TRANSLATE_NOOP("OrderForm", "Medium Stem Pink Roses"),
    QT_TRANSLATE_NOOP("OrderForm", "One Dozen Boxed Roses"),
    QT_TRANSLATE_NOOP("OrderForm", "Calypso Orchid"),
    QT_TRANSLATE_NOOP("OrderForm", "Dried Red Rose Bouquet"),
    QT_TRANSLATE_NOOP("OrderForm", "Mixed Peonies Bouquet"),
    0
};

The context argument must be the same as the context given to tr() or translate() later on.

When we start using tr() in an application, it’s easy to forget to surround some user-visible strings with a tr() call, especially when we are just beginning to use it. These missing tr() calls are eventually discovered by the translator or, worse, by users of the translated application, when some strings appear in the original language. To avoid this problem, we can tell Qt to forbid implicit conversions from const char * to QString. We do this by defining the QT_NO_CAST_FROM_ASCII preprocessor symbol before including any Qt header. The easiest way to ensure that this symbol is set is to add the following line to the application’s .pro file:

DEFINES += QT_NO_CAST_FROM_ASCII

This will force every string literal to require wrapping by tr() or by QLatin1String(), depending on whether it should be translated. Strings that are not suitably wrapped will produce a compile-time error, thereby compelling us to add the missing tr() or QLatin1String() wrapper.

Once we have wrapped every user-visible string by a tr() call, the only thing left to do to enable translation is to load a translation file. Typically, we would do this in the application’s main() function. For example, here’s how we would try to load a translation file depending on the user’s locale:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    ...
    QTranslator appTranslator;
    appTranslator.load("myapp_" + QLocale::system().name(), qmPath);
    app.installTranslator(&appTranslator);
    ...
    return app.exec();
}

The QLocale::system() function returns a QLocale object that provides information about the user’s locale. Conventionally, we use the locale’s name as part of the .qm file name. Locale names can be more or less precise; for example, fr specifies a French-language locale, fr_CA specifies a French Canadian locale, and fr_CA.ISO8859-15 specifies a French Canadian locale with ISO 8859-15 encoding (an encoding that supports ‘€’, ‘Œ’, ‘œ’, and ‘Ÿ’).

Assuming that the locale is fr_CA.ISO8859-15, the QTranslator::load() function first tries to load the file myapp_fr_CA.ISO8859-15.qm. If this file does not exist, load() next tries myapp_fr_CA.qm, then myapp_fr.qm, and finally myapp.qm, before giving up. Normally, we would only provide myapp_fr.qm, containing a standard French translation, but if we need a different file for French-speaking Canada, we can also provide myapp_fr_CA.qm and it will be used for fr_CA locales.

The second argument to QTranslator::load() is the directory where we want load() to look for the translation file. In this case, we assume that the translation files are located in the directory given in the qmPath variable.

The Qt libraries contain a few strings that need to be translated. Trolltech provides French, German, and Simplified Chinese translations in Qt’s translations directory. A few other languages are provided as well, but these are contributed by Qt users and are not officially supported. The Qt libraries’ translation file should also be loaded:

QTranslator qtTranslator;
qtTranslator.load("qt_" + QLocale::system().name(), qmPath);
app.installTranslator(&qtTranslator);

A QTranslator object can hold only one translation file at a time, so we use a separate QTranslator for Qt’s translation. Having just one file per translator is not a problem since we can install as many translators as we need. QCoreApplication will use all of them when searching for a translation.

Some languages, such as Arabic and Hebrew, are written right-to-left instead of left-to-right. In those languages, the whole layout of the application must be reversed, and this is done by calling QApplication::setLayoutDirection(Qt::RightToLeft). The translation files for Qt contain a special marker called “LTR” that tells Qt whether the language is left-to-right or right-to-left, so we normally don’t need to call setLayoutDirection() ourselves.

It may prove more convenient for our users if we supply our applications with the translation files embedded in the executable, using Qt’s resource system. Not only does this reduce the number of files distributed as part of the product, but it also avoids the risk of translation files getting lost or deleted by accident. Assuming that the .qm files are located in a translations subdirectory in the source tree, we would then have a myapp.qrc file with the following contents:

<RCC>
<qresource>
    <file>translations/myapp_de.qm</file>
    <file>translations/myapp_fr.qm</file>
    <file>translations/myapp_zh.qm</file>
    <file>translations/qt_de.qm</file>
    <file>translations/qt_fr.qm</file>
    <file>translations/qt_zh.qm</file>
</qresource>
</RCC>

The .pro file would contain the following entry:

RESOURCES += myapp.qrc

Finally, in main(), we must specify :/translations as the path for the translation files. The leading colon indicates that the path refers to a resource as opposed to a file in the file system.

We have now covered all that is required to make an application able to operate using translations into other languages. But language and the direction of the writing system are not the only things that vary between countries and cultures. An internationalized program must also take into account the local date and time formats, monetary formats, numeric formats, and string collation order. Qt includes a QLocale class that provides localized numeric and date/time formats. To query other locale-specific information, we can use the standard C++ setlocale() and localeconv() functions.

Some Qt classes and functions adapt their behavior to the locale:

  • QString::localeAwareCompare() compares two strings in a locale-dependent manner. It is useful for sorting user-visible items.

  • The toString() function provided by QDate, QTime, and QDateTime returns a string in a local format when called with Qt::LocalDate as its argument.

  • By default, the QDateEdit and QDateTimeEdit widgets present dates in the local format.

Finally, a translated application may need to use different icons in certain situations rather than the original icons. For example, the left and right arrows on a web browser’s Back and Forward buttons should be swapped when dealing with a right-to-left language. We can do this as follows:

if (QApplication::isRightToLeft()) {
    backAction->setIcon(forwardIcon);
    forwardAction->setIcon(backIcon);
} else {
    backAction->setIcon(backIcon);
    forwardAction->setIcon(forwardIcon);
}

Icons that contain alphabetic characters very commonly need to be translated. For example, the letter ‘I’ on a toolbar button associated with a word processor’s Italic option should be replaced by a ‘C’ in Spanish (Cursivo) and by a ‘K’ in Danish, Dutch, German, Norwegian, and Swedish (Kursiv). Here’s a simple way to do it:

if (tr("Italic")[0] == 'C') {
    italicAction->setIcon(iconC);
} else if (tr("Italic")[0] == 'K') {
    italicAction->setIcon(iconK);
} else {
    italicAction->setIcon(iconI);
}

An alternative is to use the resource system’s support for multiple locales. In the .qrc file, we can specify a locale for a resource using the lang attribute. For example:

<qresource>
    <file>italic.png</file>
</qresource>
<qresource lang="es">
    <file alias="italic.png">cursivo.png</file>
</qresource>
<qresource lang="sv">
    <file alias="italic.png">kursiv.png</file>
</qresource>

If the user’s locale is es (Español), :/italic.png becomes a reference to the cursivo.png image. If the locale is sv (Svenska), the kursiv.png image is used. For other locales, italic.png is used.

Dynamic Language Switching

For most applications, detecting the user’s preferred language in main() and loading the appropriate .qm files there is perfectly satisfactory. But there are some situations where users might need the ability to switch languages dynamically. An application that is used continuously by different people in shifts may need to change languages without having to be restarted. For example, applications used by call center operators, by simultaneous translators, and by computerized cash register operators often require this capability.

Making an application able to switch languages dynamically requires a little more work than loading a single translation at startup, but it is not difficult. Here is what must be done:

  • Provide a means by which the user can switch language.

  • For every widget or dialog, set all of its translatable strings in a separate function (often called retranslateUi()) and call this function when the language changes.

Let’s review the relevant parts of a “call center” application’s source code. The application provides a Language menu (shown in Figure 18.1), to allow the user to set the language at run-time. The default language is English.

A dynamic Language menu

Figure 18.1. A dynamic Language menu

Since we don’t know which language the user will want to use when the application is started, we no longer load translations in the main() function. Instead, we will load them dynamically when they are needed, so all the code that we need to handle translations must go in the main window and dialog classes.

Let’s have a look at the application’s QMainWindow subclass.

MainWindow::MainWindow()
{
    journalView = new JournalView;
    setCentralWidget(journalView);

    qApp->installTranslator(&appTranslator);
    qApp->installTranslator(&qtTranslator);

    createActions();
    createMenus();

    retranslateUi();
}

In the constructor, we set the central widget to be a JournalView, a QTableWidget subclass. Then we install two QTranslator objects on the QApplication: The appTranslator object stores the current application’s translation, and the qtTranslator object stores Qt’s translation. At the end, we call the createActions() and createMenus() private functions to create the menu system, and we call retranslateUi() (also a private function) to set the user-visible strings for the first time.

void MainWindow::createActions()
{
    newAction = new QAction(this);
    newAction->setShortcut(QKeySequence::New);
    connect(newAction, SIGNAL(triggered()), this, SLOT(newFile()));
    ...
    exitAction = new QAction(this);
    connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));
    ...
    aboutQtAction = new QAction(this);
    connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}

The createActions() function creates the QAction objects as usual, but without setting any of the texts. These will be done in retranslateUi(). For actions that have standardized shortcuts, we can set the shortcut here using the appropriate enum, and rely on Qt to translate as necessary. For actions that have custom shortcuts, such as the Exit action, we set the shortcut in the retranslateUi() function, along with the text.

void MainWindow::createMenus()
{
    fileMenu = new QMenu(this);
    fileMenu->addAction(newAction);
    fileMenu->addAction(openAction);
    fileMenu->addAction(saveAction);
    fileMenu->addAction(exitAction);

    editMenu = new QMenu(this);
    ...
    createLanguageMenu();

    helpMenu = new QMenu(this);
    helpMenu->addAction(aboutAction);
    helpMenu->addAction(aboutQtAction);

    menuBar()->addMenu(fileMenu);
    menuBar()->addMenu(editMenu);
    menuBar()->addMenu(reportsMenu);
    menuBar()->addMenu(languageMenu);
    menuBar()->addMenu(helpMenu);
}

The createMenus() function creates menus, but does not give them any titles. Again, this will be done in retranslateUi().

In the middle of the function, we call createLanguageMenu() to fill the Language menu with the list of supported languages. We will review its source code in a moment. First, let’s look at retranslateUi():

void MainWindow::retranslateUi()
{
    newAction->setText(tr("&New"));
    newAction->setStatusTip(tr("Create a new journal"));
    ...
    exitAction->setText(tr("E&xit"));
    exitAction->setShortcut(tr("Ctrl+Q"));
    ...
    aboutQtAction->setText(tr("About &Qt"));
    aboutQtAction->setStatusTip(tr("Show the Qt library's About box"));

    fileMenu->setTitle(tr("&File"));
    editMenu->setTitle(tr("&Edit"));
    reportsMenu->setTitle(tr("&Reports"));
    languageMenu->setTitle(tr("&Language"));
    helpMenu->setTitle(tr("&Help"));

    setWindowTitle(tr("Call Center"));
}

The retranslateUi() function is where all the tr() calls for the MainWindow class occur. It is called at the end of the MainWindow constructor and every time a user changes the application’s language using the Language menu.

We set each QAction’s text and status tip, and the shortcuts for those actions that have non-standardized shortcuts. We also set each QMenu’s title, as well as the window title.

The createMenus() function presented earlier called createLanguageMenu() to populate the Language menu with a list of languages:

void MainWindow::createLanguageMenu()
{
    languageMenu = new QMenu(this);

    languageActionGroup = new QActionGroup(this);
    connect(languageActionGroup, SIGNAL(triggered(QAction *)),
            this, SLOT(switchLanguage(QAction *)));

    QDir qmDir = directoryOf("translations");
    QStringList fileNames =
            qmDir.entryList(QStringList("callcenter_*.qm"));

    for (int i = 0; i < fileNames.size(); ++i) {
        QString locale = fileNames[i];
        locale.remove(0, locale.indexOf('_') + 1);
        locale.chop(3);

        QTranslator translator;
        translator.load(fileNames[i], qmDir.absolutePath());
        QString language = translator.translate("MainWindow",
                                                "English");

        QAction *action = new QAction(tr("&%1 %2")
                                      .arg(i + 1).arg(language), this);
        action->setCheckable(true);
        action->setData(locale);

        languageMenu->addAction(action);
        languageActionGroup->addAction(action);

        if (language == "English")
            action->setChecked(true);
    }
}

Instead of hard-coding the languages supported by the application, we create one menu entry for each .qm file located in the application’s translations directory. The directoryOf() function is the same as the one we used in Chapter 17 (p. 410).

For simplicity, we assume that English also has a .qm file. An alternative would have been to call clear() on the QTranslator objects when the user chooses English.

One particular difficulty is to present a nice name for the language provided by each .qm file. Just showing “en” for “English” or “de” for “Deutsch”, based on the name of the .qm file, looks crude and will confuse some users. The solution used in createLanguageMenu() is to check the translation of the string “English” in the “MainWindow” context. That string should be translated to “Deutsch” in a German translation, to “Français” in a French translation, and to “A dynamic Language menu” in a Japanese translation.

We create one checkable QAction for each language and store the locale name in the action’s “data” item. We add them to a QActionGroup object to ensure that only one Language menu item is checked at a time. When the user chooses an action from the group, the QActionGroup emits the triggered(QAction *) signal, which is connected to switchLanguage().

void MainWindow::switchLanguage(QAction *action)
{
    QString locale = action->data().toString();
    QString qmPath = directoryOf("translations").absolutePath();

    appTranslator.load("callcenter_" + locale, qmPath);
    qtTranslator.load("qt_" + locale, qmPath);
    retranslateUi();
}

The switchLanguage() slot is called when the user chooses a language from the Language menu. We load the relevant translation files for the application and for Qt, and we call retranslateUi() to retranslate all the strings for the main window.

On Windows, an alternative to providing a Language menu is to respond to LocaleChange events, a type of event emitted by Qt when it detects a change in the environment’s locale. The event type exists on all platforms supported by Qt, but is only actually generated on Windows, when the user changes the system’s locale settings (in the Control Panel’s Regional and Language Options section). To handle LocaleChange events, we can reimplement QWidget::changeEvent() as follows:

void MainWindow::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::LocaleChange) {
        QString qmPath = directoryOf("translations").absolutePath();
        appTranslator.load("callcenter_"
                           + QLocale::system().name(), qmPath);
        qtTranslator.load("qt_" + QLocale::system().name(), qmPath);
        retranslateUi();
    }
    QMainWindow::changeEvent(event);
}

If the user switches locale while the application is being run, we attempt to load the correct translation files for the new locale and call retranslateUi() to update the user interface. In all cases, we pass the event to the base class’s changeEvent() function, since the base class may also be interested in LocaleChange or other change events.

We have now finished our review of the MainWindow code. Next we will look at the code for one of the application’s widget classes, the JournalView class, to see what changes are needed to make it support dynamic translation.

JournalView::JournalView(QWidget *parent)
    : QTableWidget(parent)
{
    ...
    retranslateUi();
}

The JournalView class is a QTableWidget subclass. At the end of the constructor, we call the private function retranslateUi() to set the widget’s strings. This is similar to what we did for MainWindow.

void JournalView::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::LanguageChange)
        retranslateUi();
    QTableWidget::changeEvent(event);
}

We also reimplement the changeEvent() function to call retranslateUi() on LanguageChange events. Qt generates a LanguageChange event when the contents of a QTranslator currently installed on QCoreApplication changes. In our application, this occurs when we call load() on appTranslator or qtTranslator, either from MainWindow::switchLanguage() or from MainWindow::changeEvent().

LanguageChange events should not be confused with LocaleChange events. LocaleChange events are generated by the system and tell the application, “Maybe you should load a new translation.” LanguageChange events are generated by Qt and tell the application’s widgets, “Maybe you should retranslate all your strings.”

When we implemented MainWindow, we didn’t need to respond to LanguageChange. Instead, we simply called retranslateUi() whenever we called load() on a QTranslator.

void JournalView::retranslateUi()
{
    QStringList labels;
    labels << tr("Time") << tr("Priority") << tr("Phone Number")
           << tr("Subject");
    setHorizontalHeaderLabels(labels);
}

The retranslateUi() function updates the column headers with newly translated texts, completing the translation-related code of a hand-written widget. For widgets and dialogs developed with Qt Designer, the uic tool automatically generates a function similar to our retranslateUi() function that is automatically called in response to LanguageChange events.

Translating Applications

Translating a Qt application that contains tr() calls is a three-step process:

  1. Run lupdate to extract all the user-visible strings from the application’s source code.

  2. Translate the application using Qt Linguist.

  3. Run lrelease to generate binary .qm files that the application can load using QTranslator.

Steps 1 and 3 are performed by application developers. Step 2 is handled by translators. This cycle can be repeated as often as necessary during the application’s development and lifetime.

As an example, we will show how to translate the Spreadsheet application in Chapter 3. The application already contains tr() calls around every user-visible string.

First, we must modify the application’s .pro file slightly to specify which languages we want to support. For example, if we want to support German and French in addition to English, we would add the following TRANSLATIONS entry to spreadsheet.pro:

TRANSLATIONS = spreadsheet_de.ts 
               spreadsheet_fr.ts

Here, we specify two translation files: one for German and one for French. These files will be created the first time we run lupdate and are updated every time we subsequently run lupdate.

These files normally have a .ts extension. They are in a straightforward XML format and are not as compact as the binary .qm files understood by QTranslator. It is lrelease’s job to convert human-readable .ts files into machine-efficient .qm files. For the curious, .ts stands for “translation source” and .qm for “Qt message” file.

Assuming that we are located in the directory that contains the Spreadsheet application’s source code, we can run lupdate on spreadsheet.pro from the command line as follows:

lupdate -verbose spreadsheet.pro

The -verbose option tells lupdate to provide more feedback than usual. Here’s the expected output:

Updating 'spreadsheet_de.ts'...
    Found 98 source texts (98 new and 0 already existing)
Updating 'spreadsheet_fr.ts'...
    Found 98 source texts (98 new and 0 already existing)

Every string that appears within a tr() call in the application’s source code is stored in the .ts files, along with an empty translation. Strings that appear in the application’s .ui files are also included.

The lupdate tool assumes by default that the arguments to tr() are Latin-1 strings. If this isn’t the case, we must add a CODECFORTR entry to the .pro file. For example:

CODECFORTR = EUC-JP

This must be done in addition to calling QTextCodec::setCodecForTr() from the application’s main() function.

Translations then need to be added to the spreadsheet_de.ts and spreadsheet_fr.ts files using Qt Linguist. (Figure 18.2 shows Qt Linguist in action.)

Qt Linguist in action

Figure 18.2. Qt Linguist in action

To run Qt Linguist, click Qt by Trolltech v4.x.y|Linguist in the Start menu on Windows, type linguist on the command line on Unix, or double-click Linguist in the Mac OS X Finder. To start adding translations to a .ts file, click File|Open and choose the file to translate.

The left-hand side of Qt Linguist’s main window shows a tree view. The top-level items are the contexts of the application being translated. For the Spreadsheet application, these are “FindDialog”, “GoToCellDialog”, “MainWindow”, “SortDialog”, and “Spreadsheet”. Every context has zero or more child items. Each child item occupies three columns, the first showing a Done flag, the second showing a source text, and the third showing any translation. The top-right area shows the current source text and its translation; this is where translations are added and edited. The bottom-right area is a list of suggestions automatically provided by Qt Linguist.

Once we have a translated .ts file, we need to convert it to a binary .qm file for it to be usable by QTranslator. To do this from within Qt Linguist, click File|Release. Typically, we would start by translating only a few strings and run the application with the .qm file to make sure that everything works properly.

If we want to regenerate the .qm files for all .ts files, we can use the lrelease tool as follows:

lrelease -verbose spreadsheet.pro

Assuming that we translated 19 strings to French and clicked the Done flag for 17 of them, lrelease produces the following output:

Updating 'spreadsheet_de.qm'...
    Generated 0 translations (0 finished and 0 unfinished)
    Ignored 98 untranslated source texts
Updating 'spreadsheet_fr.qm'...
    Generated 19 translations (17 finished and 2 unfinished)
    Ignored 79 untranslated source texts

Untranslated strings are shown in the original languages when running the application. The Done flag is ignored by lrelease; it can be used by translators to identify which translations are finished and which ones must be revisited.

When we modify the source code of the application, the translation files may become out of date. The solution is to run lupdate again, provide translations for the new strings, and regenerate the .qm files. Some development teams find it useful to run lupdate frequently, whereas others prefer to wait until the application is almost ready to release.

The lupdate and Qt Linguist tools are quite smart. Translations that are no longer used are kept in the .ts files in case they are needed in later releases. When updating .ts files, lupdate uses an intelligent merging algorithm that can save translators considerable time with text that is the same or similar in different contexts.

For more information about Qt Linguist, lupdate, and lrelease, refer to the Qt Linguist manual at http://doc.trolltech.com/4.3/linguist-manual.html. The manual contains a full explanation of Qt Linguist’s user interface and a step-by-step tutorial for programmers.



[*] Recent versions of the Unicode standard assign character values above 65535. These characters can be represented using sequences of two 16-bit values called “surrogate pairs”.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset