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

From Tardis Wiki, the free Doctor Who reference
m
reverting to cqm's edit due to edits made after breaking script (fallbacks not working correctly)
m (Undo revision 185643 by Sophiedp (talk) | undoing to leave summary for reason)
m (reverting to cqm's edit due to edits made after breaking script (fallbacks not working correctly))
Line 6: Line 6:
  * @author OneTwoThreeFall <https://dev.fandom.com/User:OneTwoThreeFall>
  * @author OneTwoThreeFall <https://dev.fandom.com/User:OneTwoThreeFall>
  *
  *
  * @version 0.6.7
  * @version 0.6.6
  *
  *
  * @notes Also used by SOAP Wiki for their reporting forms (with a non-dev i18n.json page)
  * @notes Also used by VSTF wiki for their reporting forms (with a non-dev i18n.json page)
  * @notes This is apparently a commonly used library for a number of scripts and also
  * @notes This is apparently a commonly used library for a number of scripts and also includes
  *   includes a check to prevent double loading. This can make it painful to test from your
  *       a check to prevent double loading. This can make it painful to test from your JS
  *   JS console. To get around this, add ?usesitejs=0&useuserjs=0 to your URL.
  *       console. To get around this, add ?usesitejs=0&useuserjs=0 to your URL.
  */
  */


/* global mediaWiki */
/*global mediaWiki */


/* 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) {
Line 30: Line 30:
     window.dev.i18n = window.dev.i18n || {};
     window.dev.i18n = window.dev.i18n || {};


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


    /*
        /*
    * Cache of mw config variables.
        * Cache of mw config variables.
    *
        */
    * @var {object} conf Cache of mw config variables:
    * - {boolean} debug
    * - {string} wgContentLanguage Site language
    *    Be careful to use this:
    *    - In languages with variants, this will block the language conversion;
    *      see <https://www.mediawiki.org/wiki/Writing_systems>.
    *    - In multilingual wikis like "Feed The Beast", this will block both the
    *      multilingual content providing and language conversion.
    * - {string} wgPageContentLanguage Page Language or Content Modal Language
    *    or Site Language or 'en'
    *    Be careful to use this:
    *    - In Special: pages, this will be the user language.
    *    - In Module: pages, this will be the content modal language 'en'.
    * - {string} wgUserLanguage
    * - {(string|null)} wgUserVariant The language variant user currently using,
    *    'null' when the page lannguage doesn't have language variants.
    */
     var conf = mw.config.get([
     var conf = mw.config.get([
        'debug',
            'debug',
        'wgContentLanguage',
            'wgContentLanguage',
        'wgPageContentLanguage',
            'wgUserLanguage'
        'wgUserLanguage',
         ]),
         'wgUserVariant'
  ]),


         /*
         /*
         * @var {number} Current time in milliseconds, used to set and check cache age.
         * Current time in milliseconds, used to set and check cache age.
         */
         */
         now = Date.now(),
         now = Date.now(),


         /*
         /*
         * @var {number} Length of one day in milliseconds, used in cache age calculations.
         * Length of one day in milliseconds, used in cache age calculations.
         */
         */
         oneDay = 1000 * 60 * 60 * 24,
         oneDay = 1000 * 60 * 60 * 24,


         /*
         /*
         * @var {string} Prefix used for localStorage keys that contain i18n-js cache data
         * Prefix used for localStorage keys that contain i18n-js cache data.
         */
         */
         cachePrefix = 'i18n-cache-',
         cachePrefix = 'i18n-cache-',


         /*
         /*
         * @var {boolean} Whether a fallback loop warning been shown
         * Has a fallback loop warning been shown?
         */
         */
         warnedAboutFallbackLoop = false,
         warnedAboutFallbackLoop = false,


         /*
         /*
         * @var {object} Cache of loaded I18n instances.
         * Cache of loaded I18n instances.
         */
         */
         cache = {},
         cache = {},
Line 90: Line 71:
         /*
         /*
         * Initial overrides object, initialised below with the i18n global variable.
         * Initial overrides object, initialised below with the i18n global variable.
         * Allows end-users to override specific messages.
         * Allows end-users to override specific messages. See documentation for how to use.
        * See documentation for how to use.
        *
        * @var {(null|object)} overrides
         */
         */
         overrides = null,
         overrides = null,
        /*
        * Mapping of deprecated language codes that were used in previous
        * versions of MediaWiki to up-to-date, current language codes.
        *
        * These codes shouldn't be used to store translations unless there are
        * language changes to /includes/language/LanguageCode.php in mediawiki/core.
        *
        * These may or may not be valid BCP 47 codes; they are included here
        * because MediaWiki renamed these particular codes at some point.
        *
        * Note that 'als' is actually a valid ISO 639 code (Tosk Albanian), but it
        * was previously used in MediaWiki for Alsatian, which comes under 'gsw'.
        *
        * @var {object.<string, string>} Mapping from deprecated MediaWiki-internal
        *  language code to replacement MediaWiki-internal language code.
        *
        * @see /includes/language/LanguageCode.php in MediaWiki core
        * @see https://meta.wikimedia.org/wiki/Special_language_codes
        */
        deprecatedCodes = {
            'als': 'gsw', // T25215
            'bat-smg': 'sgs', // T27522
            'be-x-old': 'be-tarask', // T11823
            'fiu-vro': 'vro', // T31186
            'roa-rup': 'rup', // T17988
            'zh-classical': 'lzh', // T30443
            'zh-min-nan': 'nan', // T30442
            'zh-yue': 'yue' // T30441
        },
        /**
        * Mapping of non-standard language codes used in MediaWiki to
        * standardized BCP 47 codes.
        *
        * @var {object.<string, string>} Mapping from nonstandard
        *  MediaWiki-internal codes to BCP 47 codes
        *
        * @see /includes/language/LanguageCode.php in MediaWiki core
        * @see https://meta.wikimedia.org/wiki/Special_language_codes
        * @see https://phabricator.wikimedia.org/T125073
        */
        nonStandardCodes = {
            'cbk-zam': 'cbk', // T124657
            'de-formal': 'de-x-formal',
            'eml': 'egl', // T36217
            'en-rtl': 'en-x-rtl',
            'es-formal': 'es-x-formal',
            'hu-formal': 'hu-x-formal',
            'kk-cn': 'kk-Arab-CN',
            'kk-kz': 'kk-Cyrl-KZ',
            'kk-tr': 'kk-Latn-TR',
            'map-bms': 'jv-x-bms', // [[wikipedia:en:Banyumasan_dialect]] T125073
            'mo': 'ro-Cyrl-MD', // T125073
            'nrm': 'nrf', // [[wikipedia:en:Norman_language]] T25216
            'nl-informal': 'nl-x-informal',
            'roa-tara': 'nap-x-tara', // [[wikipedia:en:Tarantino_dialect]]
            'simple': 'en-x-simple',
            'sr-ec': 'sr-Cyrl', // T117845
            'sr-el': 'sr-Latn', // T117845
            'zh-cn': 'zh-Hans-CN',
            'zh-sg': 'zh-Hans-SG',
            'zh-my': 'zh-Hans-MY',
            'zh-tw': 'zh-Hant-TW',
            'zh-hk': 'zh-Hant-HK',
            'zh-mo': 'zh-Hant-MO'
        },


         /*
         /*
         * 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 there're language fallback chain changes
        * to /languages/messages files in mediawiki/core.
         *
         *
         * 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.
        *
        * Please note that there's bidirectional/multidirectional fallback in languages,
        * including cdo <=> nan, pt <=> pt-br, zh <=> zh-hans <=> zh-hant
        *
        * @var {object.<string, string[]>} Mapping from language codes to fallback
        * language codes
         */
         */
         fallbacks = {
         fallbacks = {
             'ab': ['ru'],
             'ab': 'ru',
             'abs': ['id'],
             'ace': 'id',
             'ace': ['id'],
             'aln': 'sq',
             'ady': ['ady-cyrl'],
             'als': 'gsw',
             'aeb': ['aeb-arab'],
             'an': 'es',
             'aeb-arab': ['ar'],
             'anp': 'hi',
             'aln': ['sq'],
             'arn': 'es',
             'alt': ['ru'],
             'arz': 'ar',
             'ami': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'],
             'av': 'ru',
             'an': ['es'],
            'ay': 'es',
             'anp': ['hi'],
             'ba': 'ru',
             'arn': ['es'],
             'bar': 'de',
             'arq': ['ar'],
             'bat-smg': 'sgs',
             'ary': ['ar'],
             'bcc': 'fa',
             'arz': ['ar'],
             'be-x-old': 'be-tarask',
             'ast': ['es'],
             'bh': 'bho',
             'atj': ['fr'],
             'bjn': 'id',
             'av': ['ru'],
             'bm': 'fr',
             'avk': ['fr', 'es', 'ru'],
             'bpy': 'bn',
             'awa': ['hi'],
             'bqi': 'fa',
             'ay': ['es'],
            'bug': 'id',
             'azb': ['fa'],
             'cbk-zam': 'es',
             'ba': ['ru'],
             'ce': 'ru',
             'ban': ['id'],
             'ckb': 'ckb-arab',
             'ban-bali': ['ban'],
             'crh': 'crh-latn',
             'bar': ['de'],
             'crh-cyrl': 'ru',
             'bbc': ['bbc-latn'],
             'csb': 'pl',
             'bbc-latn': ['id'],
             'cv': 'ru',
             'bcc': ['fa'],
             'de-at': 'de',
             'bci': ['fr'],
             'de-ch': 'de',
             'be-tarask': ['be'],
             'de-formal': 'de',
             'bgn': ['fa'],
             'dsb': 'de',
             'bh': ['bho'],
             'dtp': 'ms',
             'bi': ['en'],
             'eml': 'it',
             'bjn': ['id'],
             'ff': 'fr',
             'blk': ['my'],
             'fiu-vro': 'vro',
             'bm': ['fr'],
             'frc': 'fr',
             'bpy': ['bn'],
             'frp': 'fr',
             'bqi': ['fa'],
             'frr': 'de',
             'br': ['fr'],
             'fur': 'it',
             'btm': ['id'],
             'gag': 'tr',
             'bug': ['id'],
             'gan': 'gan-hant',
             'bxr': ['ru'],
             'gan-hans': 'zh-hans',
             'ca': ['oc'],
             'gan-hant': 'zh-hant',
             'cbk-zam': ['es'],
             'gl': 'pt',
             'cdo': ['nan', 'zh-hant', 'zh', 'zh-hans'],
             'glk': 'fa',
             'ce': ['ru'],
             'gn': 'es',
             'co': ['it'],
             'gsw': 'de',
             'crh': ['crh-latn'],
            'hif': 'hif-latn',
             'crh-cyrl': ['ru'],
             'hsb': 'de',
             'cs': ['sk'],
             'ht': 'fr',
             'csb': ['pl'],
             'ii': 'zh-cn',
             'cv': ['ru'],
             'inh': 'ru',
             'de-at': ['de'],
             'iu': 'ike-cans',
             'de-ch': ['de'],
             'jut': 'da',
             'de-formal': ['de'],
             'jv': 'id',
             'dsb': ['hsb', 'de'],
             'kaa': 'kk-latn',
             'dtp': ['ms'],
             'kbd': 'kbd-cyrl',
             'dty': ['ne'],
             'kbd-cyrl': 'ru',
             'egl': ['it'],
             'khw': 'ur',
             'eml': ['it'],
             'kiu': 'tr',
             'es-formal': ['es'],
             'kk': 'kk-cyrl',
             'ext': ['es'],
             'kk-arab': 'kk-cyrl',
             'ff': ['fr'],
             'kk-cn': 'kk-arab',
             'fit': ['fi'],
             'kk-kz': 'kk-cyrl',
             'fon': ['fr'],
             'kk-latn': 'kk-cyrl',
             'frc': ['fr'],
             'kk-tr': 'kk-latn',
             'frp': ['fr'],
             'kl': 'da',
             'frr': ['de'],
             'koi': 'ru',
             'fur': ['it'],
             'ko-kp': 'ko',
             'gag': ['tr'],
             'krc': 'ru',
             'gan': ['gan-hant', 'gan-hans', 'zh-hant', 'zh', 'zh-hans'],
             'ks': 'ks-arab',
             'gan-hans': ['gan', 'gan-hant', 'zh-hans', 'zh', 'zh-hant'],
             'ksh': 'de',
             'gan-hant': ['gan', 'gan-hans', 'zh-hant', 'zh', 'zh-hans'],
             'ku': 'ku-latn',
             'gcr': ['fr'],
             'ku-arab': 'ckb',
             'gl': ['pt'],
            'kv': 'ru',
             'gld': ['ru'],
            'lad': 'es',
             'glk': ['fa'],
             'lb': 'de',
             'gn': ['es'],
            'lbe': 'ru',
             'gom': ['gom-deva'],
            'lez': 'ru',
             'gom-deva': ['hi'],
             'li': 'nl',
             'gor': ['id'],
            'lij': 'it',
             'gsw': ['de'],
            'liv': 'et',
             'guc': ['es'],
             'lmo': 'it',
             'hak': ['zh-hant', 'zh', 'zh-hans'],
             'ln': 'fr',
             'hif': ['hif-latn'],
             'ltg': 'lv',
             'hrx': ['de'],
             'lzz': 'tr',
             'hsb': ['dsb', 'de'],
             'mai': 'hi',
             'hsn': ['zh-cn', 'zh-hans', 'zh', 'zh-hant'],
             'map-bms': 'jv',
             'ht': ['fr'],
             'mg': 'fr',
             'hu-formal': ['hu'],
             'mhr': 'ru',
             'hyw': ['hy'],
             'min': 'id',
             'ii': ['zh-cn', 'zh-hans', 'zh', 'zh-hant'],
             'mo': 'ro',
             'inh': ['ru'],
             'mrj': 'ru',
             'io': ['eo'],
            'mwl': 'pt',
             'iu': ['ike-cans'],
             'myv': 'ru',
             'jam': ['en'],
             'mzn': 'fa',
             'jut': ['da'],
             'nah': 'es',
             'jv': ['id'],
            'nap': 'it',
             'kaa': ['kk-latn', 'kk-cyrl'],
             'nds': 'de',
             'kab': ['fr'],
            'nds-nl': 'nl',
             'kbd': ['kbd-cyrl'],
            'nl-informal': 'nl',
             'kbp': ['fr'],
             'no': 'nb',
             'kea': ['pt'],
             'os': 'ru',
             'khw': ['ur'],
             'pcd': 'fr',
             'kiu': ['tr'],
             'pdc': 'de',
             'kjp': ['my'],
            'pdt': 'de',
             'kk': ['kk-cyrl'],
             'pfl': 'de',
             'kk-arab': ['kk-cyrl'],
             'pms': 'it',
             'kk-cn': ['kk-arab', 'kk-cyrl'],
             // 'pt': 'pt-br',
             'kk-kz': ['kk-cyrl'],
             'pt-br': 'pt',
             'kk-latn': ['kk-cyrl'],
             'qu': 'es',
             'kk-tr': ['kk-latn', 'kk-cyrl'],
             'qug': 'qu',
             'kl': ['da'],
             'rgn': 'it',
             'koi': ['ru'],
             'rmy': 'ro',
             'ko-kp': ['ko'],
             'rue': 'uk',
             'krc': ['ru'],
             'ruq': 'ruq-latn',
             'krl': ['fi'],
             'ruq-cyrl': 'mk',
             'ks': ['ks-arab'],
             'ruq-latn': 'ro',
             'ksh': ['de'],
             'sa': 'hi',
             'ksw': ['my'],
             'sah': 'ru',
             'ku': ['ku-latn'],
             'scn': 'it',
             'kum': ['ru'],
             'sg': 'fr',
             'ku-arab': ['ckb'],
             'sgs': 'lt',
             'kv': ['ru'],
            'shi': 'ar',
             'lad': ['es'],
             'simple': 'en',
             'lb': ['de'],
             'sli': 'de',
             'lbe': ['ru'],
             'sr': 'sr-ec',
             'lez': ['ru', 'az'],
             'srn': 'nl',
             'li': ['nl'],
             'stq': 'de',
             'lij': ['it'],
             'su': 'id',
             'liv': ['et'],
             'szl': 'pl',
             'lki': ['fa'],
             'tcy': 'kn',
             'lld': ['it', 'rm', 'fur'],
             'tg': 'tg-cyrl',
             'lmo': ['pms', 'eml', 'lij', 'vec', 'it'],
             'tt': 'tt-cyrl',
             'ln': ['fr'],
             'tt-cyrl': 'ru',
             'lrc': ['fa'],
             'ty': 'fr',
             'ltg': ['lv'],
             'udm': 'ru',
             'luz': ['fa'],
             'ug': 'ug-arab',
             'lzh': ['zh-hant', 'zh', 'zh-hans'],
             'uk': 'ru',
             'lzz': ['tr'],
             'vec': 'it',
             'mad': ['id'],
             'vep': 'et',
             'mai': ['hi'],
             'vls': 'nl',
             'map-bms': ['jv', 'id'],
             'vmf': 'de',
             'mdf': ['myv', 'ru'],
            'vot': 'fi',
            'mg': ['fr'],
             'vro': 'et',
            'mhr': ['mrj', 'ru'],
             'wa': 'fr',
            'min': ['id'],
             'wo': 'fr',
            'mnw': ['my'],
             'wuu': 'zh-hans',
            'mo': ['ro'],
             'xal': 'ru',
            'mrj': ['mhr', 'ru'],
            'xmf': 'ka',
            'ms-arab': ['ms'],
             'yi': 'he',
            'mwl': ['pt'],
            'za': 'zh-hans',
            'myv': ['mdf', 'ru'],
            'zea': 'nl',
            'mzn': ['fa'],
             'zh': 'zh-hans',
            'nah': ['es'],
             'zh-classical': 'lzh',
            'nan': ['cdo', 'zh-hant', 'zh', 'zh-hans'],
             'zh-cn': 'zh-hans',
            'nap': ['it'],
             'zh-hant': 'zh-hans',
            'nb': ['nn'],
             'zh-hk': 'zh-hant',
            'nds': ['de'],
            'zh-min-nan': 'nan',
            'nds-nl': ['nl'],
             'zh-mo': 'zh-hk',
            'nia': ['id'],
             'zh-my': 'zh-sg',
            'nl-informal': ['nl'],
             'zh-sg': 'zh-hans',
            'nn': ['nb'],
             'zh-tw': 'zh-hant',
            'nrm': ['nrf', 'fr'],
             'zh-yue': 'yue'
            'oc': ['ca', 'fr'],
            'olo': ['fi'],
            'os': ['ru'],
            'pcd': ['fr'],
            'pdc': ['de'],
            'pdt': ['de'],
            'pfl': ['de'],
            'pih': ['en'],
            'pms': ['it'],
            'pnt': ['el'],
            'pt': ['pt-br'],
            'pt-br': ['pt'],
            'pwn': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'],
            'qu': ['qug', 'es'],
            'qug': ['qu', 'es'],
            'rgn': ['it'],
            'rmy': ['ro'],
            'roa-tara': ['it'],
            'rsk': ['sr-ec'],
            'rue': ['uk', 'ru'],
            'rup': ['ro'],
            'ruq': ['ruq-latn', 'ro'],
            'ruq-cyrl': ['mk'],
            'ruq-latn': ['ro'],
            'sa': ['hi'],
            'sah': ['ru'],
            'scn': ['it'],
            'sco': ['en'],
            'sdc': ['it'],
            'sdh': ['cbk', 'fa'],
            'se': ['nb', 'fi'],
            'ses': ['fr'],
            'se-fi': ['se', 'fi', 'sv'],
            'se-no': ['se', 'nb', 'nn'],
            'se-se': ['se', 'sv'],
            'sg': ['fr'],
            'sgs': ['lt'],
            'sh': ['bs', 'sr-el', 'hr'],
            'shi': ['fr'],
            'shy': ['shy-latn'],
            'shy-latn': ['fr'],
            'sjd': ['ru'],
            'sk': ['cs'],
            'skr': ['skr-arab'],
            'skr-arab': ['ur', 'pnb'],
            'sli': ['de'],
            'smn': ['fi'],
            'sr': ['sr-ec'],
            'srn': ['nl'],
            'stq': ['de'],
            'sty': ['ru'],
            'su': ['id'],
            'szl': ['pl'],
            'szy': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'],
            'tay': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'],
            'tcy': ['kn'],
            'tet': ['pt'],
            'tg': ['tg-cyrl'],
            'trv': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'],
            'tt': ['tt-cyrl', 'ru'],
            'tt-cyrl': ['ru'],
            'ty': ['fr'],
            'tyv': ['ru'],
            'udm': ['ru'],
            'ug': ['ug-arab'],
            'vec': ['it'],
            'vep': ['et'],
            'vls': ['nl'],
            'vmf': ['de'],
            'vmw': ['pt'],
            'vot': ['fi'],
            'vro': ['et'],
            'wa': ['fr'],
            'wls': ['fr'],
            'wo': ['fr'],
            'wuu': ['zh-hans', 'zh', 'zh-hant'],
            'xal': ['ru'],
            'xmf': ['ka'],
            'yi': ['he'],
            'yue': ['zh-hk', 'zh-hant', 'zh', 'zh-hans'],
            'za': ['zh-hans', 'zh', 'zh-hant'],
            'zea': ['nl'],
            'zgh': ['kab'],
            'zh': ['zh-hans', 'zh-hant', 'zh-cn', 'zh-tw', 'zh-hk'],
            'zh-cn': ['zh-hans', 'zh', 'zh-hant'],
            'zh-hans': ['zh-cn', 'zh', 'zh-hant'],
            'zh-hant': ['zh-tw', 'zh-hk', 'zh', 'zh-hans'],
            'zh-hk': ['zh-hant', 'zh-tw', 'zh', 'zh-hans'],
            'zh-mo': ['zh-hk', 'zh-hant', 'zh-tw', 'zh', 'zh-hans'],
            'zh-my': ['zh-sg', 'zh-hans', 'zh-cn', 'zh', 'zh-hant'],
            'zh-sg': ['zh-hans', 'zh-cn', 'zh', 'zh-hant'],
            'zh-tw': ['zh-hant', 'zh-hk', 'zh', 'zh-hans']
         };
         };
    /*
    * Get the normalised IETF/BCP 47 language tag.
    *
    * mediawiki.language.bcp47 doesn't handle deprecated language codes, and
    * some non-standard language codes are missed from LanguageCode.php, so
    * this function is added to override the behavior.
    *
    * @param {string} lang The language code to convert.
    * @return {string} The language code complying with BCP 47 standards.
    *
    * @see https://gerrit.wikimedia.org/r/c/mediawiki/core/+/376506/
    * @see /resources/src/mediawiki.language/mediawiki.language.js in MediaWiki core
    * @see /includes/language/LanguageCode.php in MediaWiki core
    */
    function bcp47(lang) {
        if (nonStandardCodes[lang]) {
            return nonStandardCodes[lang];
        }
        if (deprecatedCodes[lang]) {
            return bcp47(deprecatedCodes[lang]);
        }
        /*
        * @var {string[]} formatted
        * @var {boolean} isFirstSegment Whether is the first segment
        * @var {boolean} isPrivate Whether the code of the segment is private use
        * @var {string[]} segments The segments of language code
        */
        var formatted,
            isFirstSegment = true,
            isPrivate = false,
            segments = lang.split('-');
        formatted = segments.map(function (segment) {
            /*
            * @var {string} newSegment The converted segment of language code
            */
            var newSegment;
            /* when previous segment is x, it is a private segment and should be lc */
            if (isPrivate) {
                newSegment = segment.toLowerCase();
            /* ISO 3166 country code */
            } else if (segment.length === 2 && !isFirstSegment) {
                newSegment = segment.toUpperCase();
            /* ISO 15924 script code */
            } else if (segment.length === 4 && !isFirstSegment) {
                newSegment = segment.charAt(0).toUpperCase() + segment.substring(1).toLowerCase();
            /* Use lowercase for other cases */
            } else {
                newSegment = segment.toLowerCase();
            }
            isPrivate = segment.toLowerCase() === 'x';
            isFirstSegment = false;
            return newSegment;
        });
        return formatted.join('-');
    }


     /*
     /*
Line 507: Line 254:
     * about to start a loop. Only logs once to prevent flooding the browser console.
     * about to start a loop. Only logs once to prevent flooding the browser console.
     *
     *
     * @param {string} lang Language in use when loop was found.
     * @param lang Language in use when loop was found.
     * @param {string[]} fallbackChain Array of languages involved in the loop.
     * @param fallbackChain Array of languages involved in the loop.
     */
     */
     function warnOnFallbackLoop(lang, fallbackChain) {
     function warnOnFallbackLoop(lang, fallbackChain) {
Line 517: Line 264:


         fallbackChain.push(lang);
         fallbackChain.push(lang);
         console.error('[I18n-js] Duplicated fallback language found. Please leave a message at <https://dev.fandom.com/wiki/Talk:I18n-js> and include the following line: \nLanguage fallback chain:', fallbackChain.join(', '));
         console.error('[I18n-js] Language fallback loop found. Please leave a message at <https://dev.fandom.com/wiki/Talk:I18n-js> and include the following line: \nLanguage fallback chain:', fallbackChain.join(', '));
     }
     }


Line 524: Line 271:
     * requested language.
     * requested language.
     *
     *
     * @param {object} messages The message object to look translations up in.
     * @param messages The message object to look translations up in.
     * @param {string} msgName The name of the message to get.
     * @param msgName The name of the message to get.
     * @param {string} lang The language to get the message in.
     * @param lang The language to get the message in.
     * @param {string[]} fallbackChain Array of languages that have already been checked.
     * @param fallbackChain Array of languages that have already been checked.
     *    Used to detect if the fallback chain is looping.
     *    Used to detect if the fallback chain is looping.
     * @return {(string|boolean)} The requested translation or `false` if no message could be found.
    *
     * @return The requested translation or `false` if no message could be found.
     */
     */
     function getMsg(messages, msgName, lang, fallbackChain) {
     function getMsg(messages, msgName, lang, fallbackChain) {
         if (deprecatedCodes[lang]) {
         if (messages[lang] && messages[lang][msgName]) {
             return getMsg(messages, msgName, deprecatedCodes[lang], fallbackChain);
             return messages[lang][msgName];
         }
         }


         if (messages[lang] && messages[lang][msgName]) {
         if (lang === 'en') {
             return messages[lang][msgName];
             return false;
         }
         }


Line 543: Line 291:
             fallbackChain = [];
             fallbackChain = [];
         }
         }
        fallbackChain.push(lang);


         /*
         lang = fallbacks[lang] || 'en';
        * @var {string} fallbackLang
        */
            for (var i = 0; i < fallbacks[lang].length; i += 1) {
                var fallbackLang = fallbacks[lang][i];
            if (messages[fallbackLang] && messages[fallbackLang][msgName]) {
                return messages[fallbackLang][msgName];
            }


            if (fallbackChain.indexOf(fallbackLang) !== -1) {
        if (fallbackChain.indexOf(lang) !== -1) {
                /*
            // about to enter an infinite loop - switch to English
                * Duplicated language code in fallback list
            warnOnFallbackLoop(lang, fallbackChain);
                * Try to find next fallback language from list
             lang = 'en';
                */
                warnOnFallbackLoop(fallbackLang, fallbackChain);
                continue;
             }
            fallbackChain.push(fallbackLang);
         }
         }


         /* No more languages in fallback list - switch to English */
         return getMsg(messages, msgName, lang, fallbackChain);
        if (messages.en && messages.en[msgName]) {
            return messages.en[msgName];
        }
 
        return false;
     }
     }


