How to translate a QML application
…and dynamically switch between languages at runtime.
Ideally, it would be nice if you started with the following articles from Qt’s documentation:
But no doubt, you don’t have time for this, so here’s a crash-course in my interpretation for you:
All the UI strings in your code should be surrounded by tr() function. And then you need to feed your source code to the lupdate tool, so it will parse all the strings into a separate translation file, which can be translated with Qt Linguist tool. After that you can use those translation files to switch between languages in your Qt application.
Although, in case of QML application it is a bit more complicated. First thing - instead of of tr()
you need to use qsTr()
function, and second - you need to do quite a trick for QQmlEngine to reevaluate text and apply corresponding translation. I discovered all this in the article at Qt’s wiki, but I didn’t like some parts of the article (for instance, the way of loading the corresponding translation), so I decided to create my own tutorial.
Ok, let’s start right away with the trick. You need to create a class and then add an object of that class into QmlEngine’s context.
trans.h:
#ifndef TRANS_H
#define TRANS_H
#include <QObject>
#include <QTranslator>
class Trans : public QObject
{
Q_OBJECT
// that's the "magic" string for the trick
Q_PROPERTY(QString emptyString READ getEmptyString NOTIFY languageChanged)
public:
Trans();
QString getEmptyString();
Q_INVOKABLE void selectLanguage(QString language);
signals:
void languageChanged();
private:
QTranslator *translator;
};
#endif // TRANS_H
trans.cpp:
#include "trans.h"
#include <QDebug>
#include <QGuiApplication>
#include <QDir>
Trans::Trans()
{
translator = new QTranslator(this);
}
QString Trans::getEmptyString()
{
return QString();
}
void Trans::selectLanguage(QString language)
{
// working folder
QDir dir = QDir(qApp->applicationDirPath()).absolutePath();
// #ifdef Q_OS_MACOS // crutch for Mac OS
// dir.cdUp();
// dir.cdUp();
// dir.cdUp();
// #endif
// qDebug() << dir.path();
if (!translator->load(
// for example, in case of "ru" language the file would be
// translating-qml_ru.qm
// extension is set automatically
QString("translating-qml_%1").arg(language),
// look for the file in i18n folder within working directory
QString("%1/i18n").arg(dir.path())
)
)
{
qDebug() << "Failed to load translation file, falling back to English";
}
// it's a global thing, we can use it anywhere (after #including <QGuiApplication>)
qApp->installTranslator(translator);
emit languageChanged();
}
main.cpp:
#include "trans.h"
// ...
// object of our class with "magic" property for translation
Trans trans;
QQmlApplicationEngine engine;
// make this object available from QML side
engine.rootContext()->setContextProperty("trans", &trans);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
// ...
That’s it. Now we can use it for strings in our UI. For example I have this in my main.qml
:
Button {
text: qsTr("Simple button") + trans.emptyString
onClicked: console.log(qsTr("Some output into console") + trans.emptyString)
ToolTip.visible: pressed
ToolTip.text: qsTr("Some tooltip") + trans.emptyString
}
As you can see, every string is not only processed by qsTr()
function, but also has this + trans.emptyString
expression. That’s the trick itself - it forces QmlEngine to re-evaluate the text, and without this expression it won’t happen. It looks like a dirty trick, and I consider it dirty as well, but apparently so far that’s the only way to do the dynamic language switching in QML application, because related bug was reported in motherfucking 2010 and has not yet been fixed - that’s more than 6 (six) years for the moment, for fuck’s sake.
Ok, save main.qml
. Now we need to generate a translation file. For that purpose there is an utility lupdate
. Create a i18n
folder in your project directory and execute the following command:
/path/to/Qt/5.7/clang_64/bin/lupdate main.qml -ts i18n/translating-qml_ru.ts
By the _ru
suffix it will understand, that this translation file is meant for the Russian language. The same way we can create translation files for Norwegian and German languages:
/path/to/Qt/5.7/clang_64/bin/lupdate main.qml -ts i18n/translating-qml_no.ts
and
/path/to/Qt/5.7/clang_64/bin/lupdate main.qml -ts i18n/translating-qml_de.ts
Of course, we don’t want to do this manually for each language and especially if we have more than just main.qml
that requires translation. So let’s add some stuff into the .pro
file:
# list of source files containing strings for translation
lupdate_only { # that way those files will be skipped by C++ compiler
SOURCES = *.qml # \
# pages/*.qml
}
# list of language files that will store translated strings for every language we want
# create i18n directory first, if you don't have it
TRANSLATIONS = i18n/translating-qml_de.ts \
i18n/translating-qml_no.ts \
i18n/translating-qml_ru.ts
As you could’ve noticed, we didn’t create a translation file for English language. But that’s okay, because it is the default language of the UI, so when Translator will fail to load translating-qml_en.qm
, it will just fall back to the “hardcoded” English.
Now we can run lupdate
just once for all listed files and generate translation files for all required languages:
/path/to/Qt/5.7/clang_64/bin/lupdate translating-qml.pro
Okay, we got .ts
files which simply are just XML files, so you can easily open them in any text editor. But they are supposed to be processed by the Qt Linguist tool, so open them there:
The translation process goes like this:
- Select string to translate;
- Write the translation;
- Mark this string as translated.
After you’re done with all strings, you can save and “release” the translation by pressing corresponding item in the File menu. The translation will be “compiled” into .qm
format. Or you can just translate and save all your .ts
files and then use lrelease
tool for batch “compiling”:
/path/to/Qt/5.7/clang_64/bin/lrelease translating-qml.pro
Now back to code. You need some button or menu item that will trigger selectLanguage()
function with corresponding language. For example:
onClicked: {
onClicked: trans.selectLanguage("ru");
}
After you’ll have some UI with translations ready to test, you need to deploy the .qm
files. Move them into the folder with your application binaries (the build folder)). In our case, it is again the i18n
folder as we specified in trans.cpp
, for example: build-translating-qml-Desktop_Qt_5_7_1_clang_64bit-Debug/i18n/
.
For Mac OS it’s a bit different, because there applications are bundled into the .app
package, so you need to open Package Contents and place i18n
folder with .qm
files into the translating-qml.app/Contents/MacOS/i18n
:
That’s it. You can now run the application and try to click the button / menu to switch the current language of UI without restarting the application.
In case you’ve (most probably) got lost in my tutorial, here is a demo application with sources:
If you need to go deeper, check out the following links:
…because related bug was reported in motherfucking 2010 and has not yet been fixed
In Qt 5.10 they actually did fix it. Now there is a special QQmlEngine::retranslate() function that does the job, so there is no need in this magic empty string anymore:
...
qApp->installTranslator(_translator);
_engine->retranslate();
emit languageChanged();
...
I’ve updated my demo application.
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks