Dynamic Language Selection in Qt
In this blog post, we will show an example of how to support dynamic language selection in an application using Qt. By "dynamic", I mean that the application can change the displayed language from within the application at run-time, rather than picking it up from the locale once on startup.
Text or ID-Based Translations?
In this example we will be using the text identifier translation mechanism, which may not be familiar to you if you have only used Qt's more common string-based translation method using the tr() method.
The ID-based method (1) is described as an "industrial strength" system for internationalization and localization in the Qt documentation. This system offers a number of advantages, particularly in large applications. For example, some applications may already have translations maintained in a database, spreadsheet, or other ID-based system, which is more easily supported in Qt using the ID-based approach.
For small applications and those being developed from scratch, the simpler string-based system may be more suitable.
Basics
The basic steps for implementing this localization scheme are as follows:
1. Identify the text strings in all GUI controls (window titles, menu items, tool tips, labels for buttons, check boxes and radio buttons, etc.) and define an appropriate unique identifier for each text element. Given that, we can create two files. In the case of our example we call them translation.h and translation.cpp. The first contains declarations for the global identifiers, like this:
extern const char *ID_LABEL_TEXT;
The second file contains definitions of these identifiers that will be used later in the translation (*.ts) files, e.g.
const char *ID_LABEL_TEXT = QT_TRID_NOOP("id_label_text");
In order to provide dynamic translation, we use the QT_TRID_NOOP macro in conjunction with the function qtTrId(). Elsewhere in our application's source code we should use the function qtTrId() for the actual translations. For example:
QLabel *label = new Qlabel(qtTrId(ID_LABEL_TEXT));
or
label->setText(qtTrId(ID_LABEL_TEXT));
2. Create the translation files.
A Qt translation file is just an XML file. Qt provides the lupdate utility, which is used for creating translation files. The tool collects all the identified strings into a group of XML files (one for each language), named with the ts file extension.
Typically, all ts files are kept in a directory with a suitable name, e.g. i18n. We can run the lupdate utility to extract the string identifier information and generate the translation files. In our example application, we support three different languages, and can generate the files using these commands (which should be run from within the i18n directory):
lupdate ../translation.cpp -ts lang_en_US.ts lupdate ../translation.cpp -ts lang_ru_RU.ts lupdate ../translation.cpp -ts lang_fr_FR.ts
Unlike the string-based approach, the generated ts files do not contain the text to be translated, but instead contain the unique identifiers for each string. Here is a portion of one of the files:
<message id="id_label_text"> <location filename="../translation.cpp" line="4"/> <source></source> <translation type="unfinished"></translation> </message>
We now have to fill in the translations, using an XML or text editor or, preferably, using the graphical Qt Linguist tool.
3. Run the Qt Linguist application.
A human translator needs to add appropriate translations for each string to the ts file. Next, we need to run the lrelease command on each ts file, e.g.
lrelease -idbased *.ts
The above command generates lightweight binary (qm) files that are associated with a particular ts file. These files are used by the QTranslator object, which provides the functions to look up translations in a qm file. Note that we need to use the -idbased option to lrelease when using the ID-based translation scheme.
Example Application
The demonstration application (2) illustrates how to use ID-based translation. It covers the two different ways commonly used for creating a GUI with Qt: using Qt Designer with a UI form (in this case a MainWindow class), and in hand-written C++ code (in this case a Dialog class).
The example supports three different languages: English, French and Russian, and contains a custom class: TranslatorManager. The TranslatorManager class is implemented as a singleton and is used as a container for QTranslator classes. It creates a QTranslator class for each supported language and allows setting or changing the language for the application.
It should be mentioned that if you dynamically change the language in your application, a change event of type QEvent::LanguageChange is emitted. You'll have to catch that event and reset the text everywhere in your application.
The example application will build with Qt version 4 or 5. It uses some C++11 language features so you need a reasonably recent version of C++ compiler.
Some screen shots are shown below:
Summary
This blog post presented an example of dynamic language selection using Qt's ID-based localization method. While the example was in C++ and used widgets, it should be noted that the same scheme is supported in QML using the qsTrId() function, so it is applicable to Qt Quick applications as well.
References
- Qt Linguist Manual: Text ID Based Translations, Qt documentation website, last accessed 22 May 2015, http://doc.qt.io/qt-5/linguist-id-based-i18n.html
- Source code for example application, ftp://ftp.ics.com/pub/pickup/MultiLanguageApp.zip