app/assets/javascripts/pdfjs_viewer/pdfjs/l10n.js in pdfjs_viewer-rails-0.0.6 vs app/assets/javascripts/pdfjs_viewer/pdfjs/l10n.js in pdfjs_viewer-rails-0.0.7

- old
+ new

@@ -22,13 +22,14 @@ /* Additional modifications for PDF.js project: - Disables language initialization on page loading; - Removes consoleWarn and consoleLog 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) { var gL10nData = {}; var gTextData = ''; @@ -96,18 +97,18 @@ evtObject.initEvent('localized', true, false); evtObject.language = lang; 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'); } xhr.onreadystatechange = function() { if (xhr.readyState == 4) { @@ -140,11 +141,11 @@ * * @param {string} href * URL of the l10n resource to parse. * * @param {string} lang - * locale (language) to parse. + * locale (language) to parse. Must be a lowercase string. * * @param {Function} successCallback * triggered when the l10n resource has been successully parsed. * * @param {Function} failureCallback @@ -172,104 +173,127 @@ .replace(/\\"/g, '"') .replace(/\\'/g, "'"); } // 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*$/; var reComment = /^\s*#|^\s*$/; var reSection = /^\s*\[(.*)\]\s*$/; var reImport = /^\s*@import\s+url\((.*)\)\s*$/i; 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.replace(/-[a-z]+$/i, ''); + 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); - currentLang = match[1]; - 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) xhrLoadText(href, function(response) { gTextData += response; // mostly for debug // parse *.properties text data into an l10n dictionary - var data = parseProperties(response); + 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; + // 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]; } - if (!gL10nData[id]) { - gL10nData[id] = {}; - } - 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 function loadLocale(lang, callback) { + // RFC 4646, section 2.1 states that language tags have to be treated as + // case-insensitive. Convert to lowercase for case-insensitive comparisons. + if (lang) { + lang = lang.toLowerCase(); + } + callback = callback || function _callback() {}; clear(); gLanguage = lang; @@ -280,11 +304,23 @@ if (langCount === 0) { // we might have a pre-compiled dictionary instead var dict = getL10nDictionary(); if (dict && dict.locales && dict.default_locale) { console.log('using the embedded JSON directory, early way out'); - gL10nData = dict.locales[lang] || dict.locales[dict.default_locale]; + gL10nData = dict.locales[lang]; + if (!gL10nData) { + var defaultLocale = dict.default_locale.toLowerCase(); + for (var anyCaseLang in dict.locales) { + anyCaseLang = anyCaseLang.toLowerCase(); + if (anyCaseLang === lang) { + gL10nData = dict.locales[lang]; + break; + } else if (anyCaseLang === defaultLocale) { + gL10nData = dict.locales[defaultLocale]; + } + } + } callback(); } else { console.log('no resource to load, early way out'); } // early way out @@ -306,28 +342,27 @@ }; // 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); } } // clear all l10n data function clear() { @@ -839,32 +874,21 @@ return str; } // 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 function translateElement(element) { var l10n = getL10nAttributes(element); @@ -940,11 +964,10 @@ // translate element itself if necessary translateElement(element); } - // cross-browser API (sorry, oldIE doesn't support getters & setters) return { // get a localized string get: function(key, args, fallbackString) { var index = key.lastIndexOf('.'); var prop = gTextProp; @@ -968,18 +991,25 @@ getData: function() { return gL10nData; }, getText: function() { return gTextData; }, // 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']; - return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; + var shortCode = gLanguage.split('-', 1)[0]; + return (rtlList.indexOf(shortCode) >= 0) ? 'rtl' : 'ltr'; }, // translate an element or document fragment translate: translateFragment, @@ -987,17 +1017,16 @@ getReadyState: function() { return gReadyState; }, ready: function(callback) { 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(); }); } } }; }) (window, document);