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 (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. | * @version 0.6.6 | ||
* | * | ||
* @notes Also used by | * @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 | ||
* | * 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 */ | /*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, | |||
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) { | (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 | ||
if (window.dev.i18n.loadMessages !== undefined) { | if (window.dev.i18n.loadMessages !== undefined) { | ||
return; | return; | ||
} | } | ||
/* | |||
* Cache of mw config variables. | |||
*/ | |||
var conf = mw.config.get([ | var conf = mw.config.get([ | ||
'debug', | |||
'wgContentLanguage', | |||
'wgUserLanguage' | |||
]), | |||
/* | /* | ||
* | * Current time in milliseconds, used to set and check cache age. | ||
*/ | */ | ||
now = Date.now(), | now = Date.now(), | ||
/* | /* | ||
* | * Length of one day in milliseconds, used in cache age calculations. | ||
*/ | */ | ||
oneDay = 1000 * 60 * 60 * 24, | oneDay = 1000 * 60 * 60 * 24, | ||
/* | /* | ||
* | * Prefix used for localStorage keys that contain i18n-js cache data. | ||
*/ | */ | ||
cachePrefix = 'i18n-cache-', | cachePrefix = 'i18n-cache-', | ||
/* | /* | ||
* | * Has a fallback loop warning been shown? | ||
*/ | */ | ||
warnedAboutFallbackLoop = false, | warnedAboutFallbackLoop = false, | ||
/* | /* | ||
* | * 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. | ||
*/ | */ | ||
overrides = null, | overrides = null, | ||
/* | /* | ||
* 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 | |||
* | * | ||
* To generate this, use `$ grep -R "fallback =" /path/to/messages/`, | * To generate this, use `$ grep -R "fallback =" /path/to/messages/`, | ||
* pipe the result to a text file and format the result. | * pipe the result to a text file and format the result. | ||
*/ | */ | ||
fallbacks = { | fallbacks = { | ||
'ab': | 'ab': 'ru', | ||
' | 'ace': 'id', | ||
' | 'aln': 'sq', | ||
' | 'als': 'gsw', | ||
' | 'an': 'es', | ||
' | 'anp': 'hi', | ||
' | 'arn': 'es', | ||
' | 'arz': 'ar', | ||
' | 'av': 'ru', | ||
' | 'ay': 'es', | ||
' | 'ba': 'ru', | ||
' | 'bar': 'de', | ||
' | 'bat-smg': 'sgs', | ||
' | 'bcc': 'fa', | ||
' | 'be-x-old': 'be-tarask', | ||
' | 'bh': 'bho', | ||
' | 'bjn': 'id', | ||
' | 'bm': 'fr', | ||
' | 'bpy': 'bn', | ||
' | 'bqi': 'fa', | ||
' | 'bug': 'id', | ||
' | 'cbk-zam': 'es', | ||
' | 'ce': 'ru', | ||
' | 'ckb': 'ckb-arab', | ||
' | 'crh': 'crh-latn', | ||
' | 'crh-cyrl': 'ru', | ||
' | 'csb': 'pl', | ||
' | 'cv': 'ru', | ||
' | 'de-at': 'de', | ||
' | 'de-ch': 'de', | ||
' | 'de-formal': 'de', | ||
' | 'dsb': 'de', | ||
' | 'dtp': 'ms', | ||
' | 'eml': 'it', | ||
' | 'ff': 'fr', | ||
' | 'fiu-vro': 'vro', | ||
' | 'frc': 'fr', | ||
' | 'frp': 'fr', | ||
' | 'frr': 'de', | ||
' | 'fur': 'it', | ||
' | 'gag': 'tr', | ||
' | 'gan': 'gan-hant', | ||
' | 'gan-hans': 'zh-hans', | ||
' | 'gan-hant': 'zh-hant', | ||
' | 'gl': 'pt', | ||
' | 'glk': 'fa', | ||
' | 'gn': 'es', | ||
' | 'gsw': 'de', | ||
' | 'hif': 'hif-latn', | ||
' | 'hsb': 'de', | ||
' | 'ht': 'fr', | ||
' | 'ii': 'zh-cn', | ||
' | 'inh': 'ru', | ||
' | 'iu': 'ike-cans', | ||
' | 'jut': 'da', | ||
' | 'jv': 'id', | ||
' | 'kaa': 'kk-latn', | ||
' | 'kbd': 'kbd-cyrl', | ||
' | 'kbd-cyrl': 'ru', | ||
' | 'khw': 'ur', | ||
' | 'kiu': 'tr', | ||
' | 'kk': 'kk-cyrl', | ||
' | 'kk-arab': 'kk-cyrl', | ||
' | 'kk-cn': 'kk-arab', | ||
' | 'kk-kz': 'kk-cyrl', | ||
' | 'kk-latn': 'kk-cyrl', | ||
' | 'kk-tr': 'kk-latn', | ||
' | 'kl': 'da', | ||
' | 'koi': 'ru', | ||
' | 'ko-kp': 'ko', | ||
' | 'krc': 'ru', | ||
' | 'ks': 'ks-arab', | ||
' | 'ksh': 'de', | ||
' | 'ku': 'ku-latn', | ||
' | 'ku-arab': 'ckb', | ||
' | 'kv': 'ru', | ||
' | 'lad': 'es', | ||
' | 'lb': 'de', | ||
' | 'lbe': 'ru', | ||
' | 'lez': 'ru', | ||
' | 'li': 'nl', | ||
' | 'lij': 'it', | ||
' | 'liv': 'et', | ||
' | 'lmo': 'it', | ||
' | 'ln': 'fr', | ||
' | 'ltg': 'lv', | ||
' | 'lzz': 'tr', | ||
' | 'mai': 'hi', | ||
' | 'map-bms': 'jv', | ||
' | 'mg': 'fr', | ||
' | 'mhr': 'ru', | ||
' | 'min': 'id', | ||
' | 'mo': 'ro', | ||
' | 'mrj': 'ru', | ||
' | 'mwl': 'pt', | ||
' | 'myv': 'ru', | ||
' | 'mzn': 'fa', | ||
' | 'nah': 'es', | ||
' | 'nap': 'it', | ||
' | 'nds': 'de', | ||
' | 'nds-nl': 'nl', | ||
' | 'nl-informal': 'nl', | ||
' | 'no': 'nb', | ||
' | 'os': 'ru', | ||
' | 'pcd': 'fr', | ||
' | 'pdc': 'de', | ||
' | 'pdt': 'de', | ||
' | 'pfl': 'de', | ||
' | 'pms': 'it', | ||
' | // 'pt': 'pt-br', | ||
' | 'pt-br': 'pt', | ||
' | 'qu': 'es', | ||
' | 'qug': 'qu', | ||
' | 'rgn': 'it', | ||
' | 'rmy': 'ro', | ||
' | 'rue': 'uk', | ||
' | 'ruq': 'ruq-latn', | ||
' | 'ruq-cyrl': 'mk', | ||
' | 'ruq-latn': 'ro', | ||
' | 'sa': 'hi', | ||
' | 'sah': 'ru', | ||
' | 'scn': 'it', | ||
' | 'sg': 'fr', | ||
' | 'sgs': 'lt', | ||
' | 'shi': 'ar', | ||
' | 'simple': 'en', | ||
' | 'sli': 'de', | ||
' | 'sr': 'sr-ec', | ||
' | 'srn': 'nl', | ||
' | 'stq': 'de', | ||
' | 'su': 'id', | ||
' | 'szl': 'pl', | ||
' | 'tcy': 'kn', | ||
' | 'tg': 'tg-cyrl', | ||
' | 'tt': 'tt-cyrl', | ||
' | 'tt-cyrl': 'ru', | ||
' | 'ty': 'fr', | ||
' | 'udm': 'ru', | ||
' | 'ug': 'ug-arab', | ||
' | 'uk': 'ru', | ||
' | 'vec': 'it', | ||
' | 'vep': 'et', | ||
' | 'vls': 'nl', | ||
' | 'vmf': 'de', | ||
' | 'vot': 'fi', | ||
'vro': 'et', | |||
'wa': 'fr', | |||
'wo': 'fr', | |||
'wuu': 'zh-hans', | |||
'xal': 'ru', | |||
'xmf': 'ka', | |||
'yi': 'he', | |||
'za': 'zh-hans', | |||
'zea': 'nl', | |||
'zh': 'zh-hans', | |||
'zh-classical': 'lzh', | |||
'zh-cn': 'zh-hans', | |||
'zh-hant': 'zh-hans', | |||
'zh-hk': 'zh-hant', | |||
'zh-min-nan': 'nan', | |||
'zh-mo': 'zh-hk', | |||
'zh-my': 'zh-sg', | |||
'zh-sg': 'zh-hans', | |||
'zh-tw': 'zh-hant', | |||
'zh-yue': 'yue' | |||
}; | }; | ||
/* | /* | ||
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 | * @param lang Language in use when loop was found. | ||
* @param | * @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] | 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 | * @param messages The message object to look translations up in. | ||
* @param | * @param msgName The name of the message to get. | ||
* @param | * @param lang The language to get the message in. | ||
* @param | * @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 | * | ||
* @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 ( | if (messages[lang] && messages[lang][msgName]) { | ||
return | return messages[lang][msgName]; | ||
} | } | ||
if ( | if (lang === 'en') { | ||
return | return false; | ||
} | } | ||
Line 543: | Line 291: | ||
fallbackChain = []; | fallbackChain = []; | ||
} | } | ||
fallbackChain.push(lang); | |||
lang = fallbacks[lang] || 'en'; | |||
if (fallbackChain.indexOf(lang) !== -1) { | |||
// about to enter an infinite loop - switch to English | |||
warnOnFallbackLoop(lang, fallbackChain); | |||
lang = 'en'; | |||
} | } | ||
return getMsg(messages, msgName, lang, fallbackChain); | |||
} | } | ||
Line 576: | Line 308: | ||
* as $n where n > 0. | * as $n where n > 0. | ||
* | * | ||
* @param | * @param message The message to substitute arguments into | ||
* @param | * @param arguments The arguments to substitute in. | ||
* @return | * | ||
* @return The resulting message. | |||
*/ | */ | ||
function handleArgs(message, args) { | function handleArgs(message, args) { | ||
args.forEach(function (elem, index) { | args.forEach(function (elem, index) { | ||
var rgx = new RegExp('\\$' + (index + 1), 'g'); | var rgx = new RegExp('\\$' + (index + 1), 'g'); | ||
message = message.replace(rgx, elem); | message = message.replace(rgx, elem); | ||
Line 595: | Line 325: | ||
* Generate a HTML link using the supplied parameters. | * Generate a HTML link using the supplied parameters. | ||
* | * | ||
* @param | * @param href The href of the link which will be converted to | ||
* '/wiki/href'. | * '/wiki/href'. | ||
* @param | * @param text The text and title of the link. If this is not supplied, it | ||
* will default to href. | * will default to href. | ||
* @param | * @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 | * | ||
* @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 = 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 | ||
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 | ||
} 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 | * @param message The message to process. | ||
* @return | * | ||
* @return The resulting string. | |||
*/ | */ | ||
function parse(message) { | function parse(message) { | ||
// [url text] -> [$1 $2] | |||
var urlRgx = /\[((?:https?:)?\/\/.+?) (.+?)\]/g, | var urlRgx = /\[((?:https?:)?\/\/.+?) (.+?)\]/g, | ||
/ | // [[pagename]] -> [[$1]] | ||
simplePageRgx = /\[\[([^|]*?)\]\]/g, | simplePageRgx = /\[\[([^|]*?)\]\]/g, | ||
/ | // [[pagename|text]] -> [[$1|$2]] | ||
pageWithTextRgx = /\[\[(.+?)\|(.+?)\]\]/g, | pageWithTextRgx = /\[\[(.+?)\|(.+?)\]\]/g, | ||
/ | // {{PLURAL:count|singular|plural}} -> {{PLURAL:$1|$2}} | ||
pluralRgx = /\{\{PLURAL:(\d+)\|(.+?)\}\}/gi, | pluralRgx = /\{\{PLURAL:(\d+)\|(.+?)\}\}/gi, | ||
/ | // {{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 | * @param messages The message object to look translations up in. | ||
* @param | * @param lang The language to get the message in. | ||
* @param | * @param args Any arguments to substitute into the message, [0] is message name. | ||
* @param | * @param name The name of the script the messages are for. | ||
*/ | */ | ||
function message(messages, lang, args, name) { | function message(messages, lang, args, name) { | ||
Line 760: | Line 489: | ||
} | } | ||
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 | ||
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 | ||
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 | ||
msg = overrides[name][msgName]; | msg = overrides[name][msgName]; | ||
msgExists = true; | msgExists = true; | ||
Line 791: | Line 514: | ||
return { | return { | ||
/* | /* | ||
* | * 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 | * @return The resulting string. | ||
*/ | */ | ||
parse: function () { | parse: function () { | ||
/ | // skip parsing if the message wasn't found otherwise | ||
// 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 | * @return The resulting string. | ||
*/ | */ | ||
escape: function () { | escape: function () { | ||
Line 824: | Line 545: | ||
* Return the message as is. | * Return the message as is. | ||
* | * | ||
* @return | * @return The resulting string. | ||
*/ | */ | ||
plain: function () { | plain: function () { | ||
Line 835: | Line 556: | ||
* Create a new i18n object. | * Create a new i18n object. | ||
* | * | ||
* @param | * @param messages The message object to look translations up in. | ||
* @param | * @param name The name of the script the messages are for. | ||
* @param | * @param options Options set by the loading script. | ||
*/ | */ | ||
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 | * @param lang The language code to use for the next `msg` call. | ||
* | * | ||
* @return | * @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 | * @return The current object for use in chaining. | ||
*/ | */ | ||
inContentLang: function () { | inContentLang: function () { | ||
Line 888: | Line 608: | ||
}, | }, | ||
/* | /* | ||
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 | * @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. | ||
*/ | */ | ||
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 | * @param name The name of the script the messages are for. | ||
* @param | * @param messages The message object to look translations up in. | ||
* @param | * @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 | ||
return messages; | return messages; | ||
} | } | ||
var addMsgsForLanguage = function (lang) { | var addMsgsForLanguage = function (lang) { | ||
if (optimised[lang]) { | if (optimised[lang]) { | ||
/ | // language already exists | ||
return; | return; | ||
} | } | ||
Line 997: | Line 677: | ||
msgKeys.forEach(function (msgName) { | msgKeys.forEach(function (msgName) { | ||
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 | ||
// this allows an optimised cache even when using different | |||
// 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 | ||
// 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. | ||
* | * | ||
* @param name The name of the script to check for. | |||
* @param options Options set by the loading script. | |||
* | * | ||
* @return Boolean whether the cache should be used. | |||
* @return | |||
*/ | */ | ||
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 | ||
if (!messages) { | if (!messages) { | ||
return false; | return false; | ||
} | } | ||
/ | // optimised messages missing user or content language | ||
// 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); | |||
}).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 | ||
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 | * @param json The JSON string. | ||
* @return | * | ||
* @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 | * @param name The name of the script the messages are for. | ||
* @param | * @param json The JSON object. | ||
* @param | * @param cacheVersion Cache version requested by the loading script. | ||
*/ | */ | ||
function saveToCache(name, json, cacheVersion) { | function saveToCache(name, json, cacheVersion) { | ||
var keyPrefix = cachePrefix + name; | var keyPrefix = cachePrefix + name; | ||
/ | // 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 | * @param name The name of the script the messages are for. | ||
* @param | * @param res The JSON string. | ||
* @param | * @param options Options set by the loading script. | ||
* @return | * | ||
* @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 | ||
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[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 | * @param name The name of the script the messages are for. | ||
* @param | * @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 | ||
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 | * @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. | ||
* @param options Options set by the loading script: | |||
* cacheAll: Either an array of message names for which translations should not be optimised, or `true` to disable the optimised cache. | |||
* @param | * cacheVersion: Minimum cache version requested by the loading script. | ||
* 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 | * @return A jQuery.Deferred instance. | ||
*/ | */ | ||
function loadMessages(name, options) { | function loadMessages(name, options) { | ||
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', | ||
page = 'MediaWiki:Custom-' + name + '/i18n.json', | page = 'MediaWiki:Custom-' + name + '/i18n.json', | ||
params; | params; | ||
options = options || {}; | options = options || {}; | ||
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 | ||
options.loadedFromCache = false; | options.loadedFromCache = false; | ||
/ | // allow custom i18n pages to be specified on other wikis | ||
// mainly for VSTF wiki to keep their own JSON file | |||
// 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 | ||
// and have it take effect before we load the messagaes | |||
// 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 | ||
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 | ||
// they may be changed or removed without warning | |||
// scripts should not rely on these existing or their output being in any particular format | |||
_stripComments: stripComments, | _stripComments: stripComments, | ||
_saveToCache: saveToCache, | _saveToCache: saveToCache, | ||
Line 1,351: | Line 989: | ||
}); | }); | ||
/ | // 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 | ||
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 | ||
removeOldCacheEntries(); | removeOldCacheEntries(); | ||
} (this, jQuery, mediaWiki)); | }(this, jQuery, mediaWiki)); |