Line 576: Line 308:
     * as $n where n > 0.
     * as $n where n > 0.
     *
     *
     * @param {string} message The message to substitute arguments into
     * @param message The message to substitute arguments into
     * @param {array} arguments The arguments to substitute in.
     * @param arguments The arguments to substitute in.
     * @return {string} 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 {RegExp} rgx
            */
             var rgx = new RegExp('\\$' + (index + 1), 'g');
             var rgx = new RegExp('\\$' + (index + 1), 'g');
             message = message.replace(rgx, elem);
             message = message.replace(rgx, elem);
Line 595: Line 325:
     * Generate a HTML link using the supplied parameters.
     * Generate a HTML link using the supplied parameters.
     *
     *
     * @param {string} 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 {string} 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.
     * @param {boolean} hasProtocol True if the href parameter already includes the
     * @param hasProtocol True if the href parameter already includes the
     *    protocol (i.e. it begins with 'http://', 'https://', or '//').
     *    protocol (i.e. it begins with 'http://', 'https://', or '//').
     * @return {string} The generated link.
    *
     * @return The generated link.
     */
     */
     function makeLink(href, text, hasProtocol) {
     function makeLink(href, text, hasProtocol) {
Line 632: Line 363:
     *
     *
     * @param html
     * @param html
    *
     * @return The sanitised HTML code.
     * @return The sanitised HTML code.
     */
     */
     function sanitiseHtml(html) {
     function sanitiseHtml(html) {
        /*
        * @var context
        */
         var context = document.implementation.createHTMLDocument(''),
         var context = document.implementation.createHTMLDocument(''),
             $html = $.parseHTML(html, /* document */ context, /* keepscripts */ false),
             $html = $.parseHTML(html, /* document */ context, /* keepscripts */ false),
Line 645: Line 374:
                 'style',
                 'style',
                 'class'
                 'class'
          ],
            ],
             allowedTags = [
             allowedTags = [
                 'i',
                 'i',
Line 654: Line 383:
                 'strong',
                 'strong',
                 'span',
                 'span',
          ];
            ];


         $div.find('*').each(function () {
         $div.find('*').each(function () {
Line 679: Line 408:
                 }
                 }


                 /* Make sure there's nothing nasty in style attributes */
                 // make sure there's nothing nasty in style attributes
                 if (attr.name === 'style') {
                 if (attr.name === 'style') {
                     style = $this.attr('style');
                     style = $this.attr('style');
Line 687: Line 416:
                         $this.removeAttr('style');
                         $this.removeAttr('style');


                     /* https://phabricator.wikimedia.org/T208881 */
                     // https://phabricator.wikimedia.org/T208881
                     } else if (style.indexOf('var(') > -1) {
                     } else if (style.indexOf('var(') > -1) {
                         mw.log('[I18n-js] Disallowed var() in style attribute');
                         mw.log('[I18n-js] Disallowed var() in style attribute');
Line 709: Line 438:
     * - {{GENDER:gender|masculine|feminine|neutral}}
     * - {{GENDER:gender|masculine|feminine|neutral}}
     *
     *
     * @param {string} message The message to process.
     * @param message The message to process.
     * @return {string} The resulting string.
    *
     * @return The resulting string.
     */
     */
     function parse(message) {
     function parse(message) {
        /* [url text] -> [$1 $2] */
            // [url text] -> [$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,
             /* {{PLURAL:count|singular|plural}} -> {{PLURAL:$1|$2}} */
             // {{PLURAL:count|singular|plural}} -> {{PLURAL:$1|$2}}
             pluralRgx = /\{\{PLURAL:(\d+)\|(.+?)\}\}/gi,
             pluralRgx = /\{\{PLURAL:(\d+)\|(.+?)\}\}/gi,
             /* {{GENDER:gender|masculine|feminine|neutral}} -> {{GENDER:$1|$2}} */
             // {{GENDER:gender|masculine|feminine|neutral}} -> {{GENDER:$1|$2}}
             genderRgx = /\{\{GENDER:([^|]+)\|(.+?)\}\}/gi;
             genderRgx = /\{\{GENDER:([^|]+)\|(.+?)\}\}/gi;


Line 749: Line 479:
     * Create a new Message instance.
     * Create a new Message instance.
     *
     *
     * @param {object} messages The message object to look translations up in.
     * @param messages The message object to look translations up in.
     * @param {string} lang The language to get the message in.
     * @param lang The language to get the message in.
     * @param {array} args Any arguments to substitute into the message, [0] is message name.
     * @param args Any arguments to substitute into the message, [0] is message name.
     * @param {string} name The name of the script the messages are for.
     * @param name The name of the script the messages are for.
    * @return
     */
     */
     function message(messages, lang, args, name) {
     function message(messages, lang, args, name) {
Line 760: Line 489:
         }
         }


        /*
        * @var msgName
        * @var {string} descriptiveMsgName
        * @var {object} msg
        * @var {boolean} msgExists
        */
         var msgName = args.shift(),
         var msgName = args.shift(),
             descriptiveMsgName = 'i18njs-' + name + '-' + msgName,
             descriptiveMsgName = 'i18njs-' + name + '-' + msgName,
Line 772: Line 495:


         if (!msgExists) {
         if (!msgExists) {
             /* use name wrapped in < > for missing message, per MediaWiki convention */
             // use name wrapped in < > for missing message, per MediaWiki convention
             msg = '<' + descriptiveMsgName + '>';
             msg = '<' + descriptiveMsgName + '>';
         }
         }


         if (conf.wgUserLanguage === 'qqx' && msgExists) {
         if (conf.wgUserLanguage === 'qqx' && msgExists) {
             /* https://www.mediawiki.org/wiki/Help:System_message#Finding_messages_and_documentation */
             // https://www.mediawiki.org/wiki/Help:System_message#Finding_messages_and_documentation
             msg = '(' + descriptiveMsgName + ')';
             msg = '(' + descriptiveMsgName + ')';
         } else if (overrides[name] && overrides[name][msgName]) {
         } else if (overrides[name] && overrides[name][msgName]) {
             /* if the message has been overridden, use that without checking the language */
             // if the message has been overridden, use that without checking the language
             msg = overrides[name][msgName];
             msg = overrides[name][msgName];
             msgExists = true;
             msgExists = true;
Line 791: Line 514:
         return {
         return {
             /*
             /*
             * @return {boolean} Representing whether the message exists.
             * Boolean representing whether the message exists.
             */
             */
             exists: msgExists,
             exists: msgExists,
Line 798: Line 521:
             * Parse wikitext links in the message and return the result.
             * Parse wikitext links in the message and return the result.
             *
             *
             * @return {string} The resulting string.
             * @return The resulting string.
             */
             */
             parse: function () {
             parse: function () {
                 /*
                 // skip parsing if the message wasn't found otherwise
                * Skip parsing if the message wasn't found; otherwise
                // the sanitisation will mess with it
                * the sanitisation will mess with it.
                */
                 if (!this.exists) {
                 if (!this.exists) {
                     return this.escape();
                     return this.escape();
Line 815: Line 536:
             * Escape any HTML in the message and return the result.
             * Escape any HTML in the message and return the result.
             *
             *
             * @return {string} The resulting string.
             * @return The resulting string.
             */
             */
             escape: function () {
             escape: function () {
Line 824: Line 545:
             * Return the message as is.
             * Return the message as is.
             *
             *
             * @return {string} The resulting string.
             * @return The resulting string.
             */
             */
             plain: function () {
             plain: function () {
Line 835: Line 556:
     * Create a new i18n object.
     * Create a new i18n object.
     *
     *
     * @param {object} messages The message object to look translations up in.
     * @param messages The message object to look translations up in.
     * @param {string} name The name of the script the messages are for.
     * @param name The name of the script the messages are for.
     * @param {object} options Options set by the loading script.
     * @param options Options set by the loading script.
    * @return {object}
     */
     */
     function i18n(messages, name, options) {
     function i18n(messages, name, options) {
Line 858: Line 578:
             * Set the language for the next msg call.
             * Set the language for the next msg call.
             *
             *
             * @param {string} lang The language code to use for the next `msg` call.
             * @param lang The language code to use for the next `msg` call.
             *
             *
             * @return {object} The current object for use in chaining.
             * @return The current object for use in chaining.
             */
             */
             inLang: function (lang) {
             inLang: function (lang) {
Line 881: Line 601:
             * Set the language for the next `msg` call to the content language.
             * Set the language for the next `msg` call to the content language.
             *
             *
             * @return {object} The current object for use in chaining.
             * @return The current object for use in chaining.
             */
             */
             inContentLang: function () {
             inContentLang: function () {
Line 888: Line 608:
             },
             },


            /*
            * Set the default language to the page language.
            */
            usePageLang: function () {
                defaultLang = conf.wgPageContentLanguage;
            },
            /*
            * Set the language for the next `msg` call to the page language.
            *
            * @return {object} The current object for use in chaining.
            */
            inPageLang: function () {
                tempLang = conf.wgPageContentLanguage;
                return this;
            },
            /*
            * Set the default language to the page view language.
            * This is also known as the user language variant.
            */
            usePageViewLang: function () {
                defaultLang = conf.wgUserVariant || conf.wgContentLanguage;
            },
            /*
            * Set the language for the next `msg` call to the page view language.
            * This is also known as the user language variant.
            *
            * @return {object} The current object for use in chaining.
            */
            inPageViewLang: function () {
                tempLang = conf.wgUserVariant || conf.wgContentLanguage;
                return this;
            },


             /*
             /*
Line 934: Line 619:
             * Set the language for the next msg call to the user's language.
             * Set the language for the next msg call to the user's language.
             *
             *
             * @return {object} The current object for use in chaining.
             * @return The current object for use in chaining.
             */
             */
             inUserLang: function () {
             inUserLang: function () {
Line 943: Line 628:
             /*
             /*
             * Create a new instance of Message.
             * Create a new instance of Message.
            *
            * @return {object}
             */
             */
             msg: function () {
             msg: function () {
Line 970: Line 653:
     * This allows us to save only those messages needed to the cache.
     * This allows us to save only those messages needed to the cache.
     *
     *
     * @param {string} name The name of the script the messages are for.
     * @param name The name of the script the messages are for.
     * @param {object} messages The message object to look translations up in.
     * @param messages The message object to look translations up in.
     * @param {object} options Options set by the loading script.
     * @param options Options set by the loading script.
     */
     */
     function optimiseMessages(name, messages, options) {
     function optimiseMessages(name, messages, options) {
Line 981: Line 664:


         if (!msgKeys.length) {
         if (!msgKeys.length) {
             /* No English messages, don't bother optimising */
             // no English messages, don't bother optimising
             return messages;
             return messages;
         }
         }


        /*
        * @var addMsgsForLanguage
        */
         var addMsgsForLanguage = function (lang) {
         var addMsgsForLanguage = function (lang) {
             if (optimised[lang]) {
             if (optimised[lang]) {
                 /* Language already exists */
                 // language already exists
                 return;
                 return;
             }
             }
Line 997: Line 677:


             msgKeys.forEach(function (msgName) {
             msgKeys.forEach(function (msgName) {
                /*
                * @var msg
                */
                 var msg = getMsg(messages, msgName, lang);
                 var msg = getMsg(messages, msgName, lang);


Line 1,012: Line 689:
         }
         }


         /*
         // if cache exists and is optimised, preserve existing languages
        * If cache exists and is optimised, preserve existing languages.
        // this allows an optimised cache even when using different
        * This allows an optimised cache even when using different
        // language wikis on same domain (i.e. sharing same cache)
        * language wikis on same domain (i.e. sharing same cache).
        */
         if (existingLangs) {
         if (existingLangs) {
             existingLangs.forEach(function (lang) {
             existingLangs.forEach(function (lang) {
Line 1,027: Line 702:
         langs.forEach(addMsgsForLanguage);
         langs.forEach(addMsgsForLanguage);


         /*
         // `cacheAll` is an array of message names for which translations
        * `cacheAll` is an array of message names for which translations
        // should not be optimised - save all translations of these messages
        * should not be optimised - save all translations of these messages
        */
         if (Array.isArray(options.cacheAll)) {
         if (Array.isArray(options.cacheAll)) {
             msgKeys = options.cacheAll;
             msgKeys = options.cacheAll;
Line 1,042: Line 715:


     /*
     /*
     * Check that the cache for a script exists and, if optimised, contains the
     * Check that the cache for a script exists and, if optimised, contains the necessary languages.
     * necessary languages.
    *
     * @param name The name of the script to check for.
    * @param options Options set by the loading script.
     *
     *
    * @param {string} name The name of the script to check for.
     * @return Boolean whether the cache should be used.
    * @param {object} options Options set by the loading script.
     * @return {boolean} Whether the cache should be used.
     */
     */
     function cacheIsSuitable(name, options) {
     function cacheIsSuitable(name, options) {
         var messages = cache[name] && cache[name]._messages;
         var messages = cache[name] && cache[name]._messages;


         /* Nothing in cache */
         // nothing in cache
         if (!messages) {
         if (!messages) {
             return false;
             return false;
         }
         }


         /*
         // optimised messages missing user or content language
        * Optimised messages missing user or content language.
        // we'll need to load from server in this case
        * We'll need to load from server in this case.
        */
         if (
         if (
             messages._isOptimised &&
             messages._isOptimised &&
             !(messages[options.language] && messages[conf.wgContentLanguage])
             !(messages[options.language] && messages[conf.wgContentLanguage])
      ) {
        ) {
             return false;
             return false;
         }
         }
Line 1,085: Line 756:


         storageKeys.filter(function (key) {
         storageKeys.filter(function (key) {
            return isCacheKey.test(key);
                return isCacheKey.test(key);
         }).forEach(function (key) {
         }).forEach(function (key) {
             var keyPrefix = key.match(isCacheKey)[1],
             var keyPrefix = key.match(isCacheKey)[1],
                 cacheTimestamp;
                 cacheTimestamp;
 
   
             try {
             try {
                 cacheTimestamp = Number(localStorage.getItem(keyPrefix + '-timestamp'));
                 cacheTimestamp = Number(localStorage.getItem(keyPrefix + '-timestamp'));
Line 1,095: Line 766:


             if (now - cacheTimestamp < oneDay * 2) {
             if (now - cacheTimestamp < oneDay * 2) {
                 /* Cached within last two days, keep it */
                 // cached within last two days, keep it
                 return;
                 return;
             }
             }
Line 1,111: Line 782:
     * This is a bit basic, so will remove comments inside strings too.
     * This is a bit basic, so will remove comments inside strings too.
     *
     *
     * @param {string} json The JSON string.
     * @param json The JSON string.
     * @return {string} 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) {
Line 1,124: Line 796:
     * Save messages string to local storage for caching.
     * Save messages string to local storage for caching.
     *
     *
     * @param {string} name The name of the script the messages are for.
     * @param name The name of the script the messages are for.
     * @param {object} json The JSON object.
     * @param json The JSON object.
     * @param {number} cacheVersion Cache version requested by the loading script.
     * @param cacheVersion Cache version requested by the loading script.
     */
     */
     function saveToCache(name, json, cacheVersion) {
     function saveToCache(name, json, cacheVersion) {
        /*
        * @var {string} keyPrefix
        */
         var keyPrefix = cachePrefix + name;
         var keyPrefix = cachePrefix + name;


         /* Don't cache empty JSON */
         // don't cache empty JSON
         if (Object.keys(json).length === 0) {
         if (Object.keys(json).length === 0) {
             return;
             return;
Line 1,149: Line 818:
     * Parse JSON string loaded from page and create an i18n object.
     * Parse JSON string loaded from page and create an i18n object.
     *
     *
     * @param {string} name The name of the script the messages are for.
     * @param name The name of the script the messages are for.
     * @param {string} res The JSON string.
     * @param res The JSON string.
     * @param {object} options Options set by the loading script.
     * @param options Options set by the loading script.
     * @return {object} The resulting i18n object.
    *
     * @return The resulting i18n object.
     */
     */
     function parseMessagesToObject(name, res, options) {
     function parseMessagesToObject(name, res, options) {
Line 1,159: Line 829:
             msg;
             msg;


         /* Handle parse errors gracefully */
         // handle parse errors gracefully
         try {
         try {
             res = stripComments(res);
             res = stripComments(res);
Line 1,177: Line 847:
             !options.loadedFromCache &&
             !options.loadedFromCache &&
             options.cacheAll !== true
             options.cacheAll !== true
      ) {
        ) {
             json = optimiseMessages(name, json, options);
             json = optimiseMessages(name, json, options);
         }
         }
Line 1,183: Line 853:
         obj = i18n(json, name, options);
         obj = i18n(json, name, options);


         /* 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;


Line 1,196: Line 866:
     * Load messages string from local storage cache and add to cache object.
     * Load messages string from local storage cache and add to cache object.
     *
     *
     * @param {string} name The name of the script the messages are for.
     * @param name The name of the script the messages are for.
     * @param {object} options Options set by the loading script.
     * @param options Options set by the loading script.
     */
     */
     function loadFromCache(name, options) {
     function loadFromCache(name, options) {
Line 1,209: Line 879:
         } catch (e) {}
         } catch (e) {}


         /* Cache exists, and its version is greater than or equal to requested version */
         // cache exists, and its version is greater than or equal to requested version
         if (cacheContent && cacheVersion >= options.cacheVersion) {
         if (cacheContent && cacheVersion >= options.cacheVersion) {
             options.loadedFromCache = true;
             options.loadedFromCache = true;
Line 1,219: Line 889:
     * Load messages stored as JSON on a page.
     * Load messages stored as JSON on a page.
     *
     *
     * @param {string} 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
     *    https://dev.fandom.com/wiki/MediaWiki:Custom-name/i18n.json.
     *    https://dev.fandom.com/wiki/MediaWiki:Custom-name/i18n.json.
    *  Use `u:<subdomain>` or `u:<language-path>.<subdomain>` to set other Fandom
     * @param options Options set by the loading script:
    *  wikis as the source.
     *    cacheAll: Either an array of message names for which translations should not be optimised, or `true` to disable the optimised cache.
     * @param {object} options Options set by the loading script:
     *     cacheVersion: Minimum cache version requested by the loading script.
    * - {string} apiEndpoint: Use `u:<subdomain>` or `u:<language-path>.<subdomain>`
     *     language: Set a default language for the script to use, instead of wgUserLanguage.
    *    to set other sites as the API endpoint of the source. Currently only
     *     noCache: Never load i18n from cache (not recommended for general use).
    *    support Fandom wikis.
    * - {string} page: Set other format of the full page name for the i18n JSON.
     *    Use $1 for the placeholder of name.
    * - {(array|boolean)} cacheAll: Either an array of message names for which
    *    translations should not be optimised, or `true` to disable the optimised cache.
     * - {number} cacheVersion: Minimum cache version requested by the loading script.
     * - {string} language: Set a default language for the script to use, instead of wgUserLanguage.
     * - noCache: Never load i18n from cache (not recommended for general use).
     *
     *
     * @return {object} A jQuery.Deferred instance.
     * @return A jQuery.Deferred instance.
     */
     */
     function loadMessages(name, options) {
     function loadMessages(name, options) {
        /*
        * @var {object} deferred
        * @var {string} apiEndpoint
        * @var {RegExp} apiEndpointRgx
        * @var {string} page
        * @var {object} params
        */
         var deferred = $.Deferred(),
         var deferred = $.Deferred(),
             customSource = name.match(/^u:(?:([a-z-]+)\.)?([a-z0-9-]+):/),
             customSource = name.match(/^u:(?:([a-z-]+)\.)?([a-z0-9-]+):/),
             apiEndpoint = 'https://dev.fandom.com/api.php',
             apiEndpoint = 'https://dev.fandom.com/api.php',
            apiEndpointRgx = new RegExp(
                /* '^(https:\/\/(([a-z0-9-]+)\.fandom\.com(?:\/([a-z-]+))?|(([a-z-]+)\.wikipedia\.org\/w))\/api\.php)$' */
                '^(https:\/\/(([a-z0-9-]+)\.fandom\.com(?:\/([a-z-]+))?)\/api\.php)$'
          ),
             page = 'MediaWiki:Custom-' + name + '/i18n.json',
             page = 'MediaWiki:Custom-' + name + '/i18n.json',
             params;
             params;


         options = options || {};
         options = options || {};
        if (options.apiEndpoint && apiEndpointRgx.test(options.apiEndpoint)) {
            options.apiEndpoint = options.apiEndpoint;
        } else {
            options.apiEndpoint = apiEndpoint;
        }
        options.page = (options.page && options.page.replace(/\$1/g, name)) || page;
         options.cacheVersion = Number(options.cacheVersion) || 0;
         options.cacheVersion = Number(options.cacheVersion) || 0;
         options.language = options.language || conf.wgUserLanguage;
         options.language = options.language || conf.wgUserLanguage;
Line 1,275: Line 920:
         }
         }


         /* Cache isn't suitable - loading from server */
         // cache isn't suitable - loading from server
         options.loadedFromCache = false;
         options.loadedFromCache = false;


         /*
         // allow custom i18n pages to be specified on other wikis
        * Allow custom i18n pages to be specified on other wikis.
        // mainly for VSTF wiki to keep their own JSON file
        * Mainly for SOAP Wiki to keep their own JSON file.
        // note this only supports loading from wikis on fandom.com
        * Note this only supports loading from wikis on fandom.com.
        */
         if (customSource) {
         if (customSource) {
             apiEndpoint = apiEndpoint.replace('dev', customSource[2]);
             apiEndpoint = apiEndpoint.replace('dev', customSource[2]);
Line 1,292: Line 935:
                     /api\.php$/,
                     /api\.php$/,
                     customSource[1] + '/$&'
                     customSource[1] + '/$&'
              );
                );
             }
             }
         }
         }
Line 1,309: Line 952:
         };
         };


         /*
         // site and user are dependencies so end-users can set overrides in their local JS
        * 'site' and 'user' are dependencies so end-users can set overrides in their local JS
        // and have it take effect before we load the messagaes
        * and have it take effect before we load the messages.
        // generally, we will implicitly depend on those anyway due to where/when this is loaded
        * Generally, we will implicitly depend on those anyway due to where/when this is loaded.
        */
         mw.loader.using(['mediawiki.language', 'mediawiki.util'/*, 'site', 'user'*/], function () {
         mw.loader.using(['mediawiki.language', 'mediawiki.util'/*, 'site', 'user'*/], function () {
             $.ajax(apiEndpoint, {
             $.ajax(apiEndpoint, {
Line 1,332: Line 973:
     }
     }


     /* Expose under the dev global */
     // expose under the dev global
     window.dev.i18n = $.extend(window.dev.i18n, {
     window.dev.i18n = $.extend(window.dev.i18n, {
         loadMessages: loadMessages,
         loadMessages: loadMessages,


         /*
         // 'hidden' functions to allow testing and debugging
        * "Hidden" functions to allow testing and debugging
        // they may be changed or removed without warning
        * they may be changed or removed without warning.
        // scripts should not rely on these existing or their output being in any particular format
        * Scripts should not rely on these existing or their output being in any particular format.
        */
        _bcp47: bcp47,
         _stripComments: stripComments,
         _stripComments: stripComments,
         _saveToCache: saveToCache,
         _saveToCache: saveToCache,
Line 1,351: Line 989:
     });
     });


     /* Initialise overrides object */
     // initialise overrides object
     window.dev.i18n.overrides = window.dev.i18n.overrides || {};
     window.dev.i18n.overrides = window.dev.i18n.overrides || {};
     overrides = window.dev.i18n.overrides;
     overrides = window.dev.i18n.overrides;


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


     /* Tidy the localStorage cache of old entries */
     // tidy the localStorage cache of old entries
     removeOldCacheEntries();
     removeOldCacheEntries();


} (this, jQuery, mediaWiki));
}(this, jQuery, mediaWiki));
Anonymous user
Cookies help us deliver our services. By using our services, you agree to our use of cookies.