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.

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;


import QtQuick 1.0
import "js/translator.js" as Translator

QtObject {

signal retranslateRequired(string langId)

function initializeTranslation() {

function setLanguage(languageId) {

function translate(key, args) {
return Translator.translate(key, args);


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() {
qwazerSettings.state = "Loaded";


onLanguageChanged : {
Storage.setObjectSetting("Language", language);

qml texts that needs translating:

Text {
id: languageLabel
text: translator.translate("Language%1", ":") + mainView.forceTranslate
font.pointSize: 20

  • 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