diff --git a/external/webL10n/l10n.js b/external/webL10n/l10n.js index 6c9421bc9..533fab6f8 100644 --- a/external/webL10n/l10n.js +++ b/external/webL10n/l10n.js @@ -22,11 +22,12 @@ /* Additional modifications for PDF.js project: - Disables language initialization on page loading; - - Removes consoleWarn and consoleLog and use console.log/warn directly. + - Removes console.warn and console.log and use console.log/warn directly. - Removes window._ assignment. + - Remove compatibility code for OldIE. */ -/*jshint browser: true, devel: true, globalstrict: true */ +/*jshint browser: true, devel: true, es5: true, globalstrict: true */ 'use strict'; document.webL10n = (function(window, document, undefined) { @@ -98,14 +99,14 @@ document.webL10n = (function(window, document, undefined) { document.dispatchEvent(evtObject); } - function xhrLoadText(url, onSuccess, onFailure, asynchronous) { + function xhrLoadText(url, onSuccess, onFailure) { onSuccess = onSuccess || function _onSuccess(data) {}; onFailure = onFailure || function _onFailure() { console.warn(url + ' not found.'); }; var xhr = new XMLHttpRequest(); - xhr.open('GET', url, asynchronous); + xhr.open('GET', url, gAsyncResourceLoading); if (xhr.overrideMimeType) { xhr.overrideMimeType('text/plain; charset=utf-8'); } @@ -174,8 +175,10 @@ document.webL10n = (function(window, document, undefined) { } // parse *.properties text data into an l10n dictionary - function parseProperties(text) { - var dictionary = []; + // If gAsyncResourceLoading is false, then the callback will be called + // synchronously. Otherwise it is called asynchronously. + function parseProperties(text, parsedPropertiesCallback) { + var dictionary = {}; // token expressions var reBlank = /^\s*|\s*$/; @@ -185,57 +188,69 @@ document.webL10n = (function(window, document, undefined) { var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\' // parse the *.properties file into an associative array - function parseRawLines(rawText, extendedSyntax) { + function parseRawLines(rawText, extendedSyntax, parsedRawLinesCallback) { var entries = rawText.replace(reBlank, '').split(/[\r\n]+/); var currentLang = '*'; var genericLang = lang.split('-', 1)[0]; var skipLang = false; var match = ''; - for (var i = 0; i < entries.length; i++) { - var line = entries[i]; + function nextEntry() { + // Use infinite loop instead of recursion to avoid reaching the + // maximum recursion limit for content with many lines. + while (true) { + if (!entries.length) { + parsedRawLinesCallback(); + return; + } + var line = entries.shift(); - // comment or blank line? - if (reComment.test(line)) - continue; + // comment or blank line? + if (reComment.test(line)) + continue; - // the extended syntax supports [lang] sections and @import rules - if (extendedSyntax) { - if (reSection.test(line)) { // section start? + // the extended syntax supports [lang] sections and @import rules + if (extendedSyntax) { match = reSection.exec(line); - // RFC 4646, section 4.4, "All comparisons MUST be performed - // in a case-insensitive manner." - currentLang = match[1].toLowerCase(); - skipLang = (currentLang !== '*') && - (currentLang !== lang) && (currentLang !== genericLang); - continue; - } else if (skipLang) { - continue; - } - if (reImport.test(line)) { // @import rule? + if (match) { // section start? + // RFC 4646, section 4.4, "All comparisons MUST be performed + // in a case-insensitive manner." + + currentLang = match[1].toLowerCase(); + skipLang = (currentLang !== '*') && + (currentLang !== lang) && (currentLang !== genericLang); + continue; + } else if (skipLang) { + continue; + } match = reImport.exec(line); - loadImport(baseURL + match[1]); // load the resource synchronously + if (match) { // @import rule? + loadImport(baseURL + match[1], nextEntry); + return; + } } - } - // key-value pair - var tmp = line.match(reSplit); - if (tmp && tmp.length == 3) { - dictionary[tmp[1]] = evalString(tmp[2]); + // key-value pair + var tmp = line.match(reSplit); + if (tmp && tmp.length == 3) { + dictionary[tmp[1]] = evalString(tmp[2]); + } } } + nextEntry(); } // import another *.properties file - function loadImport(url) { + function loadImport(url, callback) { xhrLoadText(url, function(content) { - parseRawLines(content, false); // don't allow recursive imports - }, null, false); // load synchronously + parseRawLines(content, false, callback); // don't allow recursive imports + }, null); } // fill the dictionary - parseRawLines(text, true); - return dictionary; + parseRawLines(text, true, function() { + parsedPropertiesCallback(dictionary); + }); } // load and parse l10n data (warning: global variables are used here) @@ -243,29 +258,30 @@ document.webL10n = (function(window, document, undefined) { gTextData += response; // mostly for debug // parse *.properties text data into an l10n dictionary - var data = parseProperties(response); - - // find attribute descriptions, if any - for (var key in data) { - var id, prop, index = key.lastIndexOf('.'); - if (index > 0) { // an attribute has been specified - id = key.substring(0, index); - prop = key.substr(index + 1); - } else { // no attribute: assuming text content by default - id = key; - prop = gTextProp; - } - if (!gL10nData[id]) { - gL10nData[id] = {}; + parseProperties(response, function(data) { + + // find attribute descriptions, if any + for (var key in data) { + var id, prop, index = key.lastIndexOf('.'); + if (index > 0) { // an attribute has been specified + id = key.substring(0, index); + prop = key.substr(index + 1); + } else { // no attribute: assuming text content by default + id = key; + prop = gTextProp; + } + if (!gL10nData[id]) { + gL10nData[id] = {}; + } + gL10nData[id][prop] = data[key]; } - gL10nData[id][prop] = data[key]; - } - // trigger callback - if (successCallback) { - successCallback(); - } - }, failureCallback, gAsyncResourceLoading); + // trigger callback + if (successCallback) { + successCallback(); + } + }); + }, failureCallback); } // load and parse all resources for the specified locale @@ -328,24 +344,23 @@ document.webL10n = (function(window, document, undefined) { // load all resource files function L10nResourceLink(link) { var href = link.href; - var type = link.type; + // Note: If |gAsyncResourceLoading| is false, then the following callbacks + // are synchronously called. this.load = function(lang, callback) { - var applied = lang; parseResource(href, lang, callback, function() { console.warn(href + ' not found.'); - applied = ''; + // lang not found, used default resource instead + console.warn('"' + lang + '" resource not found'); + gLanguage = ''; + // Resource not loaded, but we still need to call the callback. + callback(); }); - return applied; // return lang if found, an empty string if not found }; } for (var i = 0; i < langCount; i++) { var resource = new L10nResourceLink(langLinks[i]); - var rv = resource.load(lang, onResourceLoaded); - if (rv != lang) { // lang not found, used default resource instead - console.warn('"' + lang + '" resource not found'); - gLanguage = ''; - } + resource.load(lang, onResourceLoaded); } } @@ -861,28 +876,17 @@ document.webL10n = (function(window, document, undefined) { // replace {{arguments}} with their values function substArguments(str, args, key) { - var reArgs = /\{\{\s*(.+?)\s*\}\}/; - var match = reArgs.exec(str); - while (match) { - if (!match || match.length < 2) - return str; // argument key not found - - var arg = match[1]; - var sub = ''; + var reArgs = /\{\{\s*(.+?)\s*\}\}/g; + return str.replace(reArgs, function(matched_text, arg) { if (args && arg in args) { - sub = args[arg]; - } else if (arg in gL10nData) { - sub = gL10nData[arg][gTextProp]; - } else { - console.log('argument {{' + arg + '}} for #' + key + ' is undefined.'); - return str; + return args[arg]; } - - str = str.substring(0, match.index) + sub + - str.substr(match.index + match[0].length); - match = reArgs.exec(str); - } - return str; + if (arg in gL10nData) { + return gL10nData[arg]; + } + console.log('argument {{' + arg + '}} for #' + key + ' is undefined.'); + return matched_text; + }); } // translate an HTML element @@ -962,7 +966,6 @@ document.webL10n = (function(window, document, undefined) { translateElement(element); } - // cross-browser API (sorry, oldIE doesn't support getters & setters) return { // get a localized string get: function(key, args, fallbackString) { @@ -990,17 +993,20 @@ document.webL10n = (function(window, document, undefined) { // get|set the document language getLanguage: function() { return gLanguage; }, - setLanguage: function(lang) { loadLocale(lang, translateFragment); }, + setLanguage: function(lang, callback) { + loadLocale(lang, function() { + if (callback) + callback(); + translateFragment(); + }); + }, // get the direction (ltr|rtl) of the current language getDirection: function() { // http://www.w3.org/International/questions/qa-scripts // Arabic, Hebrew, Farsi, Pashto, Urdu var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; - - // use the short language code for "full" codes like 'ar-sa' (issue 5440) - var shortCode = gLanguage.split('-')[0]; - + var shortCode = gLanguage.split('-', 1)[0]; return (rtlList.indexOf(shortCode) >= 0) ? 'rtl' : 'ltr'; }, @@ -1013,14 +1019,13 @@ document.webL10n = (function(window, document, undefined) { if (!callback) { return; } else if (gReadyState == 'complete' || gReadyState == 'interactive') { - window.setTimeout(callback); + window.setTimeout(function() { + callback(); + }); } else if (document.addEventListener) { - document.addEventListener('localized', callback); - } else if (document.attachEvent) { - document.documentElement.attachEvent('onpropertychange', function(e) { - if (e.propertyName === 'localized') { - callback(); - } + document.addEventListener('localized', function once() { + document.removeEventListener('localized', once); + callback(); }); } }