MediaWiki:Gadget-I18n-js.js: Difference between revisions

From Tardis Wiki, the free Doctor Who reference
m (handle json parse errors)
m (use spaces over tabs)
Line 4: Line 4:
  *
  *
  * @author Cqm <http://dev.wikia.com/User:Cqm>
  * @author Cqm <http://dev.wikia.com/User:Cqm>
  * @version 0.3.1
  * @version 0.3.2
  *
  *
  * @todo improve error reporting for empty/invalid/misspelt message pages
  * @todo improve error reporting for empty/invalid/misspelt message pages
Line 12: Line 12:


/*jshint bitwise:true, camelcase:true, curly:true, eqeqeq:true, es3:false,
/*jshint bitwise:true, camelcase:true, curly:true, eqeqeq:true, es3:false,
forin:true, immed:true, indent:4, latedef:true, newcap:true,
    forin:true, immed:true, indent:4, latedef:true, newcap:true,
noarg:true, noempty:true, nonew:true, plusplus:true, quotmark:single,
    noarg:true, noempty:true, nonew:true, plusplus:true, quotmark:single,
undef:true, unused:true, strict:true, trailing:true,
    undef:true, unused:true, strict:true, trailing:true,
browser:true, devel:false, jquery:true,
    browser:true, devel:false, jquery:true,
onevar:true
    onevar:true
*/
*/


