Search code examples
javascriptinternationalizationrequirejsamdjs-amd

Load Locale File Dynamically using Requirejs


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?


Solution

  • 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;
        });
        //...
    });