I have a single page Marionette app built on RequireJS which needs to support translations.
My goal is to have a dictionary file for each language, and based on the logged in user's configuration, load the relevant file.
Since most of the users will use English, I want to bundle the English dictionary in the app during build (using r.js).
I wrote a small Translator module, which basically wraps jed.js (the library I'm using for i18n):
//in myTranslator.js
define(function (require) {
"use strict";
var Jed = require("jed");
var localeData = require("json!locales/en_US.json");
var Translator = function () {
var i18n = new Jed({
"domain": "messages",
"locale_data": localeData
});
return i18n;
};
return Translator;
});
//in app.js
define(function(require){
var Translator = require("myTranslator");
var translator = new Translator();
});
As you can see, the locale data is loaded from a static file.
I want to be able to pass in the locale to the Translator constructor, and based on that, load the correct JSON file.
How can that be done together with keeping the English JSON bundled with the built project?
You should be able to check user settings, construct a dependency string, pass it to Translator and then use it instead of localeData — r.js will ignore the dynamic dependency but should bundle EN locale.
if ( userLocale && userLocale !== 'en_US' ) {
var localePath = 'json!locales/' + userLocale + '.json';
require([ localePath ], function( locale ) {
var translator = new Translator( locale );
});
}
and inside Translator: "locale_data": passedData || englishData
.
(or do the same inside the Translator module, like if ( userLocale !== 'en_US' ) { require([path], function(locale) {...})
)
In theory it should work, though you cannot use simplified CommonJS here and should use callback-require, otherwise you'll get Module name ... has not been loaded yet for context error.
This is the solution I ended up doing.
It worked out quite nicely, and I also learnt about using $.Deferred which was great!
The key for me was using the require text plugin as a loader in the code.
The default locale is set as a dependency, that way it's baked in the build as well.
Explanations are in the code below:
//in translator.js
define(function (require) {
"use strict";
var $ = require("jquery");
var _ = require("underscore");
var Jed = require("jed");
var text = require("text");
var defaultDictionary = require("json!locales/en_US.json");
var Translator;
Translator = (function () {
var DEFAULT_LOCALE = "en_US";
var defaultLocaleData = {
locale: DEFAULT_LOCALE,
dictionary: defaultDictionary
};
var createTranslator = function (localeData) {
//create the actual Jed instance with the relevant dictionary
var i18n = new Jed({
"domain": "messages",
"locale_data": localeData.dictionary
});
return i18n;
};
var parseLocaleDictionary = function (locale, dictionary) {
//parse the dictionary JSON string loaded by text plugin...
//handle errors in parsing if needed
};
//get to work here
var getTranslatorForLocale = function (locale) {
//$gettingData promise will be resolved when data for Jed is loaded and ready
var $gettingData = $.Deferred();
//$creatingTranslator promise will be returned to caller and will be resolved when everything's done
var $creatingTranslator = $.Deferred();
if (!locale || locale === DEFAULT_LOCALE) {
//default locale, so resolve right away because we required it already
$gettingData.resolve(defaultLocaleData);
}
else {
//need to load the dictionary from here
var dictionaryUrl = require.toUrl("locales/" + locale + ".json");
//this is the dynamic loading
text.get(
dictionaryUrl,
function (dictionary) {
//if successful, parse the JSON string and use that dictionary
var localeData = parseLocaleDictionary(locale, dictionary);
$gettingData.resolve(localeData);
},
function (error) {
//on load error, use the default and resolve promise
$gettingData.resolve(defaultLocaleData);
});
}
//once the data is ready, we can create the translator instance
$gettingData.done(function (localeData) {
var i18n = createTranslator(localeData);
//notify caller that translator is ready
$creatingTranslator.resolve(i18n);
});
return $creatingTranslator.promise();
};
return {
//this function is returned to the user of Translator
getTranslator: function (locale) {
var $gettingTranslator = getTranslatorForLocale(locale);
return $gettingTranslator;
}
};
}());
return Translator;
});
//in app.js
define(function (require) {
var Translator = require("translator");
//in app.js
var myTranslator;
var userLocale = "fr_FR";
//this is a promise that will be resolved when translator is ready
var $getTranslator = Translator.getTranslator(userLocale);
//when translator is ready, store it
$getTranslator.done(function (translator) {
myTranslator = translator;
});
//...
});