;(function (window, $, mw, undefined) {
;(function (window, $, mw, undefined) {
'use strict';
    'use strict';


window.dev = window.dev || {};
    window.dev = window.dev || {};


// prevent double loading and loss of cache
    // prevent double loading and loss of cache
if (window.dev.i18n !== undefined) {
    if (window.dev.i18n !== undefined) {
return;
        return;
}
    }


/*
        /*
* Cache of mw config variables.
        * Cache of mw config variables.
*/
        */
var conf = mw.config.get([
    var conf = mw.config.get([
'wgContentLanguage',
            'wgContentLanguage',
'wgUserLanguage'
            'wgUserLanguage'
]),
        ]),


/*
        /*
* Cache of loaded I18n instances.
        * Cache of loaded I18n instances.
*/
        */
cache = {},
        cache = {},


/*
        /*
* Language fallbacks for those that don't fallback to English.
        * Language fallbacks for those that don't fallback to English.
* Shouldn't need updating unless Wikia change theirs.
        * Shouldn't need updating unless Wikia change theirs.
*
        *
* To generate this, use `$ grep -R "fallback =" /path/to/messages/`,
        * To generate this, use `$ grep -R "fallback =" /path/to/messages/`,
* pipe the result to a text file and format the result.
        * pipe the result to a text file and format the result.
*/
        */
fallbacks = {
        fallbacks = {
'ab': 'ru',
            'ab': 'ru',
'ace': 'id',
            'ace': 'id',
'aln': 'sq',
            'aln': 'sq',
'als': 'gsw',
            'als': 'gsw',
'an': 'es',
            'an': 'es',
'anp': 'hi',
            'anp': 'hi',
'arn': 'es',
            'arn': 'es',
'arz': 'ar',
            'arz': 'ar',
'av': 'ru',
            'av': 'ru',
'ay': 'es',
            'ay': 'es',
'ba': 'ru',
            'ba': 'ru',
'bar': 'de',
            'bar': 'de',
'bat-smg': 'sgs',
            'bat-smg': 'sgs',
'bcc': 'fa',
            'bcc': 'fa',
'be-x-old': 'be-tarask',
            'be-x-old': 'be-tarask',
'bh': 'bho',
            'bh': 'bho',
'bjn': 'id',
            'bjn': 'id',
'bm': 'fr',
            'bm': 'fr',
'bpy': 'bn',
            'bpy': 'bn',
'bqi': 'fa',
            'bqi': 'fa',
'bug': 'id',
            'bug': 'id',
'cbk-zam': 'es',
            'cbk-zam': 'es',
'ce': 'ru',
            'ce': 'ru',
'ckb': 'ckb-arab',
            'ckb': 'ckb-arab',
'crh': 'crh-latn',
            'crh': 'crh-latn',
'crh-cyrl': 'ru',
            'crh-cyrl': 'ru',
'csb': 'pl',
            'csb': 'pl',
'cv': 'ru',
            'cv': 'ru',
'de-at': 'de',
            'de-at': 'de',
'de-ch': 'de',
            'de-ch': 'de',
'de-formal': 'de',
            'de-formal': 'de',
'dsb': 'de',
            'dsb': 'de',
'dtp': 'ms',
            'dtp': 'ms',
'eml': 'it',
            'eml': 'it',
'ff': 'fr',
            'ff': 'fr',
'fiu-vro': 'vro',
            'fiu-vro': 'vro',
'frc': 'fr',
            'frc': 'fr',
'frp': 'fr',
            'frp': 'fr',
'frr': 'de',
            'frr': 'de',
'fur': 'it',
            'fur': 'it',
'gag': 'tr',
            'gag': 'tr',
'gan': 'gan-hant',
            'gan': 'gan-hant',
'gan-hans': 'zh-hans',
            'gan-hans': 'zh-hans',
'gan-hant': 'zh-hant',
            'gan-hant': 'zh-hant',
'gl': 'pt',
            'gl': 'pt',
'glk': 'fa',
            'glk': 'fa',
'gn': 'es',
            'gn': 'es',
'gsw': 'de',
            'gsw': 'de',
'hif': 'hif-latn',
            'hif': 'hif-latn',
'hsb': 'de',
            'hsb': 'de',
'ht': 'fr',
            'ht': 'fr',
'ii': 'zh-cn',
            'ii': 'zh-cn',
'inh': 'ru',
            'inh': 'ru',
'iu': 'ike-cans',
            'iu': 'ike-cans',
'jut': 'da',
            'jut': 'da',
'jv': 'id',
            'jv': 'id',
'kaa': 'kk-latn',
            'kaa': 'kk-latn',
'kbd': 'kbd-cyrl',
            'kbd': 'kbd-cyrl',
'kbd-cyrl': 'ru',
            'kbd-cyrl': 'ru',
'khw': 'ur',
            'khw': 'ur',
'kiu': 'tr',
            'kiu': 'tr',
'kk': 'kk-cyrl',
            'kk': 'kk-cyrl',
'kk-arab': 'kk-cyrl',
            'kk-arab': 'kk-cyrl',
'kk-cn': 'kk-arab',
            'kk-cn': 'kk-arab',
'kk-kz': 'kk-cyrl',
            'kk-kz': 'kk-cyrl',
'kk-latn': 'kk-cyrl',
            'kk-latn': 'kk-cyrl',
'kk-tr': 'kk-latn',
            'kk-tr': 'kk-latn',
'kl': 'da',
            'kl': 'da',
'koi': 'ru',
            'koi': 'ru',
'ko-kp': 'ko',
            'ko-kp': 'ko',
'krc': 'ru',
            'krc': 'ru',
'ks': 'ks-arab',
            'ks': 'ks-arab',
'ksh': 'de',
            'ksh': 'de',
'ku': 'ku-latn',
            'ku': 'ku-latn',
'ku-arab': 'ckb',
            'ku-arab': 'ckb',
'kv': 'ru',
            'kv': 'ru',
'lad': 'es',
            'lad': 'es',
'lb': 'de',
            'lb': 'de',
'lbe': 'ru',
            'lbe': 'ru',
'lez': 'ru',
            'lez': 'ru',
'li': 'nl',
            'li': 'nl',
'lij': 'it',
            'lij': 'it',
'liv': 'et',
            'liv': 'et',
'lmo': 'it',
            'lmo': 'it',
'ln': 'fr',
            'ln': 'fr',
'ltg': 'lv',
            'ltg': 'lv',
'lzz': 'tr',
            'lzz': 'tr',
'mai': 'hi',
            'mai': 'hi',
'map-bms': 'jv',
            'map-bms': 'jv',
'mg': 'fr',
            'mg': 'fr',
'mhr': 'ru',
            'mhr': 'ru',
'min': 'id',
            'min': 'id',
'mo': 'ro',
            'mo': 'ro',
'mrj': 'ru',
            'mrj': 'ru',
'mwl': 'pt',
            'mwl': 'pt',
'myv': 'ru',
            'myv': 'ru',
'mzn': 'fa',
            'mzn': 'fa',
'nah': 'es',
            'nah': 'es',
'nap': 'it',
            'nap': 'it',
'nds': 'de',
            'nds': 'de',
'nds-nl': 'nl',
            'nds-nl': 'nl',
'nl-informal': 'nl',
            'nl-informal': 'nl',
'no': 'nb',
            'no': 'nb',
'os': 'ru',
            'os': 'ru',
'pcd': 'fr',
            'pcd': 'fr',
'pdc': 'de',
            'pdc': 'de',
'pdt': 'de',
            'pdt': 'de',
'pfl': 'de',
            'pfl': 'de',
'pms': 'it',
            'pms': 'it',
// 'pt': 'pt-br',
            // 'pt': 'pt-br',
'pt-br': 'pt',
            'pt-br': 'pt',
'qu': 'es',
            'qu': 'es',
'qug': 'qu',
            'qug': 'qu',
'rgn': 'it',
            'rgn': 'it',
'rmy': 'ro',
            'rmy': 'ro',
'rue': 'uk',
            'rue': 'uk',
'ruq': 'ruq-latn',
            'ruq': 'ruq-latn',
'ruq-cyrl': 'mk',
            'ruq-cyrl': 'mk',
'ruq-latn': 'ro',
            'ruq-latn': 'ro',
'sa': 'hi',
            'sa': 'hi',
'sah': 'ru',
            'sah': 'ru',
'scn': 'it',
            'scn': 'it',
'sg': 'fr',
            'sg': 'fr',
'sgs': 'lt',
            'sgs': 'lt',
'shi': 'ar',
            'shi': 'ar',
'simple': 'en',
            'simple': 'en',
'sli': 'de',
            'sli': 'de',
'sr': 'sr-ec',
            'sr': 'sr-ec',
'srn': 'nl',
            'srn': 'nl',
'stq': 'de',
            'stq': 'de',
'su': 'id',
            'su': 'id',
'szl': 'pl',
            'szl': 'pl',
'tcy': 'kn',
            'tcy': 'kn',
'tg': 'tg-cyrl',
            'tg': 'tg-cyrl',
'tt': 'tt-cyrl',
            'tt': 'tt-cyrl',
'tt-cyrl': 'ru',
            'tt-cyrl': 'ru',
'ty': 'fr',
            'ty': 'fr',
'udm': 'ru',
            'udm': 'ru',
'ug': 'ug-arab',
            'ug': 'ug-arab',
'uk': 'ru',
            'uk': 'ru',
'vec': 'it',
            'vec': 'it',
'vep': 'et',
            'vep': 'et',
'vls': 'nl',
            'vls': 'nl',
'vmf': 'de',
            'vmf': 'de',
'vot': 'fi',
            'vot': 'fi',
'vro': 'et',
            'vro': 'et',
'wa': 'fr',
            'wa': 'fr',
'wo': 'fr',
            'wo': 'fr',
'wuu': 'zh-hans',
            'wuu': 'zh-hans',
'xal': 'ru',
            'xal': 'ru',
'xmf': 'ka',
            'xmf': 'ka',
'yi': 'he',
            'yi': 'he',
'za': 'zh-hans',
            'za': 'zh-hans',
'zea': 'nl',
            'zea': 'nl',
'zh': 'zh-hans',
            'zh': 'zh-hans',
'zh-classical': 'lzh',
            'zh-classical': 'lzh',
'zh-cn': 'zh-hans',
            'zh-cn': 'zh-hans',
'zh-hant': 'zh-hans',
            'zh-hant': 'zh-hans',
'zh-hk': 'zh-hant',
            'zh-hk': 'zh-hant',
'zh-min-nan': 'nan',
            'zh-min-nan': 'nan',
'zh-mo':  'zh-hk',
            'zh-mo':  'zh-hk',
'zh-my':  'zh-sg',
            'zh-my':  'zh-sg',
'zh-sg':  'zh-hans',
            'zh-sg':  'zh-hans',
'zh-tw':  'zh-hant',
            'zh-tw':  'zh-hant',
'zh-yue': 'yue'
            'zh-yue': 'yue'
};
        };


/*
    /*
* Get a translation of a message from the messages object in the
    * Get a translation of a message from the messages object in the
* requested language.
    * requested language.
*
    *
* @param messages the message object to look the message up in.
    * @param messages the message object to look the message up in.
* @param name The name of the message to get.
    * @param name The name of the message to get.
* @param lang The language to get the message in.
    * @param lang The language to get the message in.
*
    *
* @return The requested translation or the name wrapped in < ... > if no
    * @return The requested translation or the name wrapped in < ... > if no
*    message could be found.
    *    message could be found.
*/
    */
function getMsg(messages, name, lang) {
    function getMsg(messages, name, lang) {
if (messages[lang] && messages[lang][name]) {
        if (messages[lang] && messages[lang][name]) {
return messages[lang][name];
            return messages[lang][name];
}
        }


if (lang === 'en') {
        if (lang === 'en') {
return '<' + name + '>';
            return '<' + name + '>';
}
        }


lang = fallbacks[lang] || 'en';
        lang = fallbacks[lang] || 'en';
return getMsg(messages, name, lang);
        return getMsg(messages, name, lang);
}
    }


/*
    /*
* Substitute arguments into the string, where arguments are represented
    * Substitute arguments into the string, where arguments are represented
* as $n where n > 0.
    * as $n where n > 0.
*
    *
* @param message The message to substitute arguments into
    * @param message The message to substitute arguments into
* @param arguments The arguments to substitute in.
    * @param arguments The arguments to substitute in.
*
    *
* @return The resulting message.
    * @return The resulting message.
*/
    */
function handleArgs(message, args) {
    function handleArgs(message, args) {
args.forEach(function (elem, index) {
        args.forEach(function (elem, index) {
var rgx = new RegExp('\\$' + (index + 1), 'g');
            var rgx = new RegExp('\\$' + (index + 1), 'g');
message = message.replace(rgx, elem);
            message = message.replace(rgx, elem);
});
        });


return message;
        return message;
}
    }


/*
    /*
* Generate a HTML link using the supplied parameters.
    * Generate a HTML link using the supplied parameters.
*
    *
* @param href The href of the link which will be converted to
    * @param href The href of the link which will be converted to
*    '/wiki/href'.
    *    '/wiki/href'.
* @param text The text and title of the link. If this is not supplied, it
    * @param text The text and title of the link. If this is not supplied, it
*    will default to href.
    *    will default to href.
*
    *
* @return The generated link.
    * @return The generated link.
*/
    */
function makeLink(href, text) {
    function makeLink(href, text) {
text = text || href;
        text = text || href;
href = href.indexOf('http') === 0 ? href : mw.util.wikiGetlink(href);
        href = href.indexOf('http') === 0 ? href : mw.util.wikiGetlink(href);


text = mw.html.escape(text);
        text = mw.html.escape(text);
href = mw.html.escape(href);
        href = mw.html.escape(href);


return '<a href="' + href + '" title="' + text + '">' + text + '</a>';
        return '<a href="' + href + '" title="' + text + '">' + text + '</a>';
}
    }


/*
    /*
* Parse basic wikitext links into HTML.
    * Parse basic wikitext links into HTML.
*
    *
* Will process:
    * Will process:
* - [url text]
    * - [url text]
* - [[pagename]]
    * - [[pagename]]
* - [[pagename|text]]
    * - [[pagename|text]]
*
    *
* @param message The message to process.
    * @param message The message to process.
*
    *
* @return The resulting string.
    * @return The resulting string.
*/
    */
function parse(message) {
    function parse(message) {
// [url some text here] -> [$1 $2]
            // [url some text here] -> [$1 $2]
var urlRgx = /\[(https?:\/\/.+?) (.+?)\]/g,
        var urlRgx = /\[(https?:\/\/.+?) (.+?)\]/g,
// [[pagename]] -> [[$1]]
            // [[pagename]] -> [[$1]]
simplePageRgx = /\[\[([^|]*?)\]\]/g,
            simplePageRgx = /\[\[([^|]*?)\]\]/g,
// [[pagename|text]] -> [[$1|$2]]
            // [[pagename|text]] -> [[$1|$2]]
pageWithTextRgx = /\[\[(.+?)\|(.+?)\]\]/g;
            pageWithTextRgx = /\[\[(.+?)\|(.+?)\]\]/g;


return message
        return message
.replace(urlRgx, function (_match, href, text) {
            .replace(urlRgx, function (_match, href, text) {
return makeLink(href, text);
                return makeLink(href, text);
})
            })
.replace(simplePageRgx, function (_match, href) {
            .replace(simplePageRgx, function (_match, href) {
return makeLink(href);
                return makeLink(href);
})
            })
.replace(pageWithTextRgx, function (_match, href, text) {
            .replace(pageWithTextRgx, function (_match, href, text) {
return makeLink(href, text);
                return makeLink(href, text);
});
            });
}
    }


/*
    /*
* Create a new Message instance.
    * Create a new Message instance.
*
    *
* @param message The name of the message.
    * @param message The name of the message.
* @param arguments Any arguments to substitute into the message.
    * @param arguments Any arguments to substitute into the message.
*/
    */
function message(messages, defaultLang, args) {
    function message(messages, defaultLang, args) {
if (!args.length) {
        if (!args.length) {
return;
            return;
}
        }


var msg = args.shift();
        var msg = args.shift();
msg = getMsg(messages, msg, defaultLang);
        msg = getMsg(messages, msg, defaultLang);


if (args.length) {
        if (args.length) {
msg = handleArgs(msg, args);
            msg = handleArgs(msg, args);
}
        }


return {
        return {
/*
            /*
* Parse wikitext links in the message and return the result.
            * Parse wikitext links in the message and return the result.
*
            *
* @return The resulting string.
            * @return The resulting string.
*/
            */
parse: function () {
            parse: function () {
return parse(msg);
                return parse(msg);
},
            },


/*
            /*
* Escape any HTML in the message and return the result.
            * Escape any HTML in the message and return the result.
*
            *
* @return The resulting string.
            * @return The resulting string.
*/
            */
escape: function () {
            escape: function () {
return mw.html.escape(msg);
                return mw.html.escape(msg);
},
            },
           
/*
            /*
* Return the message as is.
            * Return the message as is.
*
            *
* @return The resulting string.
            * @return The resulting string.
*/
            */
plain: function () {
            plain: function () {
return msg;
                return msg;
}
            }
};
        };
}
    }


/*
    /*
* Create a new i18n object.
    * Create a new i18n object.
*
    *
* @param messages The message object to look translations up in.
    * @param messages The message object to look translations up in.
*/
    */
function i18n(messages) {
    function i18n(messages) {
var defaultLang = conf.wgUserLanguage;
        var defaultLang = conf.wgUserLanguage;


return {
        return {
/*
            /*
* Set the default language.
            * Set the default language.
*
            *
* @param lang the language code to use by default.
            * @param lang the language code to use by default.
*/
            */
useLang: function (lang) {
            useLang: function (lang) {
defaultLang = lang;
                defaultLang = lang;
},
            },


/*
            /*
* Set the default language to the content language.
            * Set the default language to the content language.
*/
            */
useContentLang: function () {
            useContentLang: function () {
defaultLang = conf.wgContentLanguage;
                defaultLang = conf.wgContentLanguage;
},
            },


/*
            /*
* Set the default language to the user's language.
            * Set the default language to the user's language.
*/
            */
useUserLang: function () {
            useUserLang: function () {
defaultLang = conf.wgUserLanguage;
                defaultLang = conf.wgUserLanguage;
},
            },


/*
            /*
* Create a new instance of Message.
            * Create a new instance of Message.
*/
            */
msg: function () {
            msg: function () {
var args = Array.prototype.slice.call(arguments);
                var args = Array.prototype.slice.call(arguments);
return message(messages, defaultLang, args);
                return message(messages, defaultLang, args);
},
            },


/*
            /*
* For accessing the raw messages.
            * For accessing the raw messages.
*/
            */
_messages: messages
            _messages: messages
};
        };
}
    }


/*
    /*
* Strip comments from a JSON string which are illegal under the JSON spec.
    * Strip comments from a JSON string which are illegal under the JSON spec.
*
    *
* @param json The JSON string.
    * @param json The JSON string.
*
    *
* @return The JSON string after any comments have been removed.
    * @return The JSON string after any comments have been removed.
*/
    */
function stripComments(json) {
    function stripComments(json) {
return json
        return json
// inline comments
            // inline comments
.replace(/\/\/.*?(\n|$)/g, '$1')
            .replace(/\/\/.*?(\n|$)/g, '$1')
// block comments
            // block comments
// this is a bit basic, so will break on comments inside strings
            // this is a bit basic, so will break on comments inside strings
.replace(/\/\*[\s\S]*?\*\//g, '');
            .replace(/\/\*[\s\S]*?\*\//g, '');
}
    }


/*
    /*
* Load a messages stored in as JSON on a page.
    * Load a messages stored in as JSON on a page.
*
    *
* @param name The name of the script the messages are for. This will be
    * @param name The name of the script the messages are for. This will be
*    used to get messages from
    *    used to get messages from
*    http://dev.wikia.com/wiki/MediaWiki:Custom-name/i18n.json.
    *    http://dev.wikia.com/wiki/MediaWiki:Custom-name/i18n.json.
*
    *
* @return A jQuery.Deferred instance.
    * @return A jQuery.Deferred instance.
*/
    */
function loadMessages(name) {
    function loadMessages(name) {
var deferred = $.Deferred(),
        var deferred = $.Deferred(),
params;
            params;


if (cache[name]) {
        if (cache[name]) {
deferred.resolve(cache[name]);
            deferred.resolve(cache[name]);
} else {
        } else {
params = {
            params = {
mode: 'articles',
                mode: 'articles',
articles: 'u:dev:MediaWiki:Custom-' + name + '/i18n.json',
                articles: 'u:dev:MediaWiki:Custom-' + name + '/i18n.json',
only: 'styles',
                only: 'styles',
debug: '1'
                debug: '1'
};
            };


mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () {
            mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () {
$.get(mw.util.wikiScript('load'), params).done(function (res) {
                $.get(mw.util.wikiScript('load'), params).done(function (res) {
var json,
                    var json,
obj;
                        obj;


// handle parse errors gracefully
                    // handle parse errors gracefully
try {
                    try {
json = JSON.parse(stripComments(res))
                        json = JSON.parse(stripComments(res));
} catch (e) {
                    } catch (e) {
console.log('[I18n-js] SyntaxError in messages: ' + e.message);
                        console.log('[I18n-js] SyntaxError in messages: ' + e.message);
json = {};
                        json = {};
}
                    }


obj = i18n(json);
                    obj = i18n(json);


// cache the result in case it's used multiple times
                    // cache the result in case it's used multiple times
cache[name] = obj;
                    cache[name] = obj;
deferred.resolve(obj);
                    deferred.resolve(obj);
});
                });
});
            });
}
        }


return deferred;
        return deferred;
}
    }


// expose under the dev global
    // expose under the dev global
window.dev.i18n = {
    window.dev.i18n = {
loadMessages: loadMessages,
        loadMessages: loadMessages,


// 'hidden' functions to allow testing
        // 'hidden' functions to allow testing
_stripComments: stripComments,
        _stripComments: stripComments,
_getMsg: getMsg,
        _getMsg: getMsg,
_handleArgs: handleArgs,
        _handleArgs: handleArgs,
_parse: parse,
        _parse: parse,
_fallbacks: fallbacks
        _fallbacks: fallbacks
};
    };


// fire an event on load
    // fire an event on load
mw.hook('dev.i18n').fire(window.dev.i18n);
    mw.hook('dev.i18n').fire(window.dev.i18n);
// alternatively, use $.getScript (or mw.loader)
    // alternatively, use $.getScript (or mw.loader)
// and use the returned promise
    // and use the returned promise


}(this, jQuery, mediaWiki));
}(this, jQuery, mediaWiki));

