MediaWiki:Gadget-I18n-js.js: Difference between revisions
From Tardis Wiki, the free Doctor Who reference
No edit summary |
(Remove Wikimedia-Gerrit-style whitespaces) |
||
Line 12: | Line 12: | ||
* includes a check to prevent double loading. This can make it painful to test from your | * includes a check to prevent double loading. This can make it painful to test from your | ||
* JS console. To get around this, add ?usesitejs=0&useuserjs=0 to your URL. | * JS console. To get around this, add ?usesitejs=0&useuserjs=0 to your URL. | ||
*/ | */ | ||
Line 26: | Line 24: | ||
*/ | */ | ||
( function ( window, $, mw, undefined ) { | (function (window, $, mw, undefined) { | ||
'use strict'; | 'use strict'; | ||
Line 33: | Line 31: | ||
/* 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; | ||
} | } | ||
Line 57: | Line 55: | ||
* 'null' when the page lannguage doesn't have language variants. | * 'null' when the page lannguage doesn't have language variants. | ||
*/ | */ | ||
var conf = mw.config.get( [ | var conf = mw.config.get([ | ||
'debug', | 'debug', | ||
'wgContentLanguage', | 'wgContentLanguage', | ||
Line 63: | Line 61: | ||
'wgUserLanguage', | 'wgUserLanguage', | ||
'wgUserVariant' | 'wgUserVariant' | ||
]), | |||
/* | /* | ||
Line 92: | Line 90: | ||
/* | /* | ||
* 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. See documentation for how to use. | * Allows end-users to override specific messages. | ||
* See documentation for how to use. | |||
* | * | ||
* @var {(null|object)} overrides | * @var {(null|object)} overrides | ||
Line 102: | Line 101: | ||
* versions of MediaWiki to up-to-date, current language codes. | * versions of MediaWiki to up-to-date, current language codes. | ||
* | * | ||
* These codes shouldn't be used to store translations unless there are | * These codes shouldn't be used to store translations unless there are | ||
* changes to /includes/language/LanguageCode.php in mediawiki/core. | * language changes to /includes/language/LanguageCode.php in mediawiki/core. | ||
* | * | ||
* These may or may not be valid BCP 47 codes; they are included here | * These may or may not be valid BCP 47 codes; they are included here | ||
Line 180: | Line 179: | ||
* including cdo <=> nan, pt <=> pt-br, zh <=> zh-hans <=> zh-hant | * including cdo <=> nan, pt <=> pt-br, zh <=> zh-hans <=> zh-hant | ||
* | * | ||
* @var {object.<string, string[]>} Mapping from language codes to fallback language codes | * @var {object.<string, string[]>} Mapping from language codes to fallback | ||
* language codes | |||
*/ | */ | ||
fallbacks = { | fallbacks = { | ||
'ab': [ 'ru' ], | 'ab': ['ru'], | ||
'abs': [ 'id' ], | 'abs': ['id'], | ||
'ace': [ 'id' ], | 'ace': ['id'], | ||
'ady': [ 'ady-cyrl' ], | 'ady': ['ady-cyrl'], | ||
'aeb': [ 'aeb-arab' ], | 'aeb': ['aeb-arab'], | ||
'aeb-arab': [ 'ar' ], | 'aeb-arab': ['ar'], | ||
'aln': [ 'sq' ], | 'aln': ['sq'], | ||
'alt': [ 'ru' ], | 'alt': ['ru'], | ||
'ami': [ 'zh-tw', 'zh-hant', 'zh', 'zh-hans' ], | 'ami': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'], | ||
'an': [ 'es' ], | 'an': ['es'], | ||
'anp': [ 'hi' ], | 'anp': ['hi'], | ||
'arn': [ 'es' ], | 'arn': ['es'], | ||
'arq': [ 'ar' ], | 'arq': ['ar'], | ||
'ary': [ 'ar' ], | 'ary': ['ar'], | ||
'arz': [ 'ar' ], | 'arz': ['ar'], | ||
'ast': [ 'es' ], | 'ast': ['es'], | ||
'atj': [ 'fr' ], | 'atj': ['fr'], | ||
'av': [ 'ru' ], | 'av': ['ru'], | ||
'avk': [ 'fr', 'es', 'ru' ], | 'avk': ['fr', 'es', 'ru'], | ||
'awa': [ 'hi' ], | 'awa': ['hi'], | ||
'ay': [ 'es' ], | 'ay': ['es'], | ||
'azb': [ 'fa' ], | 'azb': ['fa'], | ||
'ba': [ 'ru' ], | 'ba': ['ru'], | ||
'ban': [ 'id' ], | 'ban': ['id'], | ||
'ban-bali': [ 'ban' ], | 'ban-bali': ['ban'], | ||
'bar': [ 'de' ], | 'bar': ['de'], | ||
'bbc': [ 'bbc-latn' ], | 'bbc': ['bbc-latn'], | ||
'bbc-latn': [ 'id' ], | 'bbc-latn': ['id'], | ||
'bcc': [ 'fa' ], | 'bcc': ['fa'], | ||
'bci': [ 'fr' ], | 'bci': ['fr'], | ||
'be-tarask': [ 'be' ], | 'be-tarask': ['be'], | ||
'bgn': [ 'fa' ], | 'bgn': ['fa'], | ||
'bh': [ 'bho' ], | 'bh': ['bho'], | ||
'bi': [ 'en' ], | 'bi': ['en'], | ||
'bjn': [ 'id' ], | 'bjn': ['id'], | ||
'blk': [ 'my' ], | 'blk': ['my'], | ||
'bm': [ 'fr' ], | 'bm': ['fr'], | ||
'bpy': [ 'bn' ], | 'bpy': ['bn'], | ||
'bqi': [ 'fa' ], | 'bqi': ['fa'], | ||
'br': [ 'fr' ], | 'br': ['fr'], | ||
'btm': [ 'id' ], | 'btm': ['id'], | ||
'bug': [ 'id' ], | 'bug': ['id'], | ||
'bxr': [ 'ru' ], | 'bxr': ['ru'], | ||
'ca': [ 'oc' ], | 'ca': ['oc'], | ||
'cbk-zam': [ 'es' ], | 'cbk-zam': ['es'], | ||
'cdo': [ 'nan', 'zh-hant', 'zh', 'zh-hans' ], | 'cdo': ['nan', 'zh-hant', 'zh', 'zh-hans'], | ||
'ce': [ 'ru' ], | 'ce': ['ru'], | ||
'co': [ 'it' ], | 'co': ['it'], | ||
'crh': [ 'crh-latn' ], | 'crh': ['crh-latn'], | ||
'crh-cyrl': [ 'ru' ], | 'crh-cyrl': ['ru'], | ||
'cs': [ 'sk' ], | 'cs': ['sk'], | ||
'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': [ 'hsb', 'de' ], | 'dsb': ['hsb', 'de'], | ||
'dtp': [ 'ms' ], | 'dtp': ['ms'], | ||
'dty': [ 'ne' ], | 'dty': ['ne'], | ||
'egl': [ 'it' ], | 'egl': ['it'], | ||
'eml': [ 'it' ], | 'eml': ['it'], | ||
'es-formal': [ 'es' ], | 'es-formal': ['es'], | ||
'ext': [ 'es' ], | 'ext': ['es'], | ||
'ff': [ 'fr' ], | 'ff': ['fr'], | ||
'fit': [ 'fi' ], | 'fit': ['fi'], | ||
'fon': [ 'fr' ], | 'fon': ['fr'], | ||
'frc': [ 'fr' ], | 'frc': ['fr'], | ||
'frp': [ 'fr' ], | 'frp': ['fr'], | ||
'frr': [ 'de' ], | 'frr': ['de'], | ||
'fur': [ 'it' ], | 'fur': ['it'], | ||
'gag': [ 'tr' ], | 'gag': ['tr'], | ||
'gan': [ 'gan-hant', 'gan-hans', 'zh-hant', 'zh', 'zh-hans' ], | 'gan': ['gan-hant', 'gan-hans', 'zh-hant', 'zh', 'zh-hans'], | ||
'gan-hans': [ 'gan', 'gan-hant', 'zh-hans', 'zh', 'zh-hant' ], | 'gan-hans': ['gan', 'gan-hant', 'zh-hans', 'zh', 'zh-hant'], | ||
'gan-hant': [ 'gan', 'gan-hans', 'zh-hant', 'zh', 'zh-hans' ], | 'gan-hant': ['gan', 'gan-hans', 'zh-hant', 'zh', 'zh-hans'], | ||
'gcr': [ 'fr' ], | 'gcr': ['fr'], | ||
'gl': [ 'pt' ], | 'gl': ['pt'], | ||
'gld': [ 'ru' ], | 'gld': ['ru'], | ||
'glk': [ 'fa' ], | 'glk': ['fa'], | ||
'gn': [ 'es' ], | 'gn': ['es'], | ||
'gom': [ 'gom-deva' ], | 'gom': ['gom-deva'], | ||
'gom-deva': [ 'hi' ], | 'gom-deva': ['hi'], | ||
'gor': [ 'id' ], | 'gor': ['id'], | ||
'gsw': [ 'de' ], | 'gsw': ['de'], | ||
'guc': [ 'es' ], | 'guc': ['es'], | ||
'hak': [ 'zh-hant', 'zh', 'zh-hans' ], | 'hak': ['zh-hant', 'zh', 'zh-hans'], | ||
'hif': [ 'hif-latn' ], | 'hif': ['hif-latn'], | ||
'hrx': [ 'de' ], | 'hrx': ['de'], | ||
'hsb': [ 'dsb', 'de' ], | 'hsb': ['dsb', 'de'], | ||
'hsn': [ 'zh-cn', 'zh-hans', 'zh', 'zh-hant' ], | 'hsn': ['zh-cn', 'zh-hans', 'zh', 'zh-hant'], | ||
'ht': [ 'fr' ], | 'ht': ['fr'], | ||
'hu-formal': [ 'hu' ], | 'hu-formal': ['hu'], | ||
'hyw': [ 'hy' ], | 'hyw': ['hy'], | ||
'ii': [ 'zh-cn', 'zh-hans', 'zh', 'zh-hant' ], | 'ii': ['zh-cn', 'zh-hans', 'zh', 'zh-hant'], | ||
'inh': [ 'ru' ], | 'inh': ['ru'], | ||
'io': [ 'eo' ], | 'io': ['eo'], | ||
'iu': [ 'ike-cans' ], | 'iu': ['ike-cans'], | ||
'jam': [ 'en' ], | 'jam': ['en'], | ||
'jut': [ 'da' ], | 'jut': ['da'], | ||
'jv': [ 'id' ], | 'jv': ['id'], | ||
'kaa': [ 'kk-latn', 'kk-cyrl' ], | 'kaa': ['kk-latn', 'kk-cyrl'], | ||
'kab': [ 'fr' ], | 'kab': ['fr'], | ||
'kbd': [ 'kbd-cyrl' ], | 'kbd': ['kbd-cyrl'], | ||
'kbp': [ 'fr' ], | 'kbp': ['fr'], | ||
'kea': [ 'pt' ], | 'kea': ['pt'], | ||
'khw': [ 'ur' ], | 'khw': ['ur'], | ||
'kiu': [ 'tr' ], | 'kiu': ['tr'], | ||
'kjp': [ 'my' ], | 'kjp': ['my'], | ||
'kk': [ 'kk-cyrl' ], | 'kk': ['kk-cyrl'], | ||
'kk-arab': [ 'kk-cyrl' ], | 'kk-arab': ['kk-cyrl'], | ||
'kk-cn': [ 'kk-arab', 'kk-cyrl' ], | 'kk-cn': ['kk-arab', 'kk-cyrl'], | ||
'kk-kz': [ 'kk-cyrl' ], | 'kk-kz': ['kk-cyrl'], | ||
'kk-latn': [ 'kk-cyrl' ], | 'kk-latn': ['kk-cyrl'], | ||
'kk-tr': [ 'kk-latn', 'kk-cyrl' ], | 'kk-tr': ['kk-latn', 'kk-cyrl'], | ||
'kl': [ 'da' ], | 'kl': ['da'], | ||
'koi': [ 'ru' ], | 'koi': ['ru'], | ||
'ko-kp': [ 'ko' ], | 'ko-kp': ['ko'], | ||
'krc': [ 'ru' ], | 'krc': ['ru'], | ||
'krl': [ 'fi' ], | 'krl': ['fi'], | ||
'ks': [ 'ks-arab' ], | 'ks': ['ks-arab'], | ||
'ksh': [ 'de' ], | 'ksh': ['de'], | ||
'ksw': [ 'my' ], | 'ksw': ['my'], | ||
'ku': [ 'ku-latn' ], | 'ku': ['ku-latn'], | ||
'kum': [ 'ru' ], | 'kum': ['ru'], | ||
'ku-arab': [ 'ckb' ], | 'ku-arab': ['ckb'], | ||
'kv': [ 'ru' ], | 'kv': ['ru'], | ||
'lad': [ 'es' ], | 'lad': ['es'], | ||
'lb': [ 'de' ], | 'lb': ['de'], | ||
'lbe': [ 'ru' ], | 'lbe': ['ru'], | ||
'lez': [ 'ru', 'az' ], | 'lez': ['ru', 'az'], | ||
'li': [ 'nl' ], | 'li': ['nl'], | ||
'lij': [ 'it' ], | 'lij': ['it'], | ||
'liv': [ 'et' ], | 'liv': ['et'], | ||
'lki': [ 'fa' ], | 'lki': ['fa'], | ||
'lld': [ 'it', 'rm', 'fur' ], | 'lld': ['it', 'rm', 'fur'], | ||
'lmo': [ 'pms', 'eml', 'lij', 'vec', 'it' ], | 'lmo': ['pms', 'eml', 'lij', 'vec', 'it'], | ||
'ln': [ 'fr' ], | 'ln': ['fr'], | ||
'lrc': [ 'fa' ], | 'lrc': ['fa'], | ||
'ltg': [ 'lv' ], | 'ltg': ['lv'], | ||
'luz': [ 'fa' ], | 'luz': ['fa'], | ||
'lzh': [ 'zh-hant', 'zh', 'zh-hans' ], | 'lzh': ['zh-hant', 'zh', 'zh-hans'], | ||
'lzz': [ 'tr' ], | 'lzz': ['tr'], | ||
'mad': [ 'id' ], | 'mad': ['id'], | ||
'mai': [ 'hi' ], | 'mai': ['hi'], | ||
'map-bms': [ 'jv', 'id' ], | 'map-bms': ['jv', 'id'], | ||
'mdf': [ 'myv', 'ru' ], | 'mdf': ['myv', 'ru'], | ||
'mg': [ 'fr' ], | 'mg': ['fr'], | ||
'mhr': [ 'mrj', 'ru' ], | 'mhr': ['mrj', 'ru'], | ||
'min': [ 'id' ], | 'min': ['id'], | ||
'mnw': [ 'my' ], | 'mnw': ['my'], | ||
'mo': [ 'ro' ], | 'mo': ['ro'], | ||
'mrj': [ 'mhr', 'ru' ], | 'mrj': ['mhr', 'ru'], | ||
'ms-arab': [ 'ms' ], | 'ms-arab': ['ms'], | ||
'mwl': [ 'pt' ], | 'mwl': ['pt'], | ||
'myv': [ 'mdf', 'ru' ], | 'myv': ['mdf', 'ru'], | ||
'mzn': [ 'fa' ], | 'mzn': ['fa'], | ||
'nah': [ 'es' ], | 'nah': ['es'], | ||
'nan': [ 'cdo', 'zh-hant', 'zh', 'zh-hans' ], | 'nan': ['cdo', 'zh-hant', 'zh', 'zh-hans'], | ||
'nap': [ 'it' ], | 'nap': ['it'], | ||
'nb': [ 'nn' ], | 'nb': ['nn'], | ||
'nds': [ 'de' ], | 'nds': ['de'], | ||
'nds-nl': [ 'nl' ], | 'nds-nl': ['nl'], | ||
'nia': [ 'id' ], | 'nia': ['id'], | ||
'nl-informal': [ 'nl' ], | 'nl-informal': ['nl'], | ||
'nn': [ 'nb' ], | 'nn': ['nb'], | ||
'nrm': [ 'nrf', 'fr' ], | 'nrm': ['nrf', 'fr'], | ||
'oc': [ 'ca', 'fr' ], | 'oc': ['ca', 'fr'], | ||
'olo': [ 'fi' ], | 'olo': ['fi'], | ||
'os': [ 'ru' ], | 'os': ['ru'], | ||
'pcd': [ 'fr' ], | 'pcd': ['fr'], | ||
'pdc': [ 'de' ], | 'pdc': ['de'], | ||
'pdt': [ 'de' ], | 'pdt': ['de'], | ||
'pfl': [ 'de' ], | 'pfl': ['de'], | ||
'pih': [ 'en' ], | 'pih': ['en'], | ||
'pms': [ 'it' ], | 'pms': ['it'], | ||
'pnt': [ 'el' ], | 'pnt': ['el'], | ||
'pt': [ 'pt-br' ], | 'pt': ['pt-br'], | ||
'pt-br': [ 'pt' ], | 'pt-br': ['pt'], | ||
'pwn': [ 'zh-tw', 'zh-hant', 'zh', 'zh-hans' ], | 'pwn': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'], | ||
'qu': [ 'qug', 'es' ], | 'qu': ['qug', 'es'], | ||
'qug': [ 'qu', 'es' ], | 'qug': ['qu', 'es'], | ||
'rgn': [ 'it' ], | 'rgn': ['it'], | ||
'rmy': [ 'ro' ], | 'rmy': ['ro'], | ||
'roa-tara': [ 'it' ], | 'roa-tara': ['it'], | ||
'rsk': [ 'sr-ec' ], | 'rsk': ['sr-ec'], | ||
'rue': [ 'uk', 'ru' ], | 'rue': ['uk', 'ru'], | ||
'rup': [ 'ro' ], | 'rup': ['ro'], | ||
'ruq': [ 'ruq-latn', 'ro' ], | 'ruq': ['ruq-latn', 'ro'], | ||
'ruq-cyrl': [ 'mk' ], | 'ruq-cyrl': ['mk'], | ||
'ruq-latn': [ 'ro' ], | 'ruq-latn': ['ro'], | ||
'sa': [ 'hi' ], | 'sa': ['hi'], | ||
'sah': [ 'ru' ], | 'sah': ['ru'], | ||
'scn': [ 'it' ], | 'scn': ['it'], | ||
'sco': [ 'en' ], | 'sco': ['en'], | ||
'sdc': [ 'it' ], | 'sdc': ['it'], | ||
'sdh': [ 'cbk', 'fa' ], | 'sdh': ['cbk', 'fa'], | ||
'se': [ 'nb', 'fi' ], | 'se': ['nb', 'fi'], | ||
'ses': [ 'fr' ], | 'ses': ['fr'], | ||
'se-fi': [ 'se', 'fi', 'sv' ], | 'se-fi': ['se', 'fi', 'sv'], | ||
'se-no': [ 'se', 'nb', 'nn' ], | 'se-no': ['se', 'nb', 'nn'], | ||
'se-se': [ 'se', 'sv' ], | 'se-se': ['se', 'sv'], | ||
'sg': [ 'fr' ], | 'sg': ['fr'], | ||
'sgs': [ 'lt' ], | 'sgs': ['lt'], | ||
'sh': [ 'bs', 'sr-el', 'hr' ], | 'sh': ['bs', 'sr-el', 'hr'], | ||
'shi': [ 'fr' ], | 'shi': ['fr'], | ||
'shy': [ 'shy-latn' ], | 'shy': ['shy-latn'], | ||
'shy-latn': [ 'fr' ], | 'shy-latn': ['fr'], | ||
'sjd': [ 'ru' ], | 'sjd': ['ru'], | ||
'sk': [ 'cs' ], | 'sk': ['cs'], | ||
'skr': [ 'skr-arab' ], | 'skr': ['skr-arab'], | ||
'skr-arab': [ 'ur', 'pnb' ], | 'skr-arab': ['ur', 'pnb'], | ||
'sli': [ 'de' ], | 'sli': ['de'], | ||
'smn': [ 'fi' ], | 'smn': ['fi'], | ||
'sr': [ 'sr-ec' ], | 'sr': ['sr-ec'], | ||
'srn': [ 'nl' ], | 'srn': ['nl'], | ||
'stq': [ 'de' ], | 'stq': ['de'], | ||
'sty': [ 'ru' ], | 'sty': ['ru'], | ||
'su': [ 'id' ], | 'su': ['id'], | ||
'szl': [ 'pl' ], | 'szl': ['pl'], | ||
'szy': [ 'zh-tw', 'zh-hant', 'zh', 'zh-hans' ], | 'szy': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'], | ||
'tay': [ 'zh-tw', 'zh-hant', 'zh', 'zh-hans' ], | 'tay': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'], | ||
'tcy': [ 'kn' ], | 'tcy': ['kn'], | ||
'tet': [ 'pt' ], | 'tet': ['pt'], | ||
'tg': [ 'tg-cyrl' ], | 'tg': ['tg-cyrl'], | ||
'trv': [ 'zh-tw', 'zh-hant', 'zh', 'zh-hans' ], | 'trv': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'], | ||
'tt': [ 'tt-cyrl', 'ru' ], | 'tt': ['tt-cyrl', 'ru'], | ||
'tt-cyrl': [ 'ru' ], | 'tt-cyrl': ['ru'], | ||
'ty': [ 'fr' ], | 'ty': ['fr'], | ||
'tyv': [ 'ru' ], | 'tyv': ['ru'], | ||
'udm': [ 'ru' ], | 'udm': ['ru'], | ||
'ug': [ 'ug-arab' ], | 'ug': ['ug-arab'], | ||
'vec': [ 'it' ], | 'vec': ['it'], | ||
'vep': [ 'et' ], | 'vep': ['et'], | ||
'vls': [ 'nl' ], | 'vls': ['nl'], | ||
'vmf': [ 'de' ], | 'vmf': ['de'], | ||
'vmw': [ 'pt' ], | 'vmw': ['pt'], | ||
'vot': [ 'fi' ], | 'vot': ['fi'], | ||
'vro': [ 'et' ], | 'vro': ['et'], | ||
'wa': [ 'fr' ], | 'wa': ['fr'], | ||
'wls': [ 'fr' ], | 'wls': ['fr'], | ||
'wo': [ 'fr' ], | 'wo': ['fr'], | ||
'wuu': [ 'zh-hans', 'zh', 'zh-hant' ], | 'wuu': ['zh-hans', 'zh', 'zh-hant'], | ||
'xal': [ 'ru' ], | 'xal': ['ru'], | ||
'xmf': [ 'ka' ], | 'xmf': ['ka'], | ||
'yi': [ 'he' ], | 'yi': ['he'], | ||
'yue': [ 'zh-hk', 'zh-hant', 'zh', 'zh-hans' ], | 'yue': ['zh-hk', 'zh-hant', 'zh', 'zh-hans'], | ||
'za': [ 'zh-hans', 'zh', 'zh-hant' ], | 'za': ['zh-hans', 'zh', 'zh-hant'], | ||
'zea': [ 'nl' ], | 'zea': ['nl'], | ||
'zgh': [ 'kab' ], | 'zgh': ['kab'], | ||
'zh': [ 'zh-hans', 'zh-hant', 'zh-cn', 'zh-tw', 'zh-hk' ], | 'zh': ['zh-hans', 'zh-hant', 'zh-cn', 'zh-tw', 'zh-hk'], | ||
'zh-cn': [ 'zh-hans', 'zh', 'zh-hant' ], | 'zh-cn': ['zh-hans', 'zh', 'zh-hant'], | ||
'zh-hans': [ 'zh-cn', 'zh', 'zh-hant' ], | 'zh-hans': ['zh-cn', 'zh', 'zh-hant'], | ||
'zh-hant': [ 'zh-tw', 'zh-hk', 'zh', 'zh-hans' ], | 'zh-hant': ['zh-tw', 'zh-hk', 'zh', 'zh-hans'], | ||
'zh-hk': [ 'zh-hant', 'zh-tw', 'zh', 'zh-hans' ], | 'zh-hk': ['zh-hant', 'zh-tw', 'zh', 'zh-hans'], | ||
'zh-mo': [ '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-my': ['zh-sg', 'zh-hans', 'zh-cn', 'zh', 'zh-hant'], | ||
'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' ] | 'zh-tw': ['zh-hant', 'zh-hk', 'zh', 'zh-hans'] | ||
}; | }; | ||
Line 456: | Line 456: | ||
* @see /includes/language/LanguageCode.php in MediaWiki core | * @see /includes/language/LanguageCode.php in MediaWiki core | ||
*/ | */ | ||
function bcp47( lang ) { | function bcp47(lang) { | ||
if ( nonStandardCodes[ lang ] ) { | if (nonStandardCodes[lang]) { | ||
return nonStandardCodes[ lang ]; | return nonStandardCodes[lang]; | ||
} | } | ||
if ( deprecatedCodes[ lang ] ) { | if (deprecatedCodes[lang]) { | ||
return bcp47( deprecatedCodes[ lang ] ); | return bcp47(deprecatedCodes[lang]); | ||
} | } | ||
Line 474: | Line 474: | ||
isFirstSegment = true, | isFirstSegment = true, | ||
isPrivate = false, | isPrivate = false, | ||
segments = lang.split( '-' ); | segments = lang.split('-'); | ||
formatted = segments.map( function ( segment ) { | formatted = segments.map(function (segment) { | ||
/* | /* | ||
* @var {string} newSegment The converted segment of language code | * @var {string} newSegment The converted segment of language code | ||
Line 483: | Line 483: | ||
/* when previous segment is x, it is a private segment and should be lc */ | /* when previous segment is x, it is a private segment and should be lc */ | ||
if ( isPrivate ) { | if (isPrivate) { | ||
newSegment = segment.toLowerCase(); | newSegment = segment.toLowerCase(); | ||
/* ISO 3166 country code */ | /* ISO 3166 country code */ | ||
} else if ( segment.length === 2 && !isFirstSegment ) { | } else if (segment.length === 2 && !isFirstSegment) { | ||
newSegment = segment.toUpperCase(); | newSegment = segment.toUpperCase(); | ||
/* ISO 15924 script code */ | /* ISO 15924 script code */ | ||
} else if ( segment.length === 4 && !isFirstSegment ) { | } else if (segment.length === 4 && !isFirstSegment) { | ||
newSegment = segment.charAt( 0 ).toUpperCase() + segment.substring( 1 ).toLowerCase(); | newSegment = segment.charAt(0).toUpperCase() + segment.substring(1).toLowerCase(); | ||
/* Use lowercase for other cases */ | /* Use lowercase for other cases */ | ||
} else { | } else { | ||
Line 500: | Line 500: | ||
return newSegment; | return newSegment; | ||
} ); | }); | ||
return formatted.join( '-' ); | return formatted.join('-'); | ||
} | } | ||
Line 512: | Line 512: | ||
* @param {string[]} fallbackChain Array of languages involved in the loop. | * @param {string[]} fallbackChain Array of languages involved in the loop. | ||
*/ | */ | ||
function warnOnFallbackLoop( lang, fallbackChain ) { | function warnOnFallbackLoop(lang, fallbackChain) { | ||
if ( warnedAboutFallbackLoop ) { | if (warnedAboutFallbackLoop) { | ||
return; | return; | ||
} | } | ||
warnedAboutFallbackLoop = true; | warnedAboutFallbackLoop = true; | ||
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] 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(', ')); | ||
} | } | ||
Line 533: | Line 533: | ||
* @return {(string|boolean)} The requested translation or `false` if no message could be found. | * @return {(string|boolean)} 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 (deprecatedCodes[lang]) { | ||
return getMsg( messages, msgName, deprecatedCodes[ lang ], fallbackChain ); | return getMsg(messages, msgName, deprecatedCodes[lang], fallbackChain); | ||
} | } | ||
if ( messages[ lang ] && messages[ lang ][ msgName ] ) { | if (messages[lang] && messages[lang][msgName]) { | ||
return messages[ lang ][ msgName ]; | return messages[lang][msgName]; | ||
} | } | ||
if ( !fallbackChain ) { | if (!fallbackChain) { | ||
fallbackChain = []; | fallbackChain = []; | ||
} | } | ||
Line 549: | Line 549: | ||
* @var {string} fallbackLang | * @var {string} fallbackLang | ||
*/ | */ | ||
for ( var fallbackLang of fallbacks[ lang ] ) { | for (var fallbackLang of fallbacks[lang]) { | ||
if ( messages[ fallbackLang ] && messages[ fallbackLang ][ msgName ] ) { | if (messages[fallbackLang] && messages[fallbackLang][msgName]) { | ||
return messages[ fallbackLang ][ msgName ]; | return messages[fallbackLang][msgName]; | ||
} | } | ||
if ( fallbackChain.indexOf( fallbackLang ) !== -1 ) { | if (fallbackChain.indexOf(fallbackLang) !== -1) { | ||
/* | /* | ||
* Duplicated language code in fallback list | * Duplicated language code in fallback list | ||
* Try to find next fallback language from list | * Try to find next fallback language from list | ||
*/ | */ | ||
warnOnFallbackLoop( fallbackLang, fallbackChain ); | warnOnFallbackLoop(fallbackLang, fallbackChain); | ||
continue; | continue; | ||
} | } | ||
fallbackChain.push( fallbackLang ); | fallbackChain.push(fallbackLang); | ||
} | } | ||
/* No more languages in fallback list - switch to English */ | /* No more languages in fallback list - switch to English */ | ||
if ( messages.en && messages.en[ msgName ] ) { | if (messages.en && messages.en[msgName]) { | ||
return messages.en[ msgName ]; | return messages.en[msgName]; | ||
} | } | ||
Line 581: | Line 581: | ||
* @return {string} The resulting message. | * @return {string} 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 {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); | ||
} ); | }); | ||
return message; | return message; | ||
Line 604: | Line 604: | ||
* @return {string} The generated link. | * @return {string} The generated link. | ||
*/ | */ | ||
function makeLink( href, text, hasProtocol ) { | function makeLink(href, text, hasProtocol) { | ||
text = text || href; | text = text || href; | ||
href = hasProtocol ? href : mw.util.getUrl( href ); | href = hasProtocol ? href : mw.util.getUrl(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>'; | ||
Line 635: | Line 635: | ||
* @return The sanitised HTML code. | * @return The sanitised HTML code. | ||
*/ | */ | ||
function sanitiseHtml( html ) { | function sanitiseHtml(html) { | ||
/* | /* | ||
* @var context | * @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), | ||
$div = $( '<div>', context ).append( $html ), | $div = $('<div>', context).append($html), | ||
allowedAttrs = [ | allowedAttrs = [ | ||
'title', | 'title', | ||
'style', | 'style', | ||
'class' | 'class' | ||
], | |||
allowedTags = [ | allowedTags = [ | ||
'i', | 'i', | ||
Line 655: | Line 655: | ||
'strong', | 'strong', | ||
'span', | 'span', | ||
]; | |||
$div.find( '*' ).each( function () { | $div.find('*').each(function () { | ||
var $this = $( this ), | var $this = $(this), | ||
tagname = $this.prop( 'tagName' ).toLowerCase(), | tagname = $this.prop('tagName').toLowerCase(), | ||
attrs, | attrs, | ||
array, | array, | ||
style; | style; | ||
if ( allowedTags.indexOf( tagname ) === -1 ) { | if (allowedTags.indexOf(tagname) === -1) { | ||
mw.log( '[I18n-js] Disallowed tag in message: ' + tagname ); | mw.log('[I18n-js] Disallowed tag in message: ' + tagname); | ||
$this.remove(); | $this.remove(); | ||
return; | return; | ||
} | } | ||
attrs = $this.prop( 'attributes' ); | attrs = $this.prop('attributes'); | ||
array = Array.prototype.slice.call( attrs ); | array = Array.prototype.slice.call(attrs); | ||
array.forEach( function ( attr ) { | array.forEach(function (attr) { | ||
if ( allowedAttrs.indexOf( attr.name ) === -1 ) { | if (allowedAttrs.indexOf(attr.name) === -1) { | ||
mw.log( '[I18n-js] Disallowed attribute in message: ' + attr.name + ', tag: ' + tagname ); | mw.log('[I18n-js] Disallowed attribute in message: ' + attr.name + ', tag: ' + tagname); | ||
$this.removeAttr( attr.name ); | $this.removeAttr(attr.name); | ||
return; | return; | ||
} | } | ||
/* 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'); | ||
if ( style.indexOf( 'url(' ) > -1 ) { | if (style.indexOf('url(') > -1) { | ||
mw.log( '[I18n-js] Disallowed url() in style attribute' ); | mw.log('[I18n-js] Disallowed url() in style attribute'); | ||
$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'); | ||
$this.removeAttr( 'style' ); | $this.removeAttr('style'); | ||
} | } | ||
} | } | ||
} ); | }); | ||
} ); | }); | ||
return $div.prop( 'innerHTML' ); | return $div.prop('innerHTML'); | ||
} | } | ||
Line 713: | Line 713: | ||
* @return {string} The resulting string. | * @return {string} 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, | ||
Line 725: | Line 725: | ||
genderRgx = /\{\{GENDER:([^|]+)\|(.+?)\}\}/gi; | genderRgx = /\{\{GENDER:([^|]+)\|(.+?)\}\}/gi; | ||
if ( message.indexOf( '<' ) > -1 ) { | if (message.indexOf('<') > -1) { | ||
message = sanitiseHtml( message ); | message = sanitiseHtml(message); | ||
} | } | ||
return message | return message | ||
.replace( urlRgx, function ( _match, href, text ) { | .replace(urlRgx, function (_match, href, text) { | ||
return makeLink( href, text, true ); | return makeLink(href, text, true); | ||
} ) | }) | ||
.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); | ||
} ) | }) | ||
.replace( pluralRgx, function ( _match, count, forms ) { | .replace(pluralRgx, function (_match, count, forms) { | ||
return mw.language.convertPlural( Number( count ), forms.split( '|' ) ); | return mw.language.convertPlural(Number(count), forms.split('|')); | ||
} ) | }) | ||
.replace(genderRgx, function ( _match, gender, forms ) { | .replace(genderRgx, function (_match, gender, forms) { | ||
return mw.language.gender( gender, forms.split( '|' ) ); | return mw.language.gender(gender, forms.split('|')); | ||
} ); | }); | ||
} | } | ||
Line 756: | Line 756: | ||
* @return | * @return | ||
*/ | */ | ||
function message( messages, lang, args, name ) { | function message(messages, lang, args, name) { | ||
if ( !args.length ) { | if (!args.length) { | ||
return; | return; | ||
} | } | ||
Line 769: | Line 769: | ||
var msgName = args.shift(), | var msgName = args.shift(), | ||
descriptiveMsgName = 'i18njs-' + name + '-' + msgName, | descriptiveMsgName = 'i18njs-' + name + '-' + msgName, | ||
msg = getMsg( messages, msgName, lang ), | msg = getMsg(messages, msgName, lang), | ||
msgExists = msg !== false; | msgExists = msg !== false; | ||
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; | ||
} | } | ||
if ( args.length ) { | if (args.length) { | ||
msg = handleArgs( msg, args ); | msg = handleArgs(msg, args); | ||
} | } | ||
Line 806: | Line 806: | ||
* the sanitisation will mess with it. | * the sanitisation will mess with it. | ||
*/ | */ | ||
if ( !this.exists ) { | if (!this.exists) { | ||
return this.escape(); | return this.escape(); | ||
} | } | ||
return parse( msg ); | return parse(msg); | ||
}, | }, | ||
Line 819: | Line 819: | ||
*/ | */ | ||
escape: function () { | escape: function () { | ||
return mw.html.escape( msg ); | return mw.html.escape(msg); | ||
}, | }, | ||
Line 841: | Line 841: | ||
* @return {object} | * @return {object} | ||
*/ | */ | ||
function i18n( messages, name, options ) { | function i18n(messages, name, options) { | ||
var defaultLang = options.language, | var defaultLang = options.language, | ||
tempLang = null; | tempLang = null; | ||
Line 852: | Line 852: | ||
*/ | */ | ||
useLang: function () { | useLang: function () { | ||
console.warn( '[I18n-js] “useLang()” is no longer supported by I18n-js (used in “' + name + '”) - using user language.' ); | console.warn('[I18n-js] “useLang()” is no longer supported by I18n-js (used in “' + name + '”) - using user language.'); | ||
this.useUserLang(); | this.useUserLang(); | ||
}, | }, | ||
Line 864: | Line 864: | ||
*/ | */ | ||
inLang: function (lang) { | inLang: function (lang) { | ||
if ( !options.cacheAll ) { | if (!options.cacheAll) { | ||
console.warn( '[I18n-js] “inLang()” is not supported without configuring `options.cacheAll` (used in “' + name + '”) - using user language.' ); | console.warn('[I18n-js] “inLang()” is not supported without configuring `options.cacheAll` (used in “' + name + '”) - using user language.'); | ||
lang = options.language; | lang = options.language; | ||
} | } | ||
Line 948: | Line 948: | ||
*/ | */ | ||
msg: function () { | msg: function () { | ||
var args = Array.prototype.slice.call( arguments ), | var args = Array.prototype.slice.call(arguments), | ||
lang = defaultLang; | lang = defaultLang; | ||
if ( tempLang !== null ) { | if (tempLang !== null) { | ||
lang = tempLang; | lang = tempLang; | ||
tempLang = null; | tempLang = null; | ||
} | } | ||
return message( messages, lang, args, name ); | return message(messages, lang, args, name); | ||
}, | }, | ||
Line 975: | Line 975: | ||
* @param {object} options Options set by the loading script. | * @param {object} options Options set by the loading script. | ||
*/ | */ | ||
function optimiseMessages( name, messages, options ) { | function optimiseMessages(name, messages, options) { | ||
var existingLangs = cache[ name ] && cache[ name ]._messages._isOptimised, | var existingLangs = cache[name] && cache[name]._messages._isOptimised, | ||
langs = [ options.language ], | langs = [options.language], | ||
msgKeys = Object.keys( messages.en || {} ), | msgKeys = Object.keys(messages.en || {}), | ||
optimised = {}; | optimised = {}; | ||
if ( !msgKeys.length ) { | if (!msgKeys.length) { | ||
/* No English messages, don't bother optimising */ | /* No English messages, don't bother optimising */ | ||
return messages; | return messages; | ||
Line 990: | Line 990: | ||
*/ | */ | ||
var addMsgsForLanguage = function (lang) { | var addMsgsForLanguage = function (lang) { | ||
if ( optimised[ lang ] ) { | if (optimised[lang]) { | ||
/* Language already exists */ | /* Language already exists */ | ||
return; | return; | ||
} | } | ||
optimised[ lang ] = {}; | optimised[lang] = {}; | ||
msgKeys.forEach( function ( msgName ) { | msgKeys.forEach(function (msgName) { | ||
/* | /* | ||
* @var msg | * @var msg | ||
*/ | */ | ||
var msg = getMsg( messages, msgName, lang ); | var msg = getMsg(messages, msgName, lang); | ||
if ( msg !== false ) { | if (msg !== false) { | ||
optimised[ lang ][ msgName ] = msg; | optimised[lang][msgName] = msg; | ||
} | } | ||
} ); | }); | ||
}; | }; | ||
if ( langs.indexOf( conf.wgContentLanguage ) === -1 ) { | if (langs.indexOf(conf.wgContentLanguage) === -1) { | ||
langs.push( conf.wgContentLanguage ); | langs.push(conf.wgContentLanguage); | ||
} | } | ||
Line 1,018: | Line 1,018: | ||
* 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) { | ||
if ( langs.indexOf( lang ) === -1 ) { | if (langs.indexOf(lang) === -1) { | ||
langs.push( lang ); | langs.push(lang); | ||
} | } | ||
} ); | }); | ||
} | } | ||
langs.forEach( addMsgsForLanguage ); | langs.forEach(addMsgsForLanguage); | ||
/* | /* | ||
Line 1,032: | Line 1,032: | ||
* 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; | ||
Object.keys( messages ).forEach( addMsgsForLanguage ); | Object.keys(messages).forEach(addMsgsForLanguage); | ||
} | } | ||
Line 1,043: | Line 1,043: | ||
/* | /* | ||
* Check that the cache for a script exists and, if optimised, contains the necessary languages. | * Check that the cache for a script exists and, if optimised, contains the | ||
* necessary languages. | |||
* | * | ||
* @param {string} name The name of the script to check for. | * @param {string} name The name of the script to check for. | ||
Line 1,049: | Line 1,050: | ||
* @return {boolean} Whether the cache should be used. | * @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; | ||
} | } | ||
Line 1,063: | Line 1,064: | ||
if ( | if ( | ||
messages._isOptimised && | messages._isOptimised && | ||
!( messages[ options.language ] && messages[ conf.wgContentLanguage ] ) | !(messages[options.language] && messages[conf.wgContentLanguage]) | ||
) { | |||
return false; | return false; | ||
} | } | ||
Line 1,077: | Line 1,078: | ||
*/ | */ | ||
function removeOldCacheEntries() { | function removeOldCacheEntries() { | ||
var isCacheKey = new RegExp( '^(' + cachePrefix + '.+)-content$' ), | var isCacheKey = new RegExp('^(' + cachePrefix + '.+)-content$'), | ||
storageKeys = []; | storageKeys = []; | ||
try { | try { | ||
storageKeys = Object.keys( localStorage ); | storageKeys = Object.keys(localStorage); | ||
} catch ( e ) {} | } catch (e) {} | ||
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')); | ||
} catch ( e ) {} | } catch (e) {} | ||
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,100: | Line 1,101: | ||
try { | try { | ||
localStorage.removeItem( keyPrefix + '-content' ); | localStorage.removeItem(keyPrefix + '-content'); | ||
localStorage.removeItem( keyPrefix + '-timestamp' ); | localStorage.removeItem(keyPrefix + '-timestamp'); | ||
localStorage.removeItem( keyPrefix + '-version' ); | localStorage.removeItem(keyPrefix + '-version'); | ||
} catch ( e ) {} | } catch (e) {} | ||
} ); | }); | ||
} | } | ||
Line 1,114: | Line 1,115: | ||
* @return {string} The JSON string after any comments have been removed. | * @return {string} The JSON string after any comments have been removed. | ||
*/ | */ | ||
function stripComments( json ) { | function stripComments(json) { | ||
json = json | json = json | ||
.trim() | .trim() | ||
.replace( /\/\*[\s\S]*?\*\//g, '' ); | .replace(/\/\*[\s\S]*?\*\//g, ''); | ||
return json; | return json; | ||
} | } | ||
Line 1,128: | Line 1,129: | ||
* @param {number} cacheVersion Cache version requested by the loading script. | * @param {number} cacheVersion Cache version requested by the loading script. | ||
*/ | */ | ||
function saveToCache( name, json, cacheVersion ) { | function saveToCache(name, json, cacheVersion) { | ||
/* | /* | ||
* @var {string} keyPrefix | * @var {string} keyPrefix | ||
Line 1,135: | Line 1,136: | ||
/* Don't cache empty JSON */ | /* Don't cache empty JSON */ | ||
if ( Object.keys( json ).length === 0 ) { | if (Object.keys(json).length === 0) { | ||
return; | return; | ||
} | } | ||
try { | try { | ||
localStorage.setItem( keyPrefix + '-content', JSON.stringify( json ) ); | localStorage.setItem(keyPrefix + '-content', JSON.stringify(json)); | ||
localStorage.setItem( keyPrefix + '-timestamp', now ); | localStorage.setItem(keyPrefix + '-timestamp', now); | ||
localStorage.setItem( keyPrefix + '-version', cacheVersion || 0 ); | localStorage.setItem(keyPrefix + '-version', cacheVersion || 0); | ||
} catch ( e ) {} | } catch (e) {} | ||
} | } | ||
Line 1,154: | Line 1,155: | ||
* @return {object} The resulting i18n object. | * @return {object} The resulting i18n object. | ||
*/ | */ | ||
function parseMessagesToObject( name, res, options ) { | function parseMessagesToObject(name, res, options) { | ||
var json = {}, | var json = {}, | ||
obj, | obj, | ||
Line 1,161: | Line 1,162: | ||
/* Handle parse errors gracefully */ | /* Handle parse errors gracefully */ | ||
try { | try { | ||
res = stripComments( res ); | res = stripComments(res); | ||
json = JSON.parse( res ); | json = JSON.parse(res); | ||
} catch ( e ) { | } catch (e) { | ||
msg = e.message; | msg = e.message; | ||
if ( msg === 'Unexpected end of JSON input' ) { | if (msg === 'Unexpected end of JSON input') { | ||
msg += '. This may be caused by a non-existent i18n.json page.'; | msg += '. This may be caused by a non-existent i18n.json page.'; | ||
} | } | ||
console.warn( '[I18n-js] SyntaxError in messages: ' + msg ); | console.warn('[I18n-js] SyntaxError in messages: ' + msg); | ||
} | } | ||
Line 1,177: | Line 1,178: | ||
!options.loadedFromCache && | !options.loadedFromCache && | ||
options.cacheAll !== true | options.cacheAll !== true | ||
) { | |||
json = optimiseMessages( name, json, options ); | json = optimiseMessages(name, json, options); | ||
} | } | ||
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; | ||
if ( !options.loadedFromCache ) { | if (!options.loadedFromCache) { | ||
saveToCache( name, json, options.cacheVersion ); | saveToCache(name, json, options.cacheVersion); | ||
} | } | ||
Line 1,199: | Line 1,200: | ||
* @param {object} options Options set by the loading script. | * @param {object} options Options set by the loading script. | ||
*/ | */ | ||
function loadFromCache( name, options ) { | function loadFromCache(name, options) { | ||
var keyPrefix = cachePrefix + name, | var keyPrefix = cachePrefix + name, | ||
cacheContent, | cacheContent, | ||
Line 1,205: | Line 1,206: | ||
try { | try { | ||
cacheContent = localStorage.getItem( keyPrefix + '-content' ); | cacheContent = localStorage.getItem(keyPrefix + '-content'); | ||
cacheVersion = Number( localStorage.getItem( keyPrefix + '-version' ) ); | cacheVersion = Number(localStorage.getItem(keyPrefix + '-version')); | ||
} 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; | ||
parseMessagesToObject( name, cacheContent, options ); | parseMessagesToObject(name, cacheContent, options); | ||
} | } | ||
} | } | ||
Line 1,230: | Line 1,231: | ||
* - {string} page: Set other format of the full page name for the i18n JSON. | * - {string} page: Set other format of the full page name for the i18n JSON. | ||
* Use $1 for the placeholder of name. | * Use $1 for the placeholder of name. | ||
* - {(array|boolean)} cacheAll: Either an array of message names for which translations should not be optimised, | * - {(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. | * - {number} cacheVersion: Minimum cache version requested by the loading script. | ||
* - {string} language: Set a default language for the script to use, instead of wgUserLanguage. | * - {string} language: Set a default language for the script to use, instead of wgUserLanguage. | ||
Line 1,238: | Line 1,239: | ||
* @return {object} A jQuery.Deferred instance. | * @return {object} A jQuery.Deferred instance. | ||
*/ | */ | ||
function loadMessages( name, options ) { | function loadMessages(name, options) { | ||
/* | /* | ||
* @var {object} deferred | * @var {object} deferred | ||
Line 1,247: | Line 1,248: | ||
*/ | */ | ||
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( | 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-]+))?|(([a-z-]+)\.wikipedia\.org\/w))\/api\.php)$' */ | ||
'^(https:\/\/(([a-z0-9-]+)\.fandom\.com(?:\/([a-z-]+))?)\/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 ) ) { | if (options.apiEndpoint && apiEndpointRgx.test(options.apiEndpoint)) { | ||
options.apiEndpoint = options.apiEndpoint; | options.apiEndpoint = options.apiEndpoint; | ||
} else { | } else { | ||
options.apiEndpoint = apiEndpoint; | options.apiEndpoint = apiEndpoint; | ||
} | } | ||
options.page = ( options.page && options.page.replace( /\$1/g, name ) ) || page; | 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; | ||
options.useCache = ( options.noCache || conf.debug ) !== true; | options.useCache = (options.noCache || conf.debug) !== true; | ||
if ( options.useCache ) { | if (options.useCache) { | ||
loadFromCache( name, options ); | loadFromCache(name, options); | ||
if ( cacheIsSuitable( name, options ) ) { | if (cacheIsSuitable(name, options)) { | ||
return deferred.resolve( cache[ name ] ); | return deferred.resolve(cache[name]); | ||
} | } | ||
} | } | ||
Line 1,283: | Line 1,284: | ||
* 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]); | ||
page = name.slice( customSource[ 0 ].length ); | page = name.slice(customSource[0].length); | ||
// adjust endpoint when loading from interlanguage wiki | // adjust endpoint when loading from interlanguage wiki | ||
if ( customSource[ 1 ] ) { | if (customSource[1]) { | ||
apiEndpoint = apiEndpoint.replace( | apiEndpoint = apiEndpoint.replace( | ||
/api\.php$/, | /api\.php$/, | ||
customSource[ 1 ] + '/$&' | customSource[1] + '/$&' | ||
); | |||
} | } | ||
} | } | ||
Line 1,314: | Line 1,315: | ||
* 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, { | ||
data: params, | data: params, | ||
} ).always( function ( data ) { | }).always(function (data) { | ||
var res = '', | var res = '', | ||
revisionData = data.query && data.query.pages[ data.query.pageids[ 0 ] ].revisions; | revisionData = data.query && data.query.pages[data.query.pageids[0]].revisions; | ||
if ( revisionData ) { | if (revisionData) { | ||
res = revisionData[ 0 ][ '*' ]; | res = revisionData[0]['*']; | ||
} | } | ||
deferred.resolve( parseMessagesToObject( name, res, options ) ); | deferred.resolve(parseMessagesToObject(name, res, options)); | ||
} ); | }); | ||
} ); | }); | ||
return deferred; | return deferred; | ||
Line 1,333: | Line 1,334: | ||
/* 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, | ||
Line 1,349: | Line 1,350: | ||
_fallbacks: fallbacks, | _fallbacks: fallbacks, | ||
_cache: cache | _cache: cache | ||
} ); | }); | ||
/* Initialise overrides object */ | /* Initialise overrides object */ | ||
Line 1,360: | Line 1,361: | ||
* Alternatively, use $.getScript (or mw.loader) and use the returned promise | * 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); | ||
/* Tidy the localStorage cache of old entries */ | /* Tidy the localStorage cache of old entries */ | ||
removeOldCacheEntries(); | removeOldCacheEntries(); | ||
} ( this, jQuery, mediaWiki ) ); | } (this, jQuery, mediaWiki)); |
Revision as of 10:59, 23 May 2022
/* <nowiki>
* Library for accessing i18n messages for use in Dev Wiki scripts.
* See [[I18n-js]] for documentation.
*
* @author Cqm <https://dev.fandom.com/User:Cqm>
* @author OneTwoThreeFall <https://dev.fandom.com/User:OneTwoThreeFall>
*
* @version 0.6.7
*
* @notes Also used by SOAP 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
* includes a check to prevent double loading. This can make it painful to test from your
* JS console. To get around this, add ?usesitejs=0&useuserjs=0 to your URL.
*/
/* 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 || {};
window.dev.i18n = window.dev.i18n || {};
/* Prevent double loading and loss of cache */
if (window.dev.i18n.loadMessages !== undefined) {
return;
}
/*
* 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([
'debug',
'wgContentLanguage',
'wgPageContentLanguage',
'wgUserLanguage',
'wgUserVariant'
]),
/*
* @var {number} Current time in milliseconds, used to set and check cache age.
*/
now = Date.now(),
/*
* @var {number} Length of one day in milliseconds, used in cache age calculations.
*/
oneDay = 1000 * 60 * 60 * 24,
/*
* @var {string} Prefix used for localStorage keys that contain i18n-js cache data
*/
cachePrefix = 'i18n-cache-',
/*
* @var {boolean} Whether a fallback loop warning been shown
*/
warnedAboutFallbackLoop = false,
/*
* @var {object} Cache of loaded I18n instances.
*/
cache = {},
/*
* Initial overrides object, initialised below with the i18n global variable.
* Allows end-users to override specific messages.
* See documentation for how to use.
*
* @var {(null|object)} overrides
*/
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',
'zh-min-n': 'nan',
'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.
*
* 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/`,
* 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 = {
'ab': ['ru'],
'abs': ['id'],
'ace': ['id'],
'ady': ['ady-cyrl'],
'aeb': ['aeb-arab'],
'aeb-arab': ['ar'],
'aln': ['sq'],
'alt': ['ru'],
'ami': ['zh-tw', 'zh-hant', 'zh', 'zh-hans'],
'an': ['es'],
'anp': ['hi'],
'arn': ['es'],
'arq': ['ar'],
'ary': ['ar'],
'arz': ['ar'],
'ast': ['es'],
'atj': ['fr'],
'av': ['ru'],
'avk': ['fr', 'es', 'ru'],
'awa': ['hi'],
'ay': ['es'],
'azb': ['fa'],
'ba': ['ru'],
'ban': ['id'],
'ban-bali': ['ban'],
'bar': ['de'],
'bbc': ['bbc-latn'],
'bbc-latn': ['id'],
'bcc': ['fa'],
'bci': ['fr'],
'be-tarask': ['be'],
'bgn': ['fa'],
'bh': ['bho'],
'bi': ['en'],
'bjn': ['id'],
'blk': ['my'],
'bm': ['fr'],
'bpy': ['bn'],
'bqi': ['fa'],
'br': ['fr'],
'btm': ['id'],
'bug': ['id'],
'bxr': ['ru'],
'ca': ['oc'],
'cbk-zam': ['es'],
'cdo': ['nan', 'zh-hant', 'zh', 'zh-hans'],
'ce': ['ru'],
'co': ['it'],
'crh': ['crh-latn'],
'crh-cyrl': ['ru'],
'cs': ['sk'],
'csb': ['pl'],
'cv': ['ru'],
'de-at': ['de'],
'de-ch': ['de'],
'de-formal': ['de'],
'dsb': ['hsb', 'de'],
'dtp': ['ms'],
'dty': ['ne'],
'egl': ['it'],
'eml': ['it'],
'es-formal': ['es'],
'ext': ['es'],
'ff': ['fr'],
'fit': ['fi'],
'fon': ['fr'],
'frc': ['fr'],
'frp': ['fr'],
'frr': ['de'],
'fur': ['it'],
'gag': ['tr'],
'gan': ['gan-hant', 'gan-hans', 'zh-hant', 'zh', 'zh-hans'],
'gan-hans': ['gan', 'gan-hant', 'zh-hans', 'zh', 'zh-hant'],
'gan-hant': ['gan', 'gan-hans', 'zh-hant', 'zh', 'zh-hans'],
'gcr': ['fr'],
'gl': ['pt'],
'gld': ['ru'],
'glk': ['fa'],
'gn': ['es'],
'gom': ['gom-deva'],
'gom-deva': ['hi'],
'gor': ['id'],
'gsw': ['de'],
'guc': ['es'],
'hak': ['zh-hant', 'zh', 'zh-hans'],
'hif': ['hif-latn'],
'hrx': ['de'],
'hsb': ['dsb', 'de'],
'hsn': ['zh-cn', 'zh-hans', 'zh', 'zh-hant'],
'ht': ['fr'],
'hu-formal': ['hu'],
'hyw': ['hy'],
'ii': ['zh-cn', 'zh-hans', 'zh', 'zh-hant'],
'inh': ['ru'],
'io': ['eo'],
'iu': ['ike-cans'],
'jam': ['en'],
'jut': ['da'],
'jv': ['id'],
'kaa': ['kk-latn', 'kk-cyrl'],
'kab': ['fr'],
'kbd': ['kbd-cyrl'],
'kbp': ['fr'],
'kea': ['pt'],
'khw': ['ur'],
'kiu': ['tr'],
'kjp': ['my'],
'kk': ['kk-cyrl'],
'kk-arab': ['kk-cyrl'],
'kk-cn': ['kk-arab', 'kk-cyrl'],
'kk-kz': ['kk-cyrl'],
'kk-latn': ['kk-cyrl'],
'kk-tr': ['kk-latn', 'kk-cyrl'],
'kl': ['da'],
'koi': ['ru'],
'ko-kp': ['ko'],
'krc': ['ru'],
'krl': ['fi'],
'ks': ['ks-arab'],
'ksh': ['de'],
'ksw': ['my'],
'ku': ['ku-latn'],
'kum': ['ru'],
'ku-arab': ['ckb'],
'kv': ['ru'],
'lad': ['es'],
'lb': ['de'],
'lbe': ['ru'],
'lez': ['ru', 'az'],
'li': ['nl'],
'lij': ['it'],
'liv': ['et'],
'lki': ['fa'],
'lld': ['it', 'rm', 'fur'],
'lmo': ['pms', 'eml', 'lij', 'vec', 'it'],
'ln': ['fr'],
'lrc': ['fa'],
'ltg': ['lv'],
'luz': ['fa'],
'lzh': ['zh-hant', 'zh', 'zh-hans'],
'lzz': ['tr'],
'mad': ['id'],
'mai': ['hi'],
'map-bms': ['jv', 'id'],
'mdf': ['myv', 'ru'],
'mg': ['fr'],
'mhr': ['mrj', 'ru'],
'min': ['id'],
'mnw': ['my'],
'mo': ['ro'],
'mrj': ['mhr', 'ru'],
'ms-arab': ['ms'],
'mwl': ['pt'],
'myv': ['mdf', 'ru'],
'mzn': ['fa'],
'nah': ['es'],
'nan': ['cdo', 'zh-hant', 'zh', 'zh-hans'],
'nap': ['it'],
'nb': ['nn'],
'nds': ['de'],
'nds-nl': ['nl'],
'nia': ['id'],
'nl-informal': ['nl'],
'nn': ['nb'],
'nrm': ['nrf', 'fr'],
'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('-');
}
/*
* Log a warning message to the browser console if the language fallback chain is
* 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 {string[]} fallbackChain Array of languages involved in the loop.
*/
function warnOnFallbackLoop(lang, fallbackChain) {
if (warnedAboutFallbackLoop) {
return;
}
warnedAboutFallbackLoop = true;
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(', '));
}
/*
* Get a translation of a message from the messages object in the
* requested language.
*
* @param {object} messages The message object to look translations up in.
* @param {string} msgName The name of the message to get.
* @param {string} lang The language to get the message in.
* @param {string[]} fallbackChain Array of languages that have already been checked.
* Used to detect if the fallback chain is looping.
* @return {(string|boolean)} The requested translation or `false` if no message could be found.
*/
function getMsg(messages, msgName, lang, fallbackChain) {
if (deprecatedCodes[lang]) {
return getMsg(messages, msgName, deprecatedCodes[lang], fallbackChain);
}
if (messages[lang] && messages[lang][msgName]) {
return messages[lang][msgName];
}
if (!fallbackChain) {
fallbackChain = [];
}
/*
* @var {string} fallbackLang
*/
for (var fallbackLang of fallbacks[lang]) {
if (messages[fallbackLang] && messages[fallbackLang][msgName]) {
return messages[fallbackLang][msgName];
}
if (fallbackChain.indexOf(fallbackLang) !== -1) {
/*
* Duplicated language code in fallback list
* Try to find next fallback language from list
*/
warnOnFallbackLoop(fallbackLang, fallbackChain);
continue;
}
fallbackChain.push(fallbackLang);
}
/* No more languages in fallback list - switch to English */
if (messages.en && messages.en[msgName]) {
return messages.en[msgName];
}
return false;
}
/*
* Substitute arguments into the string, where arguments are represented
* as $n where n > 0.
*
* @param {string} message The message to substitute arguments into
* @param {array} arguments The arguments to substitute in.
* @return {string} The resulting message.
*/
function handleArgs(message, args) {
args.forEach(function (elem, index) {
/*
* @var {RegExp} rgx
*/
var rgx = new RegExp('\\$' + (index + 1), 'g');
message = message.replace(rgx, elem);
});
return message;
}
/*
* Generate a HTML link using the supplied parameters.
*
* @param {string} href The href of the link which will be converted to
* '/wiki/href'.
* @param {string} text The text and title of the link. If this is not supplied, it
* will default to href.
* @param {boolean} hasProtocol True if the href parameter already includes the
* protocol (i.e. it begins with 'http://', 'https://', or '//').
* @return {string} The generated link.
*/
function makeLink(href, text, hasProtocol) {
text = text || href;
href = hasProtocol ? href : mw.util.getUrl(href);
text = mw.html.escape(text);
href = mw.html.escape(href);
return '<a href="' + href + '" title="' + text + '">' + text + '</a>';
}
/*
* Allow basic inline HTML tags in wikitext.does not support <a> as that's handled by the
* wikitext links instead.
*
* Supports the following tags:
* - <i>
* - <b>
* - <s>
* - <br>
* - <em>
* - <strong>
* - <span>
*
* Supports the following tag attributes:
* - title
* - style
* - class
*
* @param html
* @return The sanitised HTML code.
*/
function sanitiseHtml(html) {
/*
* @var context
*/
var context = document.implementation.createHTMLDocument(''),
$html = $.parseHTML(html, /* document */ context, /* keepscripts */ false),
$div = $('<div>', context).append($html),
allowedAttrs = [
'title',
'style',
'class'
],
allowedTags = [
'i',
'b',
's',
'br',
'em',
'strong',
'span',
];
$div.find('*').each(function () {
var $this = $(this),
tagname = $this.prop('tagName').toLowerCase(),
attrs,
array,
style;
if (allowedTags.indexOf(tagname) === -1) {
mw.log('[I18n-js] Disallowed tag in message: ' + tagname);
$this.remove();
return;
}
attrs = $this.prop('attributes');
array = Array.prototype.slice.call(attrs);
array.forEach(function (attr) {
if (allowedAttrs.indexOf(attr.name) === -1) {
mw.log('[I18n-js] Disallowed attribute in message: ' + attr.name + ', tag: ' + tagname);
$this.removeAttr(attr.name);
return;
}
/* Make sure there's nothing nasty in style attributes */
if (attr.name === 'style') {
style = $this.attr('style');
if (style.indexOf('url(') > -1) {
mw.log('[I18n-js] Disallowed url() in style attribute');
$this.removeAttr('style');
/* https://phabricator.wikimedia.org/T208881 */
} else if (style.indexOf('var(') > -1) {
mw.log('[I18n-js] Disallowed var() in style attribute');
$this.removeAttr('style');
}
}
});
});
return $div.prop('innerHTML');
}
/*
* Parse some basic wikitext into HTML. Also supports basic inline HTML tags.
*
* Will process:
* - [url text]
* - [[pagename]]
* - [[pagename|text]]
* - {{PLURAL:count|singular|plural}}
* - {{GENDER:gender|masculine|feminine|neutral}}
*
* @param {string} message The message to process.
* @return {string} The resulting string.
*/
function parse(message) {
/* [url text] -> [$1 $2] */
var urlRgx = /\[((?:https?:)?\/\/.+?) (.+?)\]/g,
/* [[pagename]] -> [[$1]] */
simplePageRgx = /\[\[([^|]*?)\]\]/g,
/* [[pagename|text]] -> [[$1|$2]] */
pageWithTextRgx = /\[\[(.+?)\|(.+?)\]\]/g,
/* {{PLURAL:count|singular|plural}} -> {{PLURAL:$1|$2}} */
pluralRgx = /\{\{PLURAL:(\d+)\|(.+?)\}\}/gi,
/* {{GENDER:gender|masculine|feminine|neutral}} -> {{GENDER:$1|$2}} */
genderRgx = /\{\{GENDER:([^|]+)\|(.+?)\}\}/gi;
if (message.indexOf('<') > -1) {
message = sanitiseHtml(message);
}
return message
.replace(urlRgx, function (_match, href, text) {
return makeLink(href, text, true);
})
.replace(simplePageRgx, function (_match, href) {
return makeLink(href);
})
.replace(pageWithTextRgx, function (_match, href, text) {
return makeLink(href, text);
})
.replace(pluralRgx, function (_match, count, forms) {
return mw.language.convertPlural(Number(count), forms.split('|'));
})
.replace(genderRgx, function (_match, gender, forms) {
return mw.language.gender(gender, forms.split('|'));
});
}
/*
* Create a new Message instance.
*
* @param {object} messages The message object to look translations up in.
* @param {string} lang The language to get the message in.
* @param {array} args Any arguments to substitute into the message, [0] is message name.
* @param {string} name The name of the script the messages are for.
* @return
*/
function message(messages, lang, args, name) {
if (!args.length) {
return;
}
/*
* @var msgName
* @var {string} descriptiveMsgName
* @var {object} msg
* @var {boolean} msgExists
*/
var msgName = args.shift(),
descriptiveMsgName = 'i18njs-' + name + '-' + msgName,
msg = getMsg(messages, msgName, lang),
msgExists = msg !== false;
if (!msgExists) {
/* use name wrapped in < > for missing message, per MediaWiki convention */
msg = '<' + descriptiveMsgName + '>';
}
if (conf.wgUserLanguage === 'qqx' && msgExists) {
/* https://www.mediawiki.org/wiki/Help:System_message#Finding_messages_and_documentation */
msg = '(' + descriptiveMsgName + ')';
} else if (overrides[name] && overrides[name][msgName]) {
/* if the message has been overridden, use that without checking the language */
msg = overrides[name][msgName];
msgExists = true;
}
if (args.length) {
msg = handleArgs(msg, args);
}
return {
/*
* @return {boolean} Representing whether the message exists.
*/
exists: msgExists,
/*
* Parse wikitext links in the message and return the result.
*
* @return {string} The resulting string.
*/
parse: function () {
/*
* Skip parsing if the message wasn't found; otherwise
* the sanitisation will mess with it.
*/
if (!this.exists) {
return this.escape();
}
return parse(msg);
},
/*
* Escape any HTML in the message and return the result.
*
* @return {string} The resulting string.
*/
escape: function () {
return mw.html.escape(msg);
},
/*
* Return the message as is.
*
* @return {string} The resulting string.
*/
plain: function () {
return msg;
}
};
}
/*
* Create a new i18n object.
*
* @param {object} messages The message object to look translations up in.
* @param {string} name The name of the script the messages are for.
* @param {object} options Options set by the loading script.
* @return {object}
*/
function i18n(messages, name, options) {
var defaultLang = options.language,
tempLang = null;
return {
/*
* Set the default language.
*
* @deprecated since v0.6 (2020-08-25), no longer supported.
*/
useLang: function () {
console.warn('[I18n-js] “useLang()” is no longer supported by I18n-js (used in “' + name + '”) - using user language.');
this.useUserLang();
},
/*
* Set the language for the next msg call.
*
* @param {string} lang The language code to use for the next `msg` call.
*
* @return {object} The current object for use in chaining.
*/
inLang: function (lang) {
if (!options.cacheAll) {
console.warn('[I18n-js] “inLang()” is not supported without configuring `options.cacheAll` (used in “' + name + '”) - using user language.');
lang = options.language;
}
tempLang = lang;
return this;
},
/*
* Set the default language to the content language.
*/
useContentLang: function () {
defaultLang = conf.wgContentLanguage;
},
/*
* Set the language for the next `msg` call to the content language.
*
* @return {object} The current object for use in chaining.
*/
inContentLang: function () {
tempLang = conf.wgContentLanguage;
return this;
},
/*
* 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;
},
/*
* Set the default language to the user's language.
*/
useUserLang: function () {
defaultLang = options.language;
},
/*
* Set the language for the next msg call to the user's language.
*
* @return {object} The current object for use in chaining.
*/
inUserLang: function () {
tempLang = options.language;
return this;
},
/*
* Create a new instance of Message.
*
* @return {object}
*/
msg: function () {
var args = Array.prototype.slice.call(arguments),
lang = defaultLang;
if (tempLang !== null) {
lang = tempLang;
tempLang = null;
}
return message(messages, lang, args, name);
},
/*
* For accessing the raw messages.
* Scripts should not rely on it or any of its properties existing.
*/
_messages: messages
};
}
/*
* Preprocess each message's fallback chain for the user and content languages.
* 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 {object} messages The message object to look translations up in.
* @param {object} options Options set by the loading script.
*/
function optimiseMessages(name, messages, options) {
var existingLangs = cache[name] && cache[name]._messages._isOptimised,
langs = [options.language],
msgKeys = Object.keys(messages.en || {}),
optimised = {};
if (!msgKeys.length) {
/* No English messages, don't bother optimising */
return messages;
}
/*
* @var addMsgsForLanguage
*/
var addMsgsForLanguage = function (lang) {
if (optimised[lang]) {
/* Language already exists */
return;
}
optimised[lang] = {};
msgKeys.forEach(function (msgName) {
/*
* @var msg
*/
var msg = getMsg(messages, msgName, lang);
if (msg !== false) {
optimised[lang][msgName] = msg;
}
});
};
if (langs.indexOf(conf.wgContentLanguage) === -1) {
langs.push(conf.wgContentLanguage);
}
/*
* If cache exists and is optimised, preserve existing languages.
* This allows an optimised cache even when using different
* language wikis on same domain (i.e. sharing same cache).
*/
if (existingLangs) {
existingLangs.forEach(function (lang) {
if (langs.indexOf(lang) === -1) {
langs.push(lang);
}
});
}
langs.forEach(addMsgsForLanguage);
/*
* `cacheAll` is an array of message names for which translations
* should not be optimised - save all translations of these messages
*/
if (Array.isArray(options.cacheAll)) {
msgKeys = options.cacheAll;
Object.keys(messages).forEach(addMsgsForLanguage);
}
optimised._isOptimised = langs;
return optimised;
}
/*
* Check that the cache for a script exists and, if optimised, contains the
* necessary languages.
*
* @param {string} name The name of the script to check for.
* @param {object} options Options set by the loading script.
* @return {boolean} Whether the cache should be used.
*/
function cacheIsSuitable(name, options) {
var messages = cache[name] && cache[name]._messages;
/* Nothing in cache */
if (!messages) {
return false;
}
/*
* Optimised messages missing user or content language.
* We'll need to load from server in this case.
*/
if (
messages._isOptimised &&
!(messages[options.language] && messages[conf.wgContentLanguage])
) {
return false;
}
return true;
}
/*
* Remove out-of-date entries in the i18n cache (those older than two days).
*
* This can never be perfect: it will only work on wikis that are visited.
*/
function removeOldCacheEntries() {
var isCacheKey = new RegExp('^(' + cachePrefix + '.+)-content$'),
storageKeys = [];
try {
storageKeys = Object.keys(localStorage);
} catch (e) {}
storageKeys.filter(function (key) {
return isCacheKey.test(key);
}).forEach(function (key) {
var keyPrefix = key.match(isCacheKey)[1],
cacheTimestamp;
try {
cacheTimestamp = Number(localStorage.getItem(keyPrefix + '-timestamp'));
} catch (e) {}
if (now - cacheTimestamp < oneDay * 2) {
/* Cached within last two days, keep it */
return;
}
try {
localStorage.removeItem(keyPrefix + '-content');
localStorage.removeItem(keyPrefix + '-timestamp');
localStorage.removeItem(keyPrefix + '-version');
} catch (e) {}
});
}
/*
* Strip block comments from a JSON string which are illegal under the JSON spec.
* This is a bit basic, so will remove comments inside strings too.
*
* @param {string} json The JSON string.
* @return {string} The JSON string after any comments have been removed.
*/
function stripComments(json) {
json = json
.trim()
.replace(/\/\*[\s\S]*?\*\//g, '');
return json;
}
/*
* Save messages string to local storage for caching.
*
* @param {string} name The name of the script the messages are for.
* @param {object} json The JSON object.
* @param {number} cacheVersion Cache version requested by the loading script.
*/
function saveToCache(name, json, cacheVersion) {
/*
* @var {string} keyPrefix
*/
var keyPrefix = cachePrefix + name;
/* Don't cache empty JSON */
if (Object.keys(json).length === 0) {
return;
}
try {
localStorage.setItem(keyPrefix + '-content', JSON.stringify(json));
localStorage.setItem(keyPrefix + '-timestamp', now);
localStorage.setItem(keyPrefix + '-version', cacheVersion || 0);
} catch (e) {}
}
/*
* Parse JSON string loaded from page and create an i18n object.
*
* @param {string} name The name of the script the messages are for.
* @param {string} res The JSON string.
* @param {object} options Options set by the loading script.
* @return {object} The resulting i18n object.
*/
function parseMessagesToObject(name, res, options) {
var json = {},
obj,
msg;
/* Handle parse errors gracefully */
try {
res = stripComments(res);
json = JSON.parse(res);
} catch (e) {
msg = e.message;
if (msg === 'Unexpected end of JSON input') {
msg += '. This may be caused by a non-existent i18n.json page.';
}
console.warn('[I18n-js] SyntaxError in messages: ' + msg);
}
if (
options.useCache &&
!options.loadedFromCache &&
options.cacheAll !== true
) {
json = optimiseMessages(name, json, options);
}
obj = i18n(json, name, options);
/* Cache the result in case it's used multiple times */
cache[name] = obj;
if (!options.loadedFromCache) {
saveToCache(name, json, options.cacheVersion);
}
return obj;
}
/*
* 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 {object} options Options set by the loading script.
*/
function loadFromCache(name, options) {
var keyPrefix = cachePrefix + name,
cacheContent,
cacheVersion;
try {
cacheContent = localStorage.getItem(keyPrefix + '-content');
cacheVersion = Number(localStorage.getItem(keyPrefix + '-version'));
} catch (e) {}
/* Cache exists, and its version is greater than or equal to requested version */
if (cacheContent && cacheVersion >= options.cacheVersion) {
options.loadedFromCache = true;
parseMessagesToObject(name, cacheContent, options);
}
}
/*
* Load messages stored as JSON on a page.
*
* @param {string} name The name of the script the messages are for. This will be
* used to get messages from
* https://dev.fandom.com/wiki/MediaWiki:Custom-name/i18n.json.
* Use `u:<subdomain>` or `u:<language-path>.<subdomain>` to set other Fandom
* wikis as the source.
* @param {object} options Options set by the loading script:
* - {string} apiEndpoint: Use `u:<subdomain>` or `u:<language-path>.<subdomain>`
* to set other sites as the API endpoint of the source. Currently only
* 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.
*/
function loadMessages(name, options) {
/*
* @var {object} deferred
* @var {string} apiEndpoint
* @var {RegExp} apiEndpointRgx
* @var {string} page
* @var {object} params
*/
var deferred = $.Deferred(),
customSource = name.match(/^u:(?:([a-z-]+)\.)?([a-z0-9-]+):/),
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',
params;
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.language = options.language || conf.wgUserLanguage;
options.useCache = (options.noCache || conf.debug) !== true;
if (options.useCache) {
loadFromCache(name, options);
if (cacheIsSuitable(name, options)) {
return deferred.resolve(cache[name]);
}
}
/* Cache isn't suitable - loading from server */
options.loadedFromCache = false;
/*
* Allow custom i18n pages to be specified on other wikis.
* Mainly for SOAP Wiki to keep their own JSON file.
* Note this only supports loading from wikis on fandom.com.
*/
if (customSource) {
apiEndpoint = apiEndpoint.replace('dev', customSource[2]);
page = name.slice(customSource[0].length);
// adjust endpoint when loading from interlanguage wiki
if (customSource[1]) {
apiEndpoint = apiEndpoint.replace(
/api\.php$/,
customSource[1] + '/$&'
);
}
}
params = {
action: 'query',
format: 'json',
prop: 'revisions',
rvprop: 'content',
titles: page,
indexpageids: 1,
origin: '*',
// Cache results for 5 minutes in CDN and browser
maxage: 300,
smaxage: 300
};
/*
* 'site' and 'user' are dependencies so end-users can set overrides in their local JS
* 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.
*/
mw.loader.using(['mediawiki.language', 'mediawiki.util'/*, 'site', 'user'*/], function () {
$.ajax(apiEndpoint, {
data: params,
}).always(function (data) {
var res = '',
revisionData = data.query && data.query.pages[data.query.pageids[0]].revisions;
if (revisionData) {
res = revisionData[0]['*'];
}
deferred.resolve(parseMessagesToObject(name, res, options));
});
});
return deferred;
}
/* Expose under the dev global */
window.dev.i18n = $.extend(window.dev.i18n, {
loadMessages: loadMessages,
/*
* "Hidden" functions to allow testing and debugging
* they may be changed or removed without warning.
* Scripts should not rely on these existing or their output being in any particular format.
*/
_bcp47: bcp47,
_stripComments: stripComments,
_saveToCache: saveToCache,
_getMsg: getMsg,
_handleArgs: handleArgs,
_parse: parse,
_fallbacks: fallbacks,
_cache: cache
});
/* Initialise overrides object */
window.dev.i18n.overrides = window.dev.i18n.overrides || {};
overrides = window.dev.i18n.overrides;
/*
* Fire an event on load
*
* Alternatively, use $.getScript (or mw.loader) and use the returned promise
*/
mw.hook('dev.i18n').fire(window.dev.i18n);
/* Tidy the localStorage cache of old entries */
removeOldCacheEntries();
} (this, jQuery, mediaWiki));