/** Copyright © 2011-2012 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;
  - Adds fallback argument to the getL10nData;
  - Removes consoleLog and simplifies consoleWarn;
  - Removes window._ assignment.

*/ /*jshint browser: true, devel: true, es5: 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';

// read-only setting -- we recommend to load l10n resources synchronously
var gAsyncResourceLoading = true;

// debug helpers
function consoleWarn(message) {
  console.log('[l10n] ' + message);
};

/**
 * 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 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) {
      consoleWarn('could not parse arguments for #' + l10nId);
    }
  }
  return { id: l10nId, args: args };
}

function fireL10nReadyEvent(lang) {
  var evtObject = document.createEvent('Event');
  evtObject.initEvent('localized', false, false);
  evtObject.language = lang;
  window.dispatchEvent(evtObject);
}

/**
 * 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) {
      loadResource(url, function(content) {
        parseRawLines(content, false); // don't allow recursive imports
      }, false, false); // load synchronously
    }

    // fill the dictionary
    parseRawLines(text, true);
    return dictionary;
  }

  // load the specified resource file
  function loadResource(url, onSuccess, onFailure, asynchronous) {
    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) {
          if (onSuccess)
            onSuccess(xhr.responseText);
        } else {
          if (onFailure)
            onFailure();
        }
      }
    };
    xhr.send(null);
  }

  // load and parse l10n data (warning: global variables are used here)
  loadResource(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) {
  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) {
    consoleWarn('no resource to load, 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) {
      if (callback) // execute the [optional] callback
        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() {
        consoleWarn(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
      consoleWarn('"' + 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)) {
    consoleWarn('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];
  }

  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) {
    consoleWarn('#' + key + ' missing for [' + gLanguage + ']');
    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);
    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) {
  var reArgs = /\{\{\s*([a-zA-Z\.]+)\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 (arg in args) {
      sub = args[arg];
    } else if (arg in gL10nData) {
      sub = gL10nData[arg][gTextProp];
    } else {
      consoleWarn('could not find argument {{' + arg + '}}');
      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) {
    consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']');
    return;
  }

  // translate element (TODO: security checks?)
  // for the node content, replace the content of the first child textNode
  // and clear other child textNodes
  if (data[gTextProp]) { // XXX
    if (element.children.length === 0) {
      element[gTextProp] = data[gTextProp];
    } else {
      var children = element.childNodes,
          found = false;
      for (var i = 0, l = children.length; i < l; i++) {
        if (children[i].nodeType === 3 &&
            /\S/.test(children[i].textContent)) { // XXX
          // using nodeValue seems cross-browser
          if (found) {
            children[i].nodeValue = '';
          } else {
            children[i].nodeValue = data[gTextProp];
            found = true;
          }
        }
      }
      if (!found) {
        consoleWarn('unexpected error, could not translate element content');
      }
    }
    delete data[gTextProp];
  }

  for (var k in data) {
    element[k] = data[k];
  }
}

// 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, fallback) {
    var data = getL10nData(key, args, {textContent: fallback});
    if (data) { // XXX double-check this
      return 'textContent' in data ? data.textContent : '';
    }
    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; }
};

}) (window, document);