Revision as of 17:53, 3 August 2017

/* <nowiki>
 * Library for accessing i18n messages for use in Dev Wiki scripts.
 * See [[I18n-js]] for documentation.
 *
 * @author Cqm <http://dev.wikia.com/User:Cqm>
 * @version 0.3.2
 *
 * @todo improve error reporting for empty/invalid/misspelt message pages
 */

/*global mediaWiki */

/*jshint bitwise:true, camelcase:true, curly:true, eqeqeq:true, es3:false,
    forin:true, immed:true, indent:4, latedef:true, newcap:true,
    noarg:true, noempty:true, nonew:true, plusplus:true, quotmark:single,
    undef:true, unused:true, strict:true, trailing:true,
    browser:true, devel:false, jquery:true,
    onevar:true
*/

;(function (window, $, mw, undefined) {
    'use strict';

    window.dev = window.dev || {};

    // prevent double loading and loss of cache
    if (window.dev.i18n !== undefined) {
        return;
    }

        /*
         * Cache of mw config variables.
         */
    var conf = mw.config.get([
            'wgContentLanguage',
            'wgUserLanguage'
        ]),

        /*
         * Cache of loaded I18n instances.
         */
        cache = {},

        /*
         * Language fallbacks for those that don't fallback to English.
         * Shouldn't need updating unless Wikia change theirs.
         *
         * To generate this, use `$ grep -R "fallback =" /path/to/messages/`,
         * pipe the result to a text file and format the result.
         */
        fallbacks = {
            'ab': 'ru',
            'ace': 'id',
            'aln': 'sq',
            'als': 'gsw',
            'an': 'es',
            'anp': 'hi',
            'arn': 'es',
            'arz': 'ar',
            'av': 'ru',
            'ay': 'es',
            'ba': 'ru',
            'bar': 'de',
            'bat-smg': 'sgs',
            'bcc': 'fa',
            'be-x-old': 'be-tarask',
            'bh': 'bho',
            'bjn': 'id',
            'bm': 'fr',
            'bpy': 'bn',
            'bqi': 'fa',
            'bug': 'id',
            'cbk-zam': 'es',
            'ce': 'ru',
            'ckb': 'ckb-arab',
            'crh': 'crh-latn',
            'crh-cyrl': 'ru',
            'csb': 'pl',
            'cv': 'ru',
            'de-at': 'de',
            'de-ch': 'de',
            'de-formal': 'de',
            'dsb': 'de',
            'dtp': 'ms',
            'eml': 'it',
            'ff': 'fr',
            'fiu-vro': 'vro',
            'frc': 'fr',
            'frp': 'fr',
            'frr': 'de',
            'fur': 'it',
            'gag': 'tr',
            'gan': 'gan-hant',
            'gan-hans': 'zh-hans',
            'gan-hant': 'zh-hant',
            'gl': 'pt',
            'glk': 'fa',
            'gn': 'es',
            'gsw': 'de',
            'hif': 'hif-latn',
            'hsb': 'de',
            'ht': 'fr',
            'ii': 'zh-cn',
            'inh': 'ru',
            'iu': 'ike-cans',
            'jut': 'da',
            'jv': 'id',
            'kaa': 'kk-latn',
            'kbd': 'kbd-cyrl',
            'kbd-cyrl': 'ru',
            'khw': 'ur',
            'kiu': 'tr',
            'kk': 'kk-cyrl',
            'kk-arab': 'kk-cyrl',
            'kk-cn': 'kk-arab',
            'kk-kz': 'kk-cyrl',
            'kk-latn': 'kk-cyrl',
            'kk-tr': 'kk-latn',
            'kl': 'da',
            'koi': 'ru',
            'ko-kp': 'ko',
            'krc': 'ru',
            'ks': 'ks-arab',
            'ksh': 'de',
            'ku': 'ku-latn',
            'ku-arab': 'ckb',
            'kv': 'ru',
            'lad': 'es',
            'lb': 'de',
            'lbe': 'ru',
            'lez': 'ru',
            'li': 'nl',
            'lij': 'it',
            'liv': 'et',
            'lmo': 'it',
            'ln': 'fr',
            'ltg': 'lv',
            'lzz': 'tr',
            'mai': 'hi',
            'map-bms': 'jv',
            'mg': 'fr',
            'mhr': 'ru',
            'min': 'id',
            'mo': 'ro',
            'mrj': 'ru',
            'mwl': 'pt',
            'myv': 'ru',
            'mzn': 'fa',
            'nah': 'es',
            'nap': 'it',
            'nds': 'de',
            'nds-nl': 'nl',
            'nl-informal': 'nl',
            'no': 'nb',
            'os': 'ru',
            'pcd': 'fr',
            'pdc': 'de',
            'pdt': 'de',
            'pfl': 'de',
            'pms': 'it',
            // 'pt': 'pt-br',
            'pt-br': 'pt',
            'qu': 'es',
            'qug': 'qu',
            'rgn': 'it',
            'rmy': 'ro',
            'rue': 'uk',
            'ruq': 'ruq-latn',
            'ruq-cyrl': 'mk',
            'ruq-latn': 'ro',
            'sa': 'hi',
            'sah': 'ru',
            'scn': 'it',
            'sg': 'fr',
            'sgs': 'lt',
            'shi': 'ar',
            'simple': 'en',
            'sli': 'de',
            'sr': 'sr-ec',
            'srn': 'nl',
            'stq': 'de',
            'su': 'id',
            'szl': 'pl',
            'tcy': 'kn',
            'tg': 'tg-cyrl',
            'tt': 'tt-cyrl',
            'tt-cyrl': 'ru',
            'ty': 'fr',
            'udm': 'ru',
            'ug': 'ug-arab',
            'uk': 'ru',
            'vec': 'it',
            'vep': 'et',
            'vls': 'nl',
            'vmf': 'de',
            'vot': 'fi',
            'vro': 'et',
            'wa': 'fr',
            'wo': 'fr',
            'wuu': 'zh-hans',
            'xal': 'ru',
            'xmf': 'ka',
            'yi': 'he',
            'za': 'zh-hans',
            'zea': 'nl',
            'zh': 'zh-hans',
            'zh-classical': 'lzh',
            'zh-cn': 'zh-hans',
            'zh-hant': 'zh-hans',
            'zh-hk': 'zh-hant',
            'zh-min-nan': 'nan',
            'zh-mo':  'zh-hk',
            'zh-my':  'zh-sg',
            'zh-sg':  'zh-hans',
            'zh-tw':  'zh-hant',
            'zh-yue': 'yue'
        };

    /*
     * Get a translation of a message from the messages object in the
     * requested language.
     *
     * @param messages the message object to look the message up in.
     * @param name The name of the message to get.
     * @param lang The language to get the message in.
     *
     * @return The requested translation or the name wrapped in < ... > if no
     *     message could be found.
     */
    function getMsg(messages, name, lang) {
        if (messages[lang] && messages[lang][name]) {
            return messages[lang][name];
        }

        if (lang === 'en') {
            return '<' + name + '>';
        }

        lang = fallbacks[lang] || 'en';
        return getMsg(messages, name, lang);
    }

    /*
     * Substitute arguments into the string, where arguments are represented
     * as $n where n > 0.
     *
     * @param message The message to substitute arguments into
     * @param arguments The arguments to substitute in.
     *
     * @return The resulting message.
     */
    function handleArgs(message, args) {
        args.forEach(function (elem, index) {
            var rgx = new RegExp('\\$' + (index + 1), 'g');
            message = message.replace(rgx, elem);
        });

        return message;
    }

    /*
     * Generate a HTML link using the supplied parameters.
     *
     * @param href The href of the link which will be converted to
     *     '/wiki/href'.
     * @param text The text and title of the link. If this is not supplied, it
     *     will default to href.
     *
     * @return The generated link.
     */
    function makeLink(href, text) {
        text = text || href;
        href = href.indexOf('http') === 0 ? href : mw.util.wikiGetlink(href);

        text = mw.html.escape(text);
        href = mw.html.escape(href);

        return '<a href="' + href + '" title="' + text + '">' + text + '</a>';
    }

    /*
     * Parse basic wikitext links into HTML.
     *
     * Will process:
     * - [url text]
     * - [[pagename]]
     * - [[pagename|text]]
     *
     * @param message The message to process.
     *
     * @return The resulting string.
     */
    function parse(message) {
            // [url some text here] -> [$1 $2]
        var urlRgx = /\[(https?:\/\/.+?) (.+?)\]/g,
            // [[pagename]] -> [[$1]]
            simplePageRgx = /\[\[([^|]*?)\]\]/g,
            // [[pagename|text]] -> [[$1|$2]]
            pageWithTextRgx = /\[\[(.+?)\|(.+?)\]\]/g;

        return message
            .replace(urlRgx, function (_match, href, text) {
                return makeLink(href, text);
            })
            .replace(simplePageRgx, function (_match, href) {
                return makeLink(href);
            })
            .replace(pageWithTextRgx, function (_match, href, text) {
                return makeLink(href, text);
            });
    }

    /*
     * Create a new Message instance.
     *
     * @param message The name of the message.
     * @param arguments Any arguments to substitute into the message.
     */
    function message(messages, defaultLang, args) {
        if (!args.length) {
            return;
        }

        var msg = args.shift();
        msg = getMsg(messages, msg, defaultLang);

        if (args.length) {
            msg = handleArgs(msg, args);
        }

        return {
            /*
             * Parse wikitext links in the message and return the result.
             *
             * @return The resulting string.
             */
            parse: function () {
                return parse(msg);
            },

            /*
             * Escape any HTML in the message and return the result.
             *
             * @return The resulting string.
             */
            escape: function () {
                return mw.html.escape(msg);
            },
            
            /*
             * Return the message as is.
             *
             * @return The resulting string.
             */
            plain: function () {
                return msg;
            }
        };
    }

    /*
     * Create a new i18n object.
     *
     * @param messages The message object to look translations up in.
     */
    function i18n(messages) {
        var defaultLang = conf.wgUserLanguage;

        return {
            /*
             * Set the default language.
             *
             * @param lang the language code to use by default.
             */
            useLang: function (lang) {
                defaultLang = lang;
            },

            /*
             * Set the default language to the content language.
             */
            useContentLang: function () {
                defaultLang = conf.wgContentLanguage;
            },

            /*
             * Set the default language to the user's language.
             */
            useUserLang: function () {
                defaultLang = conf.wgUserLanguage;
            },

            /*
             * Create a new instance of Message.
             */
            msg: function () {
                var args = Array.prototype.slice.call(arguments);
                return message(messages, defaultLang, args);
            },

            /*
             * For accessing the raw messages.
             */
            _messages: messages
        };
    }

    /*
     * Strip comments from a JSON string which are illegal under the JSON spec.
     *
     * @param json The JSON string.
     *
     * @return The JSON string after any comments have been removed.
     */
    function stripComments(json) {
        return json
            // inline comments
            .replace(/\/\/.*?(\n|$)/g, '$1')
            // block comments
            // this is a bit basic, so will break on comments inside strings
            .replace(/\/\*[\s\S]*?\*\//g, '');
    }

    /*
     * Load a messages stored in as JSON on a page.
     *
     * @param name The name of the script the messages are for. This will be
     *     used to get messages from
     *     http://dev.wikia.com/wiki/MediaWiki:Custom-name/i18n.json.
     *
     * @return A jQuery.Deferred instance.
     */
    function loadMessages(name) {
        var deferred = $.Deferred(),
            params;

        if (cache[name]) {
            deferred.resolve(cache[name]);
        } else {
            params = {
                mode: 'articles',
                articles: 'u:dev:MediaWiki:Custom-' + name + '/i18n.json',
                only: 'styles',
                debug: '1'
            };

            mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () {
                $.get(mw.util.wikiScript('load'), params).done(function (res) {
                    var json,
                        obj;

                    // handle parse errors gracefully
                    try {
                        json = JSON.parse(stripComments(res));
                    } catch (e) {
                        console.log('[I18n-js] SyntaxError in messages: ' + e.message);
                        json = {};
                    }

                    obj = i18n(json);

                    // cache the result in case it's used multiple times
                    cache[name] = obj;
                    deferred.resolve(obj);
                });
            });
        }

        return deferred;
    }

    // expose under the dev global
    window.dev.i18n = {
        loadMessages: loadMessages,

        // 'hidden' functions to allow testing
        _stripComments: stripComments,
        _getMsg: getMsg,
        _handleArgs: handleArgs,
        _parse: parse,
        _fallbacks: fallbacks
    };

    // fire an event on load
    mw.hook('dev.i18n').fire(window.dev.i18n);
    // alternatively, use $.getScript (or mw.loader)
    // and use the returned promise

}(this, jQuery, mediaWiki));