Showing posts with label QML translation dynamic. Show all posts
Showing posts with label QML translation dynamic. Show all posts

Friday, July 1, 2011

Dynamic translations in pure QML
























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