Making Applications Translation-Aware

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

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.

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:

BlueWidget::BlueWidget(QWidget *parent, const char *name) : QWidget(parent, name) { QString str1 = tr("Legal"); QString str2 = BlueWidget::tr("Legal"); QString str3 = YellowDialog::tr("Legal"); QString str4 = YellowDialog::tr("Legal", "US paper size"); }

The first two calls to tr() have "BlueWidget" as context, and the last two calls have "YellowDialog". All four have "Legal" as 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 normally 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!"), 0); app.setMainWidget(&button); button.show(); return app.exec(); }

This idiom is useful for translating the name of the application. Instead of typing it multiple times and leaving the translator to translate it for each class it appears in, it is usually more convenient to define an APPNAME macro that expands to the translated application name and to put the macro in a header file included by all the application's files:

#define APPNAME MainWindow::tr("OpenDrawer 2D")

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 QApplications::translate() function, which accepts up to three arguments: the context, the source text, and the optional comment. For example, here's another way to define APPNAME:

#define APPNAME qApp->translate("Global Stuff", "OpenDrawer 2D")

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()->message(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()->message(tr("Host %1 found") .arg(hostName));

Notice how it works: The string literal "Host %1 found" is passed to tr(). Assuming 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 }; int i = 0; while (flowers[i]) { comboBox->insertItem(tr(flowers[i])); ++i; } }

The QT_TR_NOOP() 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, which 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 first start doing 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_ASCII preprocessor symbol before including . The easiest way to ensure this symbol is set is to add the following line to the application's .pro file:

DEFINES += QT_NO_CAST_ASCII

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

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(QString("app_") + QTextCodec::locale(), qApp->applicationDirPath()); app.installTranslator(&appTranslator); ... return app.exec(); }

The QTextCodec::locale() function returns a string that specifies the user's locale. Locales 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, load() first attempts to load the file app_fr_CA.ISO8859-15.qm. If this file does not exist, load() next tries app_fr_CA.qm, then app_fr.qm, and finally app.qm before giving up. Normally, we would only provide app_fr.qm, containing a standard French translation, but if we need a different file for French-speaking Canada, we can also provide app_fr_CA.qm and it will be used for fr_CA locales.

The second argument to 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 same directory as the executable.

The Qt library itself contains a few strings that need to be translated. Trolltech provides French and German 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 library's translation file should also be loaded:

QTranslator qtTranslator; qtTranslator.load(QString("qt_") + QTextCodec::locale(), qApp->applicationDirPath()); app.installTranslator(&qtTranslator);

A QTranslator object can only hold 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. QApplication 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, which is done by calling QApplication::setReverseLayout(true). The translation files for the Qt library 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 worry about it.

It may prove more convenient for our users if we supply our applications with the translation files embedded into the executable. 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. Qt provides the qembed tool (located in Qt's tools directory), which can convert .qm files to a C++ array that can be passed to QTranslator::load().

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 3.2 provides no specific functions for accessing these, but we can use the standard C++ setlocale() and localeconv() functions to query the program's current locale.[*]

[*] Qt 3.3 will probably include a QLocale class that will provide localized numeric formats

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

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::reverseLayout()) { backAct->setIconSet(forwardIcon); forwardAct->setIconSet(backIcon); } else { backAct->setIconSet(backIcon); forwardAct->setIconSet(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 quick way to do it:

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

Категории