1005 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1005 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * Copyright (c) 2011-2013 Fabien Cazenave, Mozilla.
 | 
						|
 *
 | 
						|
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						|
 * of this software and associated documentation files (the "Software"), to
 | 
						|
 * deal in the Software without restriction, including without limitation the
 | 
						|
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 | 
						|
 * sell copies of the Software, and to permit persons to whom the Software is
 | 
						|
 * furnished to do so, subject to the following conditions:
 | 
						|
 *
 | 
						|
 * The above copyright notice and this permission notice shall be included in
 | 
						|
 * all copies or substantial portions of the Software.
 | 
						|
 *
 | 
						|
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						|
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
						|
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						|
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | 
						|
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 | 
						|
 * IN THE SOFTWARE.
 | 
						|
 */
 | 
						|
/*
 | 
						|
  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.
 | 
						|
*/
 | 
						|
 | 
						|
/*jshint browser: true, devel: true, globalstrict: true */
 | 
						|
'use strict';
 | 
						|
 | 
						|
document.webL10n = (function(window, document, undefined) {
 | 
						|
  var gL10nData = {};
 | 
						|
  var gTextData = '';
 | 
						|
  var gTextProp = 'textContent';
 | 
						|
  var gLanguage = '';
 | 
						|
  var gMacros = {};
 | 
						|
  var gReadyState = 'loading';
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Synchronously loading l10n resources significantly minimizes flickering
 | 
						|
   * from displaying the app with non-localized strings and then updating the
 | 
						|
   * strings. Although this will block all script execution on this page, we
 | 
						|
   * expect that the l10n resources are available locally on flash-storage.
 | 
						|
   *
 | 
						|
   * As synchronous XHR is generally considered as a bad idea, we're still
 | 
						|
   * loading l10n resources asynchronously -- but we keep this in a setting,
 | 
						|
   * just in case... and applications using this library should hide their
 | 
						|
   * content until the `localized' event happens.
 | 
						|
   */
 | 
						|
 | 
						|
  var gAsyncResourceLoading = true; // read-only
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * DOM helpers for the so-called "HTML API".
 | 
						|
   *
 | 
						|
   * These functions are written for modern browsers. For old versions of IE,
 | 
						|
   * they're overridden in the 'startup' section at the end of this file.
 | 
						|
   */
 | 
						|
 | 
						|
  function getL10nResourceLinks() {
 | 
						|
    return document.querySelectorAll('link[type="application/l10n"]');
 | 
						|
  }
 | 
						|
 | 
						|
  function getL10nDictionary() {
 | 
						|
    var script = document.querySelector('script[type="application/l10n"]');
 | 
						|
    // TODO: support multiple and external JSON dictionaries
 | 
						|
    return script ? JSON.parse(script.innerHTML) : null;
 | 
						|
  }
 | 
						|
 | 
						|
  function getTranslatableChildren(element) {
 | 
						|
    return element ? element.querySelectorAll('*[data-l10n-id]') : [];
 | 
						|
  }
 | 
						|
 | 
						|
  function getL10nAttributes(element) {
 | 
						|
    if (!element)
 | 
						|
      return {};
 | 
						|
 | 
						|
    var l10nId = element.getAttribute('data-l10n-id');
 | 
						|
    var l10nArgs = element.getAttribute('data-l10n-args');
 | 
						|
    var args = {};
 | 
						|
    if (l10nArgs) {
 | 
						|
      try {
 | 
						|
        args = JSON.parse(l10nArgs);
 | 
						|
      } catch (e) {
 | 
						|
        console.warn('could not parse arguments for #' + l10nId);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return { id: l10nId, args: args };
 | 
						|
  }
 | 
						|
 | 
						|
  function fireL10nReadyEvent(lang) {
 | 
						|
    var evtObject = document.createEvent('Event');
 | 
						|
    evtObject.initEvent('localized', true, false);
 | 
						|
    evtObject.language = lang;
 | 
						|
    document.dispatchEvent(evtObject);
 | 
						|
  }
 | 
						|
 | 
						|
  function xhrLoadText(url, onSuccess, onFailure, asynchronous) {
 | 
						|
    onSuccess = onSuccess || function _onSuccess(data) {};
 | 
						|
    onFailure = onFailure || function _onFailure() {
 | 
						|
      console.warn(url + ' not found.');
 | 
						|
    };
 | 
						|
 | 
						|
    var xhr = new XMLHttpRequest();
 | 
						|
    xhr.open('GET', url, asynchronous);
 | 
						|
    if (xhr.overrideMimeType) {
 | 
						|
      xhr.overrideMimeType('text/plain; charset=utf-8');
 | 
						|
    }
 | 
						|
    xhr.onreadystatechange = function() {
 | 
						|
      if (xhr.readyState == 4) {
 | 
						|
        if (xhr.status == 200 || xhr.status === 0) {
 | 
						|
          onSuccess(xhr.responseText);
 | 
						|
        } else {
 | 
						|
          onFailure();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
    xhr.onerror = onFailure;
 | 
						|
    xhr.ontimeout = onFailure;
 | 
						|
 | 
						|
    // in Firefox OS with the app:// protocol, trying to XHR a non-existing
 | 
						|
    // URL will raise an exception here -- hence this ugly try...catch.
 | 
						|
    try {
 | 
						|
      xhr.send(null);
 | 
						|
    } catch (e) {
 | 
						|
      onFailure();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * l10n resource parser:
 | 
						|
   *  - reads (async XHR) the l10n resource matching `lang';
 | 
						|
   *  - imports linked resources (synchronously) when specified;
 | 
						|
   *  - parses the text data (fills `gL10nData' and `gTextData');
 | 
						|
   *  - triggers success/failure callbacks when done.
 | 
						|
   *
 | 
						|
   * @param {string} href
 | 
						|
   *    URL of the l10n resource to parse.
 | 
						|
   *
 | 
						|
   * @param {string} lang
 | 
						|
   *    locale (language) to parse.
 | 
						|
   *
 | 
						|
   * @param {Function} successCallback
 | 
						|
   *    triggered when the l10n resource has been successully parsed.
 | 
						|
   *
 | 
						|
   * @param {Function} failureCallback
 | 
						|
   *    triggered when the an error has occured.
 | 
						|
   *
 | 
						|
   * @return {void}
 | 
						|
   *    uses the following global variables: gL10nData, gTextData, gTextProp.
 | 
						|
   */
 | 
						|
 | 
						|
  function parseResource(href, lang, successCallback, failureCallback) {
 | 
						|
    var baseURL = href.replace(/[^\/]*$/, '') || './';
 | 
						|
 | 
						|
    // handle escaped characters (backslashes) in a string
 | 
						|
    function evalString(text) {
 | 
						|
      if (text.lastIndexOf('\\') < 0)
 | 
						|
        return text;
 | 
						|
      return text.replace(/\\\\/g, '\\')
 | 
						|
                 .replace(/\\n/g, '\n')
 | 
						|
                 .replace(/\\r/g, '\r')
 | 
						|
                 .replace(/\\t/g, '\t')
 | 
						|
                 .replace(/\\b/g, '\b')
 | 
						|
                 .replace(/\\f/g, '\f')
 | 
						|
                 .replace(/\\{/g, '{')
 | 
						|
                 .replace(/\\}/g, '}')
 | 
						|
                 .replace(/\\"/g, '"')
 | 
						|
                 .replace(/\\'/g, "'");
 | 
						|
    }
 | 
						|
 | 
						|
    // parse *.properties text data into an l10n dictionary
 | 
						|
    function parseProperties(text) {
 | 
						|
      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) {
 | 
						|
        var entries = rawText.replace(reBlank, '').split(/[\r\n]+/);
 | 
						|
        var currentLang = '*';
 | 
						|
        var genericLang = lang.replace(/-[a-z]+$/i, '');
 | 
						|
        var skipLang = false;
 | 
						|
        var match = '';
 | 
						|
 | 
						|
        for (var i = 0; i < entries.length; i++) {
 | 
						|
          var line = entries[i];
 | 
						|
 | 
						|
          // 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?
 | 
						|
              match = reSection.exec(line);
 | 
						|
              currentLang = match[1];
 | 
						|
              skipLang = (currentLang !== '*') &&
 | 
						|
                  (currentLang !== lang) && (currentLang !== genericLang);
 | 
						|
              continue;
 | 
						|
            } else if (skipLang) {
 | 
						|
              continue;
 | 
						|
            }
 | 
						|
            if (reImport.test(line)) { // @import rule?
 | 
						|
              match = reImport.exec(line);
 | 
						|
              loadImport(baseURL + match[1]); // load the resource synchronously
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          // key-value pair
 | 
						|
          var tmp = line.match(reSplit);
 | 
						|
          if (tmp && tmp.length == 3) {
 | 
						|
            dictionary[tmp[1]] = evalString(tmp[2]);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // import another *.properties file
 | 
						|
      function loadImport(url) {
 | 
						|
        xhrLoadText(url, function(content) {
 | 
						|
          parseRawLines(content, false); // don't allow recursive imports
 | 
						|
        }, null, false); // load synchronously
 | 
						|
      }
 | 
						|
 | 
						|
      // fill the dictionary
 | 
						|
      parseRawLines(text, true);
 | 
						|
      return 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);
 | 
						|
 | 
						|
      // 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];
 | 
						|
      }
 | 
						|
 | 
						|
      // trigger callback
 | 
						|
      if (successCallback) {
 | 
						|
        successCallback();
 | 
						|
      }
 | 
						|
    }, failureCallback, gAsyncResourceLoading);
 | 
						|
  }
 | 
						|
 | 
						|
  // load and parse all resources for the specified locale
 | 
						|
  function loadLocale(lang, callback) {
 | 
						|
    callback = callback || function _callback() {};
 | 
						|
 | 
						|
    clear();
 | 
						|
    gLanguage = lang;
 | 
						|
 | 
						|
    // check all <link type="application/l10n" href="..." /> nodes
 | 
						|
    // and load the resource files
 | 
						|
    var langLinks = getL10nResourceLinks();
 | 
						|
    var langCount = langLinks.length;
 | 
						|
    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];
 | 
						|
        callback();
 | 
						|
      } else {
 | 
						|
        console.log('no resource to load, early way out');
 | 
						|
      }
 | 
						|
      // early way out
 | 
						|
      fireL10nReadyEvent(lang);
 | 
						|
      gReadyState = 'complete';
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // start the callback when all resources are loaded
 | 
						|
    var onResourceLoaded = null;
 | 
						|
    var gResourceCount = 0;
 | 
						|
    onResourceLoaded = function() {
 | 
						|
      gResourceCount++;
 | 
						|
      if (gResourceCount >= langCount) {
 | 
						|
        callback();
 | 
						|
        fireL10nReadyEvent(lang);
 | 
						|
        gReadyState = 'complete';
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    // load all resource files
 | 
						|
    function L10nResourceLink(link) {
 | 
						|
      var href = link.href;
 | 
						|
      var type = link.type;
 | 
						|
      this.load = function(lang, callback) {
 | 
						|
        var applied = lang;
 | 
						|
        parseResource(href, lang, callback, function() {
 | 
						|
          console.warn(href + ' not found.');
 | 
						|
          applied = '';
 | 
						|
        });
 | 
						|
        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 = '';
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // clear all l10n data
 | 
						|
  function clear() {
 | 
						|
    gL10nData = {};
 | 
						|
    gTextData = '';
 | 
						|
    gLanguage = '';
 | 
						|
    // TODO: clear all non predefined macros.
 | 
						|
    // There's no such macro /yet/ but we're planning to have some...
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Get rules for plural forms (shared with JetPack), see:
 | 
						|
   * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
 | 
						|
   * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
 | 
						|
   *
 | 
						|
   * @param {string} lang
 | 
						|
   *    locale (language) used.
 | 
						|
   *
 | 
						|
   * @return {Function}
 | 
						|
   *    returns a function that gives the plural form name for a given integer:
 | 
						|
   *       var fun = getPluralRules('en');
 | 
						|
   *       fun(1)    -> 'one'
 | 
						|
   *       fun(0)    -> 'other'
 | 
						|
   *       fun(1000) -> 'other'.
 | 
						|
   */
 | 
						|
 | 
						|
  function getPluralRules(lang) {
 | 
						|
    var locales2rules = {
 | 
						|
      'af': 3,
 | 
						|
      'ak': 4,
 | 
						|
      'am': 4,
 | 
						|
      'ar': 1,
 | 
						|
      'asa': 3,
 | 
						|
      'az': 0,
 | 
						|
      'be': 11,
 | 
						|
      'bem': 3,
 | 
						|
      'bez': 3,
 | 
						|
      'bg': 3,
 | 
						|
      'bh': 4,
 | 
						|
      'bm': 0,
 | 
						|
      'bn': 3,
 | 
						|
      'bo': 0,
 | 
						|
      'br': 20,
 | 
						|
      'brx': 3,
 | 
						|
      'bs': 11,
 | 
						|
      'ca': 3,
 | 
						|
      'cgg': 3,
 | 
						|
      'chr': 3,
 | 
						|
      'cs': 12,
 | 
						|
      'cy': 17,
 | 
						|
      'da': 3,
 | 
						|
      'de': 3,
 | 
						|
      'dv': 3,
 | 
						|
      'dz': 0,
 | 
						|
      'ee': 3,
 | 
						|
      'el': 3,
 | 
						|
      'en': 3,
 | 
						|
      'eo': 3,
 | 
						|
      'es': 3,
 | 
						|
      'et': 3,
 | 
						|
      'eu': 3,
 | 
						|
      'fa': 0,
 | 
						|
      'ff': 5,
 | 
						|
      'fi': 3,
 | 
						|
      'fil': 4,
 | 
						|
      'fo': 3,
 | 
						|
      'fr': 5,
 | 
						|
      'fur': 3,
 | 
						|
      'fy': 3,
 | 
						|
      'ga': 8,
 | 
						|
      'gd': 24,
 | 
						|
      'gl': 3,
 | 
						|
      'gsw': 3,
 | 
						|
      'gu': 3,
 | 
						|
      'guw': 4,
 | 
						|
      'gv': 23,
 | 
						|
      'ha': 3,
 | 
						|
      'haw': 3,
 | 
						|
      'he': 2,
 | 
						|
      'hi': 4,
 | 
						|
      'hr': 11,
 | 
						|
      'hu': 0,
 | 
						|
      'id': 0,
 | 
						|
      'ig': 0,
 | 
						|
      'ii': 0,
 | 
						|
      'is': 3,
 | 
						|
      'it': 3,
 | 
						|
      'iu': 7,
 | 
						|
      'ja': 0,
 | 
						|
      'jmc': 3,
 | 
						|
      'jv': 0,
 | 
						|
      'ka': 0,
 | 
						|
      'kab': 5,
 | 
						|
      'kaj': 3,
 | 
						|
      'kcg': 3,
 | 
						|
      'kde': 0,
 | 
						|
      'kea': 0,
 | 
						|
      'kk': 3,
 | 
						|
      'kl': 3,
 | 
						|
      'km': 0,
 | 
						|
      'kn': 0,
 | 
						|
      'ko': 0,
 | 
						|
      'ksb': 3,
 | 
						|
      'ksh': 21,
 | 
						|
      'ku': 3,
 | 
						|
      'kw': 7,
 | 
						|
      'lag': 18,
 | 
						|
      'lb': 3,
 | 
						|
      'lg': 3,
 | 
						|
      'ln': 4,
 | 
						|
      'lo': 0,
 | 
						|
      'lt': 10,
 | 
						|
      'lv': 6,
 | 
						|
      'mas': 3,
 | 
						|
      'mg': 4,
 | 
						|
      'mk': 16,
 | 
						|
      'ml': 3,
 | 
						|
      'mn': 3,
 | 
						|
      'mo': 9,
 | 
						|
      'mr': 3,
 | 
						|
      'ms': 0,
 | 
						|
      'mt': 15,
 | 
						|
      'my': 0,
 | 
						|
      'nah': 3,
 | 
						|
      'naq': 7,
 | 
						|
      'nb': 3,
 | 
						|
      'nd': 3,
 | 
						|
      'ne': 3,
 | 
						|
      'nl': 3,
 | 
						|
      'nn': 3,
 | 
						|
      'no': 3,
 | 
						|
      'nr': 3,
 | 
						|
      'nso': 4,
 | 
						|
      'ny': 3,
 | 
						|
      'nyn': 3,
 | 
						|
      'om': 3,
 | 
						|
      'or': 3,
 | 
						|
      'pa': 3,
 | 
						|
      'pap': 3,
 | 
						|
      'pl': 13,
 | 
						|
      'ps': 3,
 | 
						|
      'pt': 3,
 | 
						|
      'rm': 3,
 | 
						|
      'ro': 9,
 | 
						|
      'rof': 3,
 | 
						|
      'ru': 11,
 | 
						|
      'rwk': 3,
 | 
						|
      'sah': 0,
 | 
						|
      'saq': 3,
 | 
						|
      'se': 7,
 | 
						|
      'seh': 3,
 | 
						|
      'ses': 0,
 | 
						|
      'sg': 0,
 | 
						|
      'sh': 11,
 | 
						|
      'shi': 19,
 | 
						|
      'sk': 12,
 | 
						|
      'sl': 14,
 | 
						|
      'sma': 7,
 | 
						|
      'smi': 7,
 | 
						|
      'smj': 7,
 | 
						|
      'smn': 7,
 | 
						|
      'sms': 7,
 | 
						|
      'sn': 3,
 | 
						|
      'so': 3,
 | 
						|
      'sq': 3,
 | 
						|
      'sr': 11,
 | 
						|
      'ss': 3,
 | 
						|
      'ssy': 3,
 | 
						|
      'st': 3,
 | 
						|
      'sv': 3,
 | 
						|
      'sw': 3,
 | 
						|
      'syr': 3,
 | 
						|
      'ta': 3,
 | 
						|
      'te': 3,
 | 
						|
      'teo': 3,
 | 
						|
      'th': 0,
 | 
						|
      'ti': 4,
 | 
						|
      'tig': 3,
 | 
						|
      'tk': 3,
 | 
						|
      'tl': 4,
 | 
						|
      'tn': 3,
 | 
						|
      'to': 0,
 | 
						|
      'tr': 0,
 | 
						|
      'ts': 3,
 | 
						|
      'tzm': 22,
 | 
						|
      'uk': 11,
 | 
						|
      'ur': 3,
 | 
						|
      've': 3,
 | 
						|
      'vi': 0,
 | 
						|
      'vun': 3,
 | 
						|
      'wa': 4,
 | 
						|
      'wae': 3,
 | 
						|
      'wo': 0,
 | 
						|
      'xh': 3,
 | 
						|
      'xog': 3,
 | 
						|
      'yo': 0,
 | 
						|
      'zh': 0,
 | 
						|
      'zu': 3
 | 
						|
    };
 | 
						|
 | 
						|
    // utility functions for plural rules methods
 | 
						|
    function isIn(n, list) {
 | 
						|
      return list.indexOf(n) !== -1;
 | 
						|
    }
 | 
						|
    function isBetween(n, start, end) {
 | 
						|
      return start <= n && n <= end;
 | 
						|
    }
 | 
						|
 | 
						|
    // list of all plural rules methods:
 | 
						|
    // map an integer to the plural form name to use
 | 
						|
    var pluralRules = {
 | 
						|
      '0': function(n) {
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '1': function(n) {
 | 
						|
        if ((isBetween((n % 100), 3, 10)))
 | 
						|
          return 'few';
 | 
						|
        if (n === 0)
 | 
						|
          return 'zero';
 | 
						|
        if ((isBetween((n % 100), 11, 99)))
 | 
						|
          return 'many';
 | 
						|
        if (n == 2)
 | 
						|
          return 'two';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '2': function(n) {
 | 
						|
        if (n !== 0 && (n % 10) === 0)
 | 
						|
          return 'many';
 | 
						|
        if (n == 2)
 | 
						|
          return 'two';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '3': function(n) {
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '4': function(n) {
 | 
						|
        if ((isBetween(n, 0, 1)))
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '5': function(n) {
 | 
						|
        if ((isBetween(n, 0, 2)) && n != 2)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '6': function(n) {
 | 
						|
        if (n === 0)
 | 
						|
          return 'zero';
 | 
						|
        if ((n % 10) == 1 && (n % 100) != 11)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '7': function(n) {
 | 
						|
        if (n == 2)
 | 
						|
          return 'two';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '8': function(n) {
 | 
						|
        if ((isBetween(n, 3, 6)))
 | 
						|
          return 'few';
 | 
						|
        if ((isBetween(n, 7, 10)))
 | 
						|
          return 'many';
 | 
						|
        if (n == 2)
 | 
						|
          return 'two';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '9': function(n) {
 | 
						|
        if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19)))
 | 
						|
          return 'few';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '10': function(n) {
 | 
						|
        if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
 | 
						|
          return 'few';
 | 
						|
        if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '11': function(n) {
 | 
						|
        if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
 | 
						|
          return 'few';
 | 
						|
        if ((n % 10) === 0 ||
 | 
						|
            (isBetween((n % 10), 5, 9)) ||
 | 
						|
            (isBetween((n % 100), 11, 14)))
 | 
						|
          return 'many';
 | 
						|
        if ((n % 10) == 1 && (n % 100) != 11)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '12': function(n) {
 | 
						|
        if ((isBetween(n, 2, 4)))
 | 
						|
          return 'few';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '13': function(n) {
 | 
						|
        if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
 | 
						|
          return 'few';
 | 
						|
        if (n != 1 && (isBetween((n % 10), 0, 1)) ||
 | 
						|
            (isBetween((n % 10), 5, 9)) ||
 | 
						|
            (isBetween((n % 100), 12, 14)))
 | 
						|
          return 'many';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '14': function(n) {
 | 
						|
        if ((isBetween((n % 100), 3, 4)))
 | 
						|
          return 'few';
 | 
						|
        if ((n % 100) == 2)
 | 
						|
          return 'two';
 | 
						|
        if ((n % 100) == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '15': function(n) {
 | 
						|
        if (n === 0 || (isBetween((n % 100), 2, 10)))
 | 
						|
          return 'few';
 | 
						|
        if ((isBetween((n % 100), 11, 19)))
 | 
						|
          return 'many';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '16': function(n) {
 | 
						|
        if ((n % 10) == 1 && n != 11)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '17': function(n) {
 | 
						|
        if (n == 3)
 | 
						|
          return 'few';
 | 
						|
        if (n === 0)
 | 
						|
          return 'zero';
 | 
						|
        if (n == 6)
 | 
						|
          return 'many';
 | 
						|
        if (n == 2)
 | 
						|
          return 'two';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '18': function(n) {
 | 
						|
        if (n === 0)
 | 
						|
          return 'zero';
 | 
						|
        if ((isBetween(n, 0, 2)) && n !== 0 && n != 2)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '19': function(n) {
 | 
						|
        if ((isBetween(n, 2, 10)))
 | 
						|
          return 'few';
 | 
						|
        if ((isBetween(n, 0, 1)))
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '20': function(n) {
 | 
						|
        if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(
 | 
						|
            isBetween((n % 100), 10, 19) ||
 | 
						|
            isBetween((n % 100), 70, 79) ||
 | 
						|
            isBetween((n % 100), 90, 99)
 | 
						|
            ))
 | 
						|
          return 'few';
 | 
						|
        if ((n % 1000000) === 0 && n !== 0)
 | 
						|
          return 'many';
 | 
						|
        if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
 | 
						|
          return 'two';
 | 
						|
        if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '21': function(n) {
 | 
						|
        if (n === 0)
 | 
						|
          return 'zero';
 | 
						|
        if (n == 1)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '22': function(n) {
 | 
						|
        if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '23': function(n) {
 | 
						|
        if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0)
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      },
 | 
						|
      '24': function(n) {
 | 
						|
        if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
 | 
						|
          return 'few';
 | 
						|
        if (isIn(n, [2, 12]))
 | 
						|
          return 'two';
 | 
						|
        if (isIn(n, [1, 11]))
 | 
						|
          return 'one';
 | 
						|
        return 'other';
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    // return a function that gives the plural form name for a given integer
 | 
						|
    var index = locales2rules[lang.replace(/-.*$/, '')];
 | 
						|
    if (!(index in pluralRules)) {
 | 
						|
      console.warn('plural form unknown for [' + lang + ']');
 | 
						|
      return function() { return 'other'; };
 | 
						|
    }
 | 
						|
    return pluralRules[index];
 | 
						|
  }
 | 
						|
 | 
						|
  // pre-defined 'plural' macro
 | 
						|
  gMacros.plural = function(str, param, key, prop) {
 | 
						|
    var n = parseFloat(param);
 | 
						|
    if (isNaN(n))
 | 
						|
      return str;
 | 
						|
 | 
						|
    // TODO: support other properties (l20n still doesn't...)
 | 
						|
    if (prop != gTextProp)
 | 
						|
      return str;
 | 
						|
 | 
						|
    // initialize _pluralRules
 | 
						|
    if (!gMacros._pluralRules) {
 | 
						|
      gMacros._pluralRules = getPluralRules(gLanguage);
 | 
						|
    }
 | 
						|
    var index = '[' + gMacros._pluralRules(n) + ']';
 | 
						|
 | 
						|
    // try to find a [zero|one|two] key if it's defined
 | 
						|
    if (n === 0 && (key + '[zero]') in gL10nData) {
 | 
						|
      str = gL10nData[key + '[zero]'][prop];
 | 
						|
    } else if (n == 1 && (key + '[one]') in gL10nData) {
 | 
						|
      str = gL10nData[key + '[one]'][prop];
 | 
						|
    } else if (n == 2 && (key + '[two]') in gL10nData) {
 | 
						|
      str = gL10nData[key + '[two]'][prop];
 | 
						|
    } else if ((key + index) in gL10nData) {
 | 
						|
      str = gL10nData[key + index][prop];
 | 
						|
    } else if ((key + '[other]') in gL10nData) {
 | 
						|
      str = gL10nData[key + '[other]'][prop];
 | 
						|
    }
 | 
						|
 | 
						|
    return str;
 | 
						|
  };
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * l10n dictionary functions
 | 
						|
   */
 | 
						|
 | 
						|
  // fetch an l10n object, warn if not found, apply `args' if possible
 | 
						|
  function getL10nData(key, args, fallback) {
 | 
						|
    var data = gL10nData[key];
 | 
						|
    if (!data) {
 | 
						|
      console.warn('#' + key + ' is undefined.');
 | 
						|
      if (!fallback) {
 | 
						|
        return null;
 | 
						|
      }
 | 
						|
      data = fallback;
 | 
						|
    }
 | 
						|
 | 
						|
    /** This is where l10n expressions should be processed.
 | 
						|
      * The plan is to support C-style expressions from the l20n project;
 | 
						|
      * until then, only two kinds of simple expressions are supported:
 | 
						|
      *   {[ index ]} and {{ arguments }}.
 | 
						|
      */
 | 
						|
    var rv = {};
 | 
						|
    for (var prop in data) {
 | 
						|
      var str = data[prop];
 | 
						|
      str = substIndexes(str, args, key, prop);
 | 
						|
      str = substArguments(str, args, key);
 | 
						|
      rv[prop] = str;
 | 
						|
    }
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // replace {[macros]} with their values
 | 
						|
  function substIndexes(str, args, key, prop) {
 | 
						|
    var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/;
 | 
						|
    var reMatch = reIndex.exec(str);
 | 
						|
    if (!reMatch || !reMatch.length)
 | 
						|
      return str;
 | 
						|
 | 
						|
    // an index/macro has been found
 | 
						|
    // Note: at the moment, only one parameter is supported
 | 
						|
    var macroName = reMatch[1];
 | 
						|
    var paramName = reMatch[2];
 | 
						|
    var param;
 | 
						|
    if (args && paramName in args) {
 | 
						|
      param = args[paramName];
 | 
						|
    } else if (paramName in gL10nData) {
 | 
						|
      param = gL10nData[paramName];
 | 
						|
    }
 | 
						|
 | 
						|
    // there's no macro parser yet: it has to be defined in gMacros
 | 
						|
    if (macroName in gMacros) {
 | 
						|
      var macro = gMacros[macroName];
 | 
						|
      str = macro(str, param, key, prop);
 | 
						|
    }
 | 
						|
    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 = '';
 | 
						|
      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;
 | 
						|
      }
 | 
						|
 | 
						|
      str = str.substring(0, match.index) + sub +
 | 
						|
            str.substr(match.index + match[0].length);
 | 
						|
      match = reArgs.exec(str);
 | 
						|
    }
 | 
						|
    return str;
 | 
						|
  }
 | 
						|
 | 
						|
  // translate an HTML element
 | 
						|
  function translateElement(element) {
 | 
						|
    var l10n = getL10nAttributes(element);
 | 
						|
    if (!l10n.id)
 | 
						|
      return;
 | 
						|
 | 
						|
    // get the related l10n object
 | 
						|
    var data = getL10nData(l10n.id, l10n.args);
 | 
						|
    if (!data) {
 | 
						|
      console.warn('#' + l10n.id + ' is undefined.');
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // translate element (TODO: security checks?)
 | 
						|
    if (data[gTextProp]) { // XXX
 | 
						|
      if (getChildElementCount(element) === 0) {
 | 
						|
        element[gTextProp] = data[gTextProp];
 | 
						|
      } else {
 | 
						|
        // this element has element children: replace the content of the first
 | 
						|
        // (non-empty) child textNode and clear other child textNodes
 | 
						|
        var children = element.childNodes;
 | 
						|
        var found = false;
 | 
						|
        for (var i = 0, l = children.length; i < l; i++) {
 | 
						|
          if (children[i].nodeType === 3 && /\S/.test(children[i].nodeValue)) {
 | 
						|
            if (found) {
 | 
						|
              children[i].nodeValue = '';
 | 
						|
            } else {
 | 
						|
              children[i].nodeValue = data[gTextProp];
 | 
						|
              found = true;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
        // if no (non-empty) textNode is found, insert a textNode before the
 | 
						|
        // first element child.
 | 
						|
        if (!found) {
 | 
						|
          var textNode = document.createTextNode(data[gTextProp]);
 | 
						|
          element.insertBefore(textNode, element.firstChild);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      delete data[gTextProp];
 | 
						|
    }
 | 
						|
 | 
						|
    for (var k in data) {
 | 
						|
      element[k] = data[k];
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // webkit browsers don't currently support 'children' on SVG elements...
 | 
						|
  function getChildElementCount(element) {
 | 
						|
    if (element.children) {
 | 
						|
      return element.children.length;
 | 
						|
    }
 | 
						|
    if (typeof element.childElementCount !== 'undefined') {
 | 
						|
      return element.childElementCount;
 | 
						|
    }
 | 
						|
    var count = 0;
 | 
						|
    for (var i = 0; i < element.childNodes.length; i++) {
 | 
						|
      count += element.nodeType === 1 ? 1 : 0;
 | 
						|
    }
 | 
						|
    return count;
 | 
						|
  }
 | 
						|
 | 
						|
  // translate an HTML subtree
 | 
						|
  function translateFragment(element) {
 | 
						|
    element = element || document.documentElement;
 | 
						|
 | 
						|
    // check all translatable children (= w/ a `data-l10n-id' attribute)
 | 
						|
    var children = getTranslatableChildren(element);
 | 
						|
    var elementCount = children.length;
 | 
						|
    for (var i = 0; i < elementCount; i++) {
 | 
						|
      translateElement(children[i]);
 | 
						|
    }
 | 
						|
 | 
						|
    // 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;
 | 
						|
      if (index > 0) { // An attribute has been specified
 | 
						|
        prop = key.substr(index + 1);
 | 
						|
        key = key.substring(0, index);
 | 
						|
      }
 | 
						|
      var fallback;
 | 
						|
      if (fallbackString) {
 | 
						|
        fallback = {};
 | 
						|
        fallback[prop] = fallbackString;
 | 
						|
      }
 | 
						|
      var data = getL10nData(key, args, fallback);
 | 
						|
      if (data && prop in data) {
 | 
						|
        return data[prop];
 | 
						|
      }
 | 
						|
      return '{{' + key + '}}';
 | 
						|
    },
 | 
						|
 | 
						|
    // debug
 | 
						|
    getData: function() { return gL10nData; },
 | 
						|
    getText: function() { return gTextData; },
 | 
						|
 | 
						|
    // get|set the document language
 | 
						|
    getLanguage: function() { return gLanguage; },
 | 
						|
    setLanguage: function(lang) { loadLocale(lang, 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';
 | 
						|
    },
 | 
						|
 | 
						|
    // translate an element or document fragment
 | 
						|
    translate: translateFragment,
 | 
						|
 | 
						|
    // this can be used to prevent race conditions
 | 
						|
    getReadyState: function() { return gReadyState; },
 | 
						|
    ready: function(callback) {
 | 
						|
      if (!callback) {
 | 
						|
        return;
 | 
						|
      } else if (gReadyState == 'complete' || gReadyState == 'interactive') {
 | 
						|
        window.setTimeout(callback);
 | 
						|
      } else if (document.addEventListener) {
 | 
						|
        document.addEventListener('localized', callback);
 | 
						|
      } else if (document.attachEvent) {
 | 
						|
        document.documentElement.attachEvent('onpropertychange', function(e) {
 | 
						|
          if (e.propertyName === 'localized') {
 | 
						|
            callback();
 | 
						|
          }
 | 
						|
        });
 | 
						|
      }
 | 
						|
    }
 | 
						|
  };
 | 
						|
}) (window, document);
 |