Anyone who develop in QML knows for a fact that it is a problem to translate those at runtime.
I am currently working on a new opensource project named qwazer which is a web client for Waze written in pure QML. I initially did it as a Hebrew & Israel only client, but then there was a lot of requests for multi-country/multi-lingual support.
Still wanting to stay in pure QML, it would seems that there was no way but to use the QtLinguist for translations and use QT/C++ calls.
I said no to that after several attempts and came up with a dynamic translations system where I can change the language with a click of a button from inside the application QML itself without involving any QT/C++ code.
Howto:
translator.js - This is uncomplete as I will add soon a mechanism that loads all translation files dynamically from other JS files as needed in order to avoid putting all translation in the same JS:
var _currentTranslation;
var _translations = [];
var _hebrewTranslation = {"Settings": "הגדרות", "Language%1": "שפה%1"};
function initializeTranslation() {
console.log("initialized translations");
_translations["en"] = {};
_translations["he"] = _hebrewTranslation;
_currentTranslation = _translations["en"];
}
function setLanguage(languageId) {
console.log("language set requested: " + languageId);
_currentTranslation = _translations[languageId];
console.log("language was set");
}
function translate(key, args) {
console.log("translating " + key);
var value = key;
if (typeof(_currentTranslation) != "undefined" && typeof(_currentTranslation[key]) != "undefined")
{
value = _currentTranslation[key];
}
if (typeof(args) != "undefined")
{
for(var i=0; i<args.length; i++)
value = value.replace(eval("/%"+(i+1)+"/"), args[i]);
}
return value;
}
Translator.qml:
import QtQuick 1.0
import "js/translator.js" as Translator
QtObject {
signal retranslateRequired(string langId)
function initializeTranslation() {
Translator.initializeTranslation();
}
function setLanguage(languageId) {
Translator.setLanguage(languageId);
retranslateRequired(languageId)
}
function translate(key, args) {
return Translator.translate(key, args);
}
}
root.qml:
Rectangle {
id: mainView
width: 800
height: 400
property string forceTranslate
onForceTranslateChanged: console.log("retranslation requested")
Connections {
target: translator
onRetranslateRequired: forceTranslateChanged()
}
Translator {
id: translator
}
...
settings.qml - translator and persistent configuration maintainer:
Rectangle {
id: qwazerSettings
signal settingsLoaded
function initialize() {
translator.initializeTranslation();
Storage.initialize();
qwazerSettings.state = "Loaded";
settingsLoaded();
}
...
onLanguageChanged : {
Storage.setObjectSetting("Language", language);
translator.setLanguage(language.langId);
retranslateRequired(language.langId);
}
qml texts that needs translating:
Text {
id: languageLabel
text: translator.translate("Language%1", ":") + mainView.forceTranslate
font.pointSize: 20
}
Explanation:
- The translator is initialized at earlymost possible
- Whenever the language changes, I call the event that will eventually set the current translation map
- Translation and string format (denotes by %1, %2, etc...) is done by the translate function of the translator.
- In order to reevaluate the translation, I concat an empty string and link between the language changed event and the empty string event - found it in a forum
- The keys are actually the English translation - and that is why I set the English translations to empty