http://git-wip-us.apache.org/repos/asf/cordova-plugin-globalization/blob/86e33d3f/www/firefoxos/l10n.js
----------------------------------------------------------------------
diff --git a/www/firefoxos/l10n.js b/www/firefoxos/l10n.js
index a2cd328..1253831 100644
--- a/www/firefoxos/l10n.js
+++ b/www/firefoxos/l10n.js
@@ -21,1597 +21,1568 @@
 
 /* global unescape */
 
-(function(window, undefined) {
-  'use strict';
+(function (window, undefined) { // eslint-disable-line 
no-shadow-restricted-names
+    'use strict';
+
+    /* jshint validthis:true */
+    function L10nError (message, id, loc) {
+        this.name = 'L10nError';
+        this.message = message;
+        this.id = id;
+        this.loc = loc;
+    }
+    L10nError.prototype = Object.create(Error.prototype);
+    L10nError.prototype.constructor = L10nError;
+
+    /* jshint browser:true */
+
+    var io = {
+        load: function load (url, callback, sync) {
+            var xhr = new XMLHttpRequest(); // eslint-disable-line no-undef
+
+            if (xhr.overrideMimeType) {
+                xhr.overrideMimeType('text/plain');
+            }
+
+            xhr.open('GET', url, !sync);
+
+            xhr.addEventListener('load', function io_load (e) {
+                if (e.target.status === 200 || e.target.status === 0) {
+                    callback(null, e.target.responseText);
+                } else {
+                    callback(new L10nError('Not found: ' + url));
+                }
+            });
+            xhr.addEventListener('error', callback);
+            xhr.addEventListener('timeout', callback);
+
+            // the app: protocol throws on 404, see https://bugzil.la/827243
+            try {
+                xhr.send(null);
+            } catch (e) {
+                callback(new L10nError('Not found: ' + url));
+            }
+        },
+
+        loadJSON: function loadJSON (url, callback) {
+            var xhr = new XMLHttpRequest(); // eslint-disable-line no-undef
+
+            if (xhr.overrideMimeType) {
+                xhr.overrideMimeType('application/json');
+            }
+
+            xhr.open('GET', url);
+
+            xhr.responseType = 'json';
+            xhr.addEventListener('load', function io_loadjson (e) {
+                if (e.target.status === 200 || e.target.status === 0) {
+                    callback(null, e.target.response);
+                } else {
+                    callback(new L10nError('Not found: ' + url));
+                }
+            });
+            xhr.addEventListener('error', callback);
+            xhr.addEventListener('timeout', callback);
+
+            // the app: protocol throws on 404, see https://bugzil.la/827243
+            try {
+                xhr.send(null);
+            } catch (e) {
+                callback(new L10nError('Not found: ' + url));
+            }
+        }
+    };
 
-  /* jshint validthis:true */
-  function L10nError(message, id, loc) {
-    this.name = 'L10nError';
-    this.message = message;
-    this.id = id;
-    this.loc = loc;
-  }
-  L10nError.prototype = Object.create(Error.prototype);
-  L10nError.prototype.constructor = L10nError;
+    function EventEmitter () {}
 
+    EventEmitter.prototype.emit = function ee_emit () {
+        if (!this._listeners) {
+            return;
+        }
 
-  /* jshint browser:true */
+        var args = Array.prototype.slice.call(arguments);
+        var type = args.shift();
+        if (!this._listeners[type]) {
+            return;
+        }
 
-  var io = {
-    load: function load(url, callback, sync) {
-      var xhr = new XMLHttpRequest();
+        var typeListeners = this._listeners[type].slice();
+        for (var i = 0; i < typeListeners.length; i++) {
+            typeListeners[i].apply(this, args);
+        }
+    };
 
-      if (xhr.overrideMimeType) {
-        xhr.overrideMimeType('text/plain');
-      }
+    EventEmitter.prototype.addEventListener = function ee_add (type, listener) 
{
+        if (!this._listeners) {
+            this._listeners = {};
+        }
+        if (!(type in this._listeners)) {
+            this._listeners[type] = [];
+        }
+        this._listeners[type].push(listener);
+    };
 
-      xhr.open('GET', url, !sync);
+    EventEmitter.prototype.removeEventListener = function ee_rm (type, 
listener) {
+        if (!this._listeners) {
+            return;
+        }
 
-      xhr.addEventListener('load', function io_load(e) {
-        if (e.target.status === 200 || e.target.status === 0) {
-          callback(null, e.target.responseText);
-        } else {
-          callback(new L10nError('Not found: ' + url));
+        var typeListeners = this._listeners[type];
+        var pos = typeListeners.indexOf(listener);
+        if (pos === -1) {
+            return;
         }
-      });
-      xhr.addEventListener('error', callback);
-      xhr.addEventListener('timeout', callback);
 
-      // the app: protocol throws on 404, see https://bugzil.la/827243
-      try {
-        xhr.send(null);
-      } catch (e) {
-        callback(new L10nError('Not found: ' + url));
-      }
-    },
+        typeListeners.splice(pos, 1);
+    };
 
-    loadJSON: function loadJSON(url, callback) {
-      var xhr = new XMLHttpRequest();
+    function getPluralRule (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 typeof n === typeof start && start <= n && n <= end;
+        }
+
+        // list of all plural rules methods:
+        // map an integer to the plural form name to use
+        var pluralRules = {
+            '0': function () {
+                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)) {
+            return function () { return 'other'; };
+        }
+        return pluralRules[index];
+    }
+
+    var parsePatterns;
+
+    function parse (ctx, source) {
+        var ast = {};
+        /* eslint-disable no-useless-escape */
+        if (!parsePatterns) {
+            parsePatterns = {
+                comment: /^\s*#|^\s*$/,
+                entity: /^([^=\s]+)\s*=\s*(.+)$/,
+                multiline: /[^\\]\\$/,
+                macro: /\{\[\s*(\w+)\(([^\)]*)\)\s*\]\}/i,
+                unicode: /\\u([0-9a-fA-F]{1,4})/g,
+                entries: /[\r\n]+/,
+                controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g
+            };
+        }
+        /* eslint-enable no-useless-escape */
+        var entries = source.split(parsePatterns.entries);
+        for (var i = 0; i < entries.length; i++) {
+            var line = entries[i];
+
+            if (parsePatterns.comment.test(line)) {
+                continue;
+            }
+
+            while (parsePatterns.multiline.test(line) && i < entries.length) {
+                line = line.slice(0, -1) + entries[++i].trim();
+            }
+
+            var entityMatch = line.match(parsePatterns.entity);
+            if (entityMatch) {
+                try {
+                    parseEntity(entityMatch[1], entityMatch[2], ast);
+                } catch (e) {
+                    if (ctx) {
+                        ctx._emitter.emit('error', e);
+                    } else {
+                        throw e;
+                    }
+                }
+            }
+        }
+        return ast;
+    }
+
+    function setEntityValue (id, attr, key, value, ast) {
+        var obj = ast;
+        var prop = id;
+
+        if (attr) {
+            if (!(id in obj)) {
+                obj[id] = {};
+            }
+            if (typeof (obj[id]) === 'string') {
+                obj[id] = {'_': obj[id]};
+            }
+            obj = obj[id];
+            prop = attr;
+        }
+
+        if (!key) {
+            obj[prop] = value;
+            return;
+        }
+
+        if (!(prop in obj)) {
+            obj[prop] = {'_': {}};
+        } else if (typeof (obj[prop]) === 'string') {
+            obj[prop] = {'_index': parseMacro(obj[prop]), '_': {}};
+        }
+        obj[prop]._[key] = value;
+    }
+
+    function parseEntity (id, value, ast) {
+        var name, key;
+
+        var pos = id.indexOf('[');
+        if (pos !== -1) {
+            name = id.substr(0, pos);
+            key = id.substring(pos + 1, id.length - 1);
+        } else {
+            name = id;
+            key = null;
+        }
 
-      if (xhr.overrideMimeType) {
-        xhr.overrideMimeType('application/json');
-      }
+        var nameElements = name.split('.');
 
-      xhr.open('GET', url);
+        if (nameElements.length > 2) {
+            throw new Error('Error in ID: "' + name + '".' +
+                      ' Nested attributes are not supported.');
+        }
 
-      xhr.responseType = 'json';
-      xhr.addEventListener('load', function io_loadjson(e) {
-        if (e.target.status === 200 || e.target.status === 0) {
-          callback(null, e.target.response);
+        var attr;
+        if (nameElements.length > 1) {
+            name = nameElements[0];
+            attr = nameElements[1];
         } else {
-          callback(new L10nError('Not found: ' + url));
-        }
-      });
-      xhr.addEventListener('error', callback);
-      xhr.addEventListener('timeout', callback);
-
-      // the app: protocol throws on 404, see https://bugzil.la/827243
-      try {
-        xhr.send(null);
-      } catch (e) {
-        callback(new L10nError('Not found: ' + url));
-      }
-    }
-  };
-
-  function EventEmitter() {}
+            attr = null;
+        }
 
-  EventEmitter.prototype.emit = function ee_emit() {
-    if (!this._listeners) {
-      return;
+        setEntityValue(name, attr, key, unescapeString(value), ast);
     }
 
-    var args = Array.prototype.slice.call(arguments);
-    var type = args.shift();
-    if (!this._listeners[type]) {
-      return;
+    function unescapeControlCharacters (str) {
+        return str.replace(parsePatterns.controlChars, '$1');
     }
 
-    var typeListeners = this._listeners[type].slice();
-    for (var i = 0; i < typeListeners.length; i++) {
-      typeListeners[i].apply(this, args);
+    function unescapeUnicode (str) {
+        return str.replace(parsePatterns.unicode, function (match, token) {
+            return unescape('%u' + '0000'.slice(token.length) + token);
+        });
     }
-  };
 
-  EventEmitter.prototype.addEventListener = function ee_add(type, listener) {
-    if (!this._listeners) {
-      this._listeners = {};
-    }
-    if (!(type in this._listeners)) {
-      this._listeners[type] = [];
+    function unescapeString (str) {
+        if (str.lastIndexOf('\\') !== -1) {
+            str = unescapeControlCharacters(str);
+        }
+        return unescapeUnicode(str);
     }
-    this._listeners[type].push(listener);
-  };
 
-  EventEmitter.prototype.removeEventListener = function ee_rm(type, listener) {
-    if (!this._listeners) {
-      return;
+    function parseMacro (str) {
+        var match = str.match(parsePatterns.macro);
+        if (!match) {
+            throw new L10nError('Malformed macro');
+        }
+        return [match[1], match[2]];
     }
 
-    var typeListeners = this._listeners[type];
-    var pos = typeListeners.indexOf(listener);
-    if (pos === -1) {
-      return;
-    }
+    var MAX_PLACEABLE_LENGTH = 2500;
+    var MAX_PLACEABLES = 100;
+    var rePlaceables = /\{\{\s*(.+?)\s*\}\}/g;
 
-    typeListeners.splice(pos, 1);
-  };
-
-
-  function getPluralRule(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
+    function Entity (id, node, env) {
+        this.id = id;
+        this.env = env;
+        // the dirty guard prevents cyclic or recursive references from other
+        // Entities; see Entity.prototype.resolve
+        this.dirty = false;
+        if (typeof node === 'string') {
+            this.value = node;
+        } else {
+            // it's either a hash or it has attrs, or both
+            for (var key in node) {
+                if (node.hasOwnProperty(key) && key[0] !== '_') {
+                    if (!this.attributes) {
+                        this.attributes = {};
+                    }
+                    this.attributes[key] = new Entity(this.id + '.' + key, 
node[key],
+                        env);
+                }
+            }
+            this.value = node._ || null;
+            this.index = node._index;
+        }
+    }
+
+    Entity.prototype.resolve = function E_resolve (ctxdata) {
+        if (this.dirty) {
+            return undefined;
+        }
+
+        this.dirty = true;
+        var val;
+        // if resolve fails, we want the exception to bubble up and stop the 
whole
+        // resolving process;  however, we still need to clean up the dirty 
flag
+        try {
+            val = resolve(ctxdata, this.env, this.value, this.index);
+        } finally {
+            this.dirty = false;
+        }
+        return val;
     };
 
-    // utility functions for plural rules methods
-    function isIn(n, list) {
-      return list.indexOf(n) !== -1;
-    }
-    function isBetween(n, start, end) {
-      return typeof n === typeof start && start <= n && n <= end;
-    }
-
-    // list of all plural rules methods:
-    // map an integer to the plural form name to use
-    var pluralRules = {
-      '0': function() {
-        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';
+    Entity.prototype.toString = function E_toString (ctxdata) {
+        try {
+            return this.resolve(ctxdata);
+        } catch (e) {
+            return undefined;
         }
-        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)) {
-      return function() { return 'other'; };
-    }
-    return pluralRules[index];
-  }
-
-
-
-
-  var parsePatterns;
-
-  function parse(ctx, source) {
-    var ast = {};
-
-    if (!parsePatterns) {
-      parsePatterns = {
-        comment: /^\s*#|^\s*$/,
-        entity: /^([^=\s]+)\s*=\s*(.+)$/,
-        multiline: /[^\\]\\$/,
-        macro: /\{\[\s*(\w+)\(([^\)]*)\)\s*\]\}/i,
-        unicode: /\\u([0-9a-fA-F]{1,4})/g,
-        entries: /[\r\n]+/,
-        controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g
-      };
-    }
+    Entity.prototype.valueOf = function E_valueOf (ctxdata) {
+        if (!this.attributes) {
+            return this.toString(ctxdata);
+        }
 
-    var entries = source.split(parsePatterns.entries);
-    for (var i = 0; i < entries.length; i++) {
-      var line = entries[i];
+        var entity = {
+            value: this.toString(ctxdata),
+            attributes: {}
+        };
 
-      if (parsePatterns.comment.test(line)) {
-        continue;
-      }
+        for (var key in this.attributes) {
+            if (this.attributes.hasOwnProperty(key)) {
+                entity.attributes[key] = 
this.attributes[key].toString(ctxdata);
+            }
+        }
 
-      while (parsePatterns.multiline.test(line) && i < entries.length) {
-        line = line.slice(0, -1) + entries[++i].trim();
-      }
+        return entity;
+    };
 
-      var entityMatch = line.match(parsePatterns.entity);
-      if (entityMatch) {
-        try {
-          parseEntity(entityMatch[1], entityMatch[2], ast);
-        } catch (e) {
-          if (ctx) {
-            ctx._emitter.emit('error', e);
-          } else {
-            throw e;
-          }
+    function subPlaceable (ctxdata, env, match, id) {
+        if (ctxdata && ctxdata.hasOwnProperty(id) &&
+        (typeof ctxdata[id] === 'string' ||
+         (typeof ctxdata[id] === 'number' && !isNaN(ctxdata[id])))) {
+            return ctxdata[id];
+        }
+
+        if (env.hasOwnProperty(id)) {
+            if (!(env[id] instanceof Entity)) {
+                env[id] = new Entity(id, env[id], env);
+            }
+            var value = env[id].resolve(ctxdata);
+            if (typeof value === 'string') {
+                // prevent Billion Laughs attacks
+                if (value.length >= MAX_PLACEABLE_LENGTH) {
+                    throw new L10nError('Too many characters in placeable (' +
+                              value.length + ', max allowed is ' +
+                              MAX_PLACEABLE_LENGTH + ')');
+                }
+                return value;
+            }
         }
-      }
-    }
-    return ast;
-  }
-
-  function setEntityValue(id, attr, key, value, ast) {
-    var obj = ast;
-    var prop = id;
-
-    if (attr) {
-      if (!(id in obj)) {
-        obj[id] = {};
-      }
-      if (typeof(obj[id]) === 'string') {
-        obj[id] = {'_': obj[id]};
-      }
-      obj = obj[id];
-      prop = attr;
+        return match;
     }
 
-    if (!key) {
-      obj[prop] = value;
-      return;
+    function interpolate (ctxdata, env, str) {
+        var placeablesCount = 0;
+        var value = str.replace(rePlaceables, function (match, id) {
+            // prevent Quadratic Blowup attacks
+            if (placeablesCount++ >= MAX_PLACEABLES) {
+                throw new L10nError('Too many placeables (' + placeablesCount +
+                            ', max allowed is ' + MAX_PLACEABLES + ')');
+            }
+            return subPlaceable(ctxdata, env, match, id);
+        });
+        placeablesCount = 0;
+        return value;
     }
 
-    if (!(prop in obj)) {
-      obj[prop] = {'_': {}};
-    } else if (typeof(obj[prop]) === 'string') {
-      obj[prop] = {'_index': parseMacro(obj[prop]), '_': {}};
-    }
-    obj[prop]._[key] = value;
-  }
-
-  function parseEntity(id, value, ast) {
-    var name, key;
-
-    var pos = id.indexOf('[');
-    if (pos !== -1) {
-      name = id.substr(0, pos);
-      key = id.substring(pos + 1, id.length - 1);
-    } else {
-      name = id;
-      key = null;
-    }
+    function resolve (ctxdata, env, expr, index) {
+        if (typeof expr === 'string') {
+            return interpolate(ctxdata, env, expr);
+        }
 
-    var nameElements = name.split('.');
+        if (typeof expr === 'boolean' ||
+        typeof expr === 'number' ||
+        !expr) {
+            return expr;
+        }
 
-    if (nameElements.length > 2) {
-      throw new Error('Error in ID: "' + name + '".' +
-                      ' Nested attributes are not supported.');
-    }
+        // otherwise, it's a dict
 
-    var attr;
-    if (nameElements.length > 1) {
-      name = nameElements[0];
-      attr = nameElements[1];
-    } else {
-      attr = null;
-    }
+        if (index && ctxdata && ctxdata.hasOwnProperty(index[1])) {
+            var argValue = ctxdata[index[1]];
 
-    setEntityValue(name, attr, key, unescapeString(value), ast);
-  }
+            // special cases for zero, one, two if they are defined on the hash
+            if (argValue === 0 && 'zero' in expr) {
+                return resolve(ctxdata, env, expr.zero);
+            }
+            if (argValue === 1 && 'one' in expr) {
+                return resolve(ctxdata, env, expr.one);
+            }
+            if (argValue === 2 && 'two' in expr) {
+                return resolve(ctxdata, env, expr.two);
+            }
 
-  function unescapeControlCharacters(str) {
-    return str.replace(parsePatterns.controlChars, '$1');
-  }
+            var selector = env.__plural(argValue);
+            if (expr.hasOwnProperty(selector)) {
+                return resolve(ctxdata, env, expr[selector]);
+            }
+        }
 
-  function unescapeUnicode(str) {
-    return str.replace(parsePatterns.unicode, function(match, token) {
-      return unescape('%u' + '0000'.slice(token.length) + token);
-    });
-  }
+        // if there was no index or no selector was found, try 'other'
+        if ('other' in expr) {
+            return resolve(ctxdata, env, expr.other);
+        }
 
-  function unescapeString(str) {
-    if (str.lastIndexOf('\\') !== -1) {
-      str = unescapeControlCharacters(str);
+        return undefined;
     }
-    return unescapeUnicode(str);
-  }
 
-  function parseMacro(str) {
-    var match = str.match(parsePatterns.macro);
-    if (!match) {
-      throw new L10nError('Malformed macro');
-    }
-    return [match[1], match[2]];
-  }
-
-
-
-  var MAX_PLACEABLE_LENGTH = 2500;
-  var MAX_PLACEABLES = 100;
-  var rePlaceables = /\{\{\s*(.+?)\s*\}\}/g;
-
-  function Entity(id, node, env) {
-    this.id = id;
-    this.env = env;
-    // the dirty guard prevents cyclic or recursive references from other
-    // Entities; see Entity.prototype.resolve
-    this.dirty = false;
-    if (typeof node === 'string') {
-      this.value = node;
-    } else {
-      // it's either a hash or it has attrs, or both
-      for (var key in node) {
-        if (node.hasOwnProperty(key) && key[0] !== '_') {
-          if (!this.attributes) {
-            this.attributes = {};
-          }
-          this.attributes[key] = new Entity(this.id + '.' + key, node[key],
-                                            env);
-        }
-      }
-      this.value = node._ || null;
-      this.index = node._index;
+    function compile (env, ast) {
+        env = env || {};
+        for (var id in ast) {
+            if (ast.hasOwnProperty(id)) {
+                env[id] = new Entity(id, ast[id], env);
+            }
+        }
+        return env;
     }
-  }
 
-  Entity.prototype.resolve = function E_resolve(ctxdata) {
-    if (this.dirty) {
-      return undefined;
+    function Locale (id, ctx) {
+        this.id = id;
+        this.ctx = ctx;
+        this.isReady = false;
+        this.entries = {
+            __plural: getPluralRule(id)
+        };
     }
 
-    this.dirty = true;
-    var val;
-    // if resolve fails, we want the exception to bubble up and stop the whole
-    // resolving process;  however, we still need to clean up the dirty flag
-    try {
-      val = resolve(ctxdata, this.env, this.value, this.index);
-    } finally {
-      this.dirty = false;
-    }
-    return val;
-  };
-
-  Entity.prototype.toString = function E_toString(ctxdata) {
-    try {
-      return this.resolve(ctxdata);
-    } catch (e) {
-      return undefined;
-    }
-  };
+    Locale.prototype.getEntry = function L_getEntry (id) {
 
-  Entity.prototype.valueOf = function E_valueOf(ctxdata) {
-    if (!this.attributes) {
-      return this.toString(ctxdata);
-    }
+        var entries = this.entries;
 
-    var entity = {
-      value: this.toString(ctxdata),
-      attributes: {}
-    };
+        if (!entries.hasOwnProperty(id)) {
+            return undefined;
+        }
 
-    for (var key in this.attributes) {
-      if (this.attributes.hasOwnProperty(key)) {
-        entity.attributes[key] = this.attributes[key].toString(ctxdata);
-      }
-    }
+        if (entries[id] instanceof Entity) {
+            return entries[id];
+        }
+
+        return entries[id] = new Entity(id, entries[id], entries); // 
eslint-disable-line no-return-assign
+    };
 
-    return entity;
-  };
+    Locale.prototype.build = function L_build (callback) {
+        var sync = !callback;
+        var ctx = this.ctx;
+        var self = this;
 
-  function subPlaceable(ctxdata, env, match, id) {
-    if (ctxdata && ctxdata.hasOwnProperty(id) &&
-        (typeof ctxdata[id] === 'string' ||
-         (typeof ctxdata[id] === 'number' && !isNaN(ctxdata[id])))) {
-      return ctxdata[id];
-    }
+        var l10nLoads = ctx.resLinks.length;
 
-    if (env.hasOwnProperty(id)) {
-      if (!(env[id] instanceof Entity)) {
-        env[id] = new Entity(id, env[id], env);
-      }
-      var value = env[id].resolve(ctxdata);
-      if (typeof value === 'string') {
-        // prevent Billion Laughs attacks
-        if (value.length >= MAX_PLACEABLE_LENGTH) {
-          throw new L10nError('Too many characters in placeable (' +
-                              value.length + ', max allowed is ' +
-                              MAX_PLACEABLE_LENGTH + ')');
+        function onL10nLoaded (err) {
+            if (err) {
+                ctx._emitter.emit('error', err);
+            }
+            if (--l10nLoads <= 0) {
+                self.isReady = true;
+                if (callback) {
+                    callback();
+                }
+            }
         }
-        return value;
-      }
-    }
-    return match;
-  }
-
-  function interpolate(ctxdata, env, str) {
-    var placeablesCount = 0;
-    var value = str.replace(rePlaceables, function(match, id) {
-      // prevent Quadratic Blowup attacks
-      if (placeablesCount++ >= MAX_PLACEABLES) {
-        throw new L10nError('Too many placeables (' + placeablesCount +
-                            ', max allowed is ' + MAX_PLACEABLES + ')');
-      }
-      return subPlaceable(ctxdata, env, match, id);
-    });
-    placeablesCount = 0;
-    return value;
-  }
-
-  function resolve(ctxdata, env, expr, index) {
-    if (typeof expr === 'string') {
-      return interpolate(ctxdata, env, expr);
-    }
 
-    if (typeof expr === 'boolean' ||
-        typeof expr === 'number' ||
-        !expr) {
-      return expr;
-    }
+        if (l10nLoads === 0) {
+            onL10nLoaded();
+            return;
+        }
 
-    // otherwise, it's a dict
-
-    if (index && ctxdata && ctxdata.hasOwnProperty(index[1])) {
-      var argValue = ctxdata[index[1]];
-
-      // special cases for zero, one, two if they are defined on the hash
-      if (argValue === 0 && 'zero' in expr) {
-        return resolve(ctxdata, env, expr.zero);
-      }
-      if (argValue === 1 && 'one' in expr) {
-        return resolve(ctxdata, env, expr.one);
-      }
-      if (argValue === 2 && 'two' in expr) {
-        return resolve(ctxdata, env, expr.two);
-      }
-
-      var selector = env.__plural(argValue);
-      if (expr.hasOwnProperty(selector)) {
-        return resolve(ctxdata, env, expr[selector]);
-      }
-    }
+        function onJSONLoaded (err, json) {
+            if (!err && json) {
+                self.addAST(json);
+            }
+            onL10nLoaded(err);
+        }
 
-    // if there was no index or no selector was found, try 'other'
-    if ('other' in expr) {
-      return resolve(ctxdata, env, expr.other);
-    }
+        function onPropLoaded (err, source) {
+            if (!err && source) {
+                var ast = parse(ctx, source);
+                self.addAST(ast);
+            }
+            onL10nLoaded(err);
+        }
 
-    return undefined;
-  }
+        for (var i = 0; i < ctx.resLinks.length; i++) {
+            var path = ctx.resLinks[i].replace('{{locale}}', this.id);
+            var type = path.substr(path.lastIndexOf('.') + 1);
 
-  function compile(env, ast) {
-    env = env || {};
-    for (var id in ast) {
-      if (ast.hasOwnProperty(id)) {
-        env[id] = new Entity(id, ast[id], env);
-      }
-    }
-    return env;
-  }
+            switch (type) {
+            case 'json':
+                io.loadJSON(path, onJSONLoaded, sync);
+                break;
+            case 'properties':
+                io.load(path, onPropLoaded, sync);
+                break;
+            }
+        }
+    };
 
+    Locale.prototype.addAST = function (ast) {
+        for (var id in ast) {
+            if (ast.hasOwnProperty(id)) {
+                this.entries[id] = ast[id];
+            }
+        }
+    };
 
+    Locale.prototype.getEntity = function (id, ctxdata) {
+        var entry = this.getEntry(id);
 
-  function Locale(id, ctx) {
-    this.id = id;
-    this.ctx = ctx;
-    this.isReady = false;
-    this.entries = {
-      __plural: getPluralRule(id)
+        if (!entry) {
+            return null;
+        }
+        return entry.valueOf(ctxdata);
     };
-  }
 
-  Locale.prototype.getEntry = function L_getEntry(id) {
-    /* jshint -W093 */
+    function Context (id) {
 
-    var entries = this.entries;
+        this.id = id;
+        this.isReady = false;
+        this.isLoading = false;
 
-    if (!entries.hasOwnProperty(id)) {
-      return undefined;
-    }
+        this.supportedLocales = [];
+        this.resLinks = [];
+        this.locales = {};
 
-    if (entries[id] instanceof Entity) {
-      return entries[id];
-    }
+        this._emitter = new EventEmitter();
+
+        // Getting translations
 
-    return entries[id] = new Entity(id, entries[id], entries);
-  };
+        function getWithFallback (id) {
 
-  Locale.prototype.build = function L_build(callback) {
-    var sync = !callback;
-    var ctx = this.ctx;
-    var self = this;
+            if (!this.isReady) {
+                throw new L10nError('Context not ready');
+            }
 
-    var l10nLoads = ctx.resLinks.length;
+            var cur = 0;
+            var loc;
+            var locale;
+            while (loc = this.supportedLocales[cur]) { // eslint-disable-line 
no-cond-assign
+                locale = this.getLocale(loc);
+                if (!locale.isReady) {
+                    // build without callback, synchronously
+                    locale.build(null);
+                }
+                var entry = locale.getEntry(id);
+                if (entry === undefined) {
+                    cur++;
+                    warning.call(this, new L10nError(id + ' not found in ' + 
loc, id,
+                        loc));
+                    continue;
+                }
+                return entry;
+            }
 
-    function onL10nLoaded(err) {
-      if (err) {
-        ctx._emitter.emit('error', err);
-      }
-      if (--l10nLoads <= 0) {
-        self.isReady = true;
-        if (callback) {
-          callback();
+            error.call(this, new L10nError(id + ' not found', id));
+            return null;
         }
-      }
-    }
 
-    if (l10nLoads === 0) {
-      onL10nLoaded();
-      return;
-    }
+        this.get = function get (id, ctxdata) {
+            var entry = getWithFallback.call(this, id);
+            if (entry === null) {
+                return '';
+            }
 
-    function onJSONLoaded(err, json) {
-      if (!err && json) {
-        self.addAST(json);
-      }
-      onL10nLoaded(err);
-    }
+            return entry.toString(ctxdata) || '';
+        };
 
-    function onPropLoaded(err, source) {
-      if (!err && source) {
-        var ast = parse(ctx, source);
-        self.addAST(ast);
-      }
-      onL10nLoaded(err);
-    }
+        this.getEntity = function getEntity (id, ctxdata) {
+            var entry = getWithFallback.call(this, id);
+            if (entry === null) {
+                return null;
+            }
 
+            return entry.valueOf(ctxdata);
+        };
 
-    for (var i = 0; i < ctx.resLinks.length; i++) {
-      var path = ctx.resLinks[i].replace('{{locale}}', this.id);
-      var type = path.substr(path.lastIndexOf('.') + 1);
+        // Helpers
 
-      switch (type) {
-        case 'json':
-          io.loadJSON(path, onJSONLoaded, sync);
-          break;
-        case 'properties':
-          io.load(path, onPropLoaded, sync);
-          break;
-      }
-    }
-  };
+        this.getLocale = function getLocale (code) {
 
-  Locale.prototype.addAST = function(ast) {
-    for (var id in ast) {
-      if (ast.hasOwnProperty(id)) {
-        this.entries[id] = ast[id];
-      }
-    }
-  };
+            var locales = this.locales;
+            if (locales[code]) {
+                return locales[code];
+            }
 
-  Locale.prototype.getEntity = function(id, ctxdata) {
-    var entry = this.getEntry(id);
+            return locales[code] = new Locale(code, this); // 
eslint-disable-line no-return-assign
+        };
 
-    if (!entry) {
-      return null;
-    }
-    return entry.valueOf(ctxdata);
-  };
+        // Getting ready
+
+        function negotiate (available, requested, defaultLocale) {
+            if (available.indexOf(requested[0]) === -1 ||
+          requested[0] === defaultLocale) {
+                return [defaultLocale];
+            } else {
+                return [requested[0], defaultLocale];
+            }
+        }
 
+        function freeze (supported) {
+            var locale = this.getLocale(supported[0]);
+            if (locale.isReady) {
+                setReady.call(this, supported);
+            } else {
+                locale.build(setReady.bind(this, supported));
+            }
+        }
 
+        function setReady (supported) {
+            this.supportedLocales = supported;
+            this.isReady = true;
+            this._emitter.emit('ready');
+        }
 
-  function Context(id) {
+        this.requestLocales = function requestLocales () {
+            if (this.isLoading && !this.isReady) {
+                throw new L10nError('Context not ready');
+            }
 
-    this.id = id;
-    this.isReady = false;
-    this.isLoading = false;
+            this.isLoading = true;
+            var requested = Array.prototype.slice.call(arguments);
 
-    this.supportedLocales = [];
-    this.resLinks = [];
-    this.locales = {};
+            var supported = negotiate(requested.concat('en-US'), requested, 
'en-US');
+            freeze.call(this, supported);
+        };
 
-    this._emitter = new EventEmitter();
+        // Events
 
+        this.addEventListener = function addEventListener (type, listener) {
+            this._emitter.addEventListener(type, listener);
+        };
 
-    // Getting translations
+        this.removeEventListener = function removeEventListener (type, 
listener) {
+            this._emitter.removeEventListener(type, listener);
+        };
 
-    function getWithFallback(id) {
-      /* jshint -W084 */
+        this.ready = function ready (callback) {
+            if (this.isReady) {
+                setTimeout(callback);
+            }
+            this.addEventListener('ready', callback);
+        };
 
-      if (!this.isReady) {
-        throw new L10nError('Context not ready');
-      }
+        this.once = function once (callback) {
+            if (this.isReady) {
+                setTimeout(callback);
+                return;
+            }
 
-      var cur = 0;
-      var loc;
-      var locale;
-      while (loc = this.supportedLocales[cur]) {
-        locale = this.getLocale(loc);
-        if (!locale.isReady) {
-          // build without callback, synchronously
-          locale.build(null);
-        }
-        var entry = locale.getEntry(id);
-        if (entry === undefined) {
-          cur++;
-          warning.call(this, new L10nError(id + ' not found in ' + loc, id,
-                                           loc));
-          continue;
+            var callAndRemove = function () {
+                this.removeEventListener('ready', callAndRemove);
+                callback();
+            }.bind(this);
+            this.addEventListener('ready', callAndRemove);
+        };
+
+        // Errors
+
+        function warning (e) {
+            this._emitter.emit('warning', e);
+            return e;
         }
-        return entry;
-      }
 
-      error.call(this, new L10nError(id + ' not found', id));
-      return null;
+        function error (e) {
+            this._emitter.emit('error', e);
+            return e;
+        }
     }
 
-    this.get = function get(id, ctxdata) {
-      var entry = getWithFallback.call(this, id);
-      if (entry === null) {
-        return '';
-      }
+    var DEBUG = false;
+    var isPretranslated = false;
+    var rtlList = ['ar', 'he', 'fa', 'ps', 'qps-plocm', 'ur'];
+    var nodeObserver = false;
 
-      return entry.toString(ctxdata) || '';
+    var moConfig = {
+        attributes: true,
+        characterData: false,
+        childList: true,
+        subtree: true,
+        attributeFilter: ['data-l10n-id', 'data-l10n-args']
     };
 
-    this.getEntity = function getEntity(id, ctxdata) {
-      var entry = getWithFallback.call(this, id);
-      if (entry === null) {
-        return null;
-      }
-
-      return entry.valueOf(ctxdata);
+    // Public API
+
+    navigator.mozL10n = {
+        ctx: new Context(),
+        get: function get (id, ctxdata) {
+            return navigator.mozL10n.ctx.get(id, ctxdata);
+        },
+        localize: function localize (element, id, args) {
+            return localizeElement.call(navigator.mozL10n, element, id, args);
+        },
+        translate: function () {
+        // XXX: Remove after removing obsolete calls. Bugs 992473 and 1020136
+        },
+        translateFragment: function (fragment) {
+            return translateFragment.call(navigator.mozL10n, fragment);
+        },
+        setAttributes: setL10nAttributes,
+        getAttributes: getL10nAttributes,
+        ready: function ready (callback) {
+            return navigator.mozL10n.ctx.ready(callback);
+        },
+        once: function once (callback) {
+            return navigator.mozL10n.ctx.once(callback);
+        },
+        get readyState () {
+            return navigator.mozL10n.ctx.isReady ? 'complete' : 'loading';
+        },
+        language: {
+            set code (lang) {
+                navigator.mozL10n.ctx.requestLocales(lang);
+            },
+            get code () {
+                return navigator.mozL10n.ctx.supportedLocales[0];
+            },
+            get direction () {
+                return getDirection(navigator.mozL10n.ctx.supportedLocales[0]);
+            }
+        },
+        _getInternalAPI: function () {
+            return {
+                Error: L10nError,
+                Context: Context,
+                Locale: Locale,
+                Entity: Entity,
+                getPluralRule: getPluralRule,
+                rePlaceables: rePlaceables,
+                getTranslatableChildren: getTranslatableChildren,
+                translateDocument: translateDocument,
+                loadINI: loadINI,
+                fireLocalizedEvent: fireLocalizedEvent,
+                parse: parse,
+                compile: compile
+            };
+        }
     };
 
+    navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n));
 
-    // Helpers
-
-    this.getLocale = function getLocale(code) {
-      /* jshint -W093 */
+    if (DEBUG) {
+        navigator.mozL10n.ctx.addEventListener('error', console.error);
+        navigator.mozL10n.ctx.addEventListener('warning', console.warn);
+    }
 
-      var locales = this.locales;
-      if (locales[code]) {
-        return locales[code];
-      }
+    function getDirection (lang) {
+        return (rtlList.indexOf(lang) >= 0) ? 'rtl' : 'ltr';
+    }
 
-      return locales[code] = new Locale(code, this);
+    var readyStates = {
+        'loading': 0,
+        'interactive': 1,
+        'complete': 2
     };
 
+    function waitFor (state, callback) {
+        state = readyStates[state];
+        if (readyStates[document.readyState] >= state) {
+            callback();
+            return;
+        }
 
-    // Getting ready
-
-    function negotiate(available, requested, defaultLocale) {
-      if (available.indexOf(requested[0]) === -1 ||
-          requested[0] === defaultLocale) {
-        return [defaultLocale];
-      } else {
-        return [requested[0], defaultLocale];
-      }
-    }
-
-    function freeze(supported) {
-      var locale = this.getLocale(supported[0]);
-      if (locale.isReady) {
-        setReady.call(this, supported);
-      } else {
-        locale.build(setReady.bind(this, supported));
-      }
+        document.addEventListener('readystatechange', function l10n_onrsc () {
+            if (readyStates[document.readyState] >= state) {
+                document.removeEventListener('readystatechange', l10n_onrsc);
+                callback();
+            }
+        });
     }
 
-    function setReady(supported) {
-      this.supportedLocales = supported;
-      this.isReady = true;
-      this._emitter.emit('ready');
-    }
+    if (window.document) {
+        isPretranslated = (document.documentElement.lang === 
navigator.language);
 
-    this.requestLocales = function requestLocales() {
-      if (this.isLoading && !this.isReady) {
-        throw new L10nError('Context not ready');
-      }
+        // this is a special case for netError bug; see 
https://bugzil.la/444165
+        if (document.documentElement.dataset.noCompleteBug) {
+            pretranslate.call(navigator.mozL10n);
+            return;
+        }
 
-      this.isLoading = true;
-      var requested = Array.prototype.slice.call(arguments);
+        if (isPretranslated) {
+            waitFor('interactive', function () {
+                window.setTimeout(initResources.bind(navigator.mozL10n));
+            });
+        } else {
+            if (document.readyState === 'complete') {
+                window.setTimeout(initResources.bind(navigator.mozL10n));
+            } else {
+                waitFor('interactive', pretranslate.bind(navigator.mozL10n));
+            }
+        }
 
-      var supported = negotiate(requested.concat('en-US'), requested, 'en-US');
-      freeze.call(this, supported);
-    };
+    }
 
+    function pretranslate () {
+        if (inlineLocalization.call(this)) {
+            waitFor('interactive', function () {
+                window.setTimeout(initResources.bind(this));
+            }.bind(this));
+        } else {
+            initResources.call(this);
+        }
+    }
 
-    // Events
+    function inlineLocalization () {
+        var script = document.documentElement
+            .querySelector('script[type="application/l10n"]' +
+            '[lang="' + navigator.language + '"]');
+        if (!script) {
+            return false;
+        }
 
-    this.addEventListener = function addEventListener(type, listener) {
-      this._emitter.addEventListener(type, listener);
-    };
+        var locale = this.ctx.getLocale(navigator.language);
+        // the inline localization is happenning very early, when the ctx is 
not
+        // yet ready and when the resources haven't been downloaded yet;  add 
the
+        // inlined JSON directly to the current locale
+        locale.addAST(JSON.parse(script.innerHTML));
+        // localize the visible DOM
+        var l10n = {
+            ctx: locale,
+            language: {
+                code: locale.id,
+                direction: getDirection(locale.id)
+            }
+        };
+        translateDocument.call(l10n);
 
-    this.removeEventListener = function removeEventListener(type, listener) {
-      this._emitter.removeEventListener(type, listener);
-    };
+        // the visible DOM is now pretranslated
+        isPretranslated = true;
+        return true;
+    }
 
-    this.ready = function ready(callback) {
-      if (this.isReady) {
-        setTimeout(callback);
-      }
-      this.addEventListener('ready', callback);
-    };
+    function initResources () {
+        var resLinks = document.head
+            .querySelectorAll('link[type="application/l10n"]');
+        var iniLinks = [];
 
-    this.once = function once(callback) {
-      /* jshint -W068 */
-      if (this.isReady) {
-        setTimeout(callback);
-        return;
-      }
-
-      var callAndRemove = (function() {
-        this.removeEventListener('ready', callAndRemove);
-        callback();
-      }).bind(this);
-      this.addEventListener('ready', callAndRemove);
-    };
+        for (var i = 0; i < resLinks.length; i++) {
+            var link = resLinks[i];
+            var url = link.getAttribute('href');
+            var type = url.substr(url.lastIndexOf('.') + 1);
+            if (type === 'ini') {
+                iniLinks.push(url);
+            }
+            this.ctx.resLinks.push(url);
+        }
 
+        var iniLoads = iniLinks.length;
+        if (iniLoads === 0) {
+            initLocale.call(this);
+            return;
+        }
 
-    // Errors
+        function onIniLoaded (err) {
+            if (err) {
+                this.ctx._emitter.emit('error', err);
+            }
+            if (--iniLoads === 0) {
+                initLocale.call(this);
+            }
+        }
 
-    function warning(e) {
-      this._emitter.emit('warning', e);
-      return e;
+        for (i = 0; i < iniLinks.length; i++) {
+            loadINI.call(this, iniLinks[i], onIniLoaded.bind(this));
+        }
     }
 
-    function error(e) {
-      this._emitter.emit('error', e);
-      return e;
-    }
-  }
-
-
-
-  var DEBUG = false;
-  var isPretranslated = false;
-  var rtlList = ['ar', 'he', 'fa', 'ps', 'qps-plocm', 'ur'];
-  var nodeObserver = false;
-
-  var moConfig = {
-    attributes: true,
-    characterData: false,
-    childList: true,
-    subtree: true,
-    attributeFilter: ['data-l10n-id', 'data-l10n-args']
-  };
-
-  // Public API
-
-  navigator.mozL10n = {
-    ctx: new Context(),
-    get: function get(id, ctxdata) {
-      return navigator.mozL10n.ctx.get(id, ctxdata);
-    },
-    localize: function localize(element, id, args) {
-      return localizeElement.call(navigator.mozL10n, element, id, args);
-    },
-    translate: function () {
-      // XXX: Remove after removing obsolete calls. Bugs 992473 and 1020136
-    },
-    translateFragment: function (fragment) {
-      return translateFragment.call(navigator.mozL10n, fragment);
-    },
-    setAttributes: setL10nAttributes,
-    getAttributes: getL10nAttributes,
-    ready: function ready(callback) {
-      return navigator.mozL10n.ctx.ready(callback);
-    },
-    once: function once(callback) {
-      return navigator.mozL10n.ctx.once(callback);
-    },
-    get readyState() {
-      return navigator.mozL10n.ctx.isReady ? 'complete' : 'loading';
-    },
-    language: {
-      set code(lang) {
-        navigator.mozL10n.ctx.requestLocales(lang);
-      },
-      get code() {
-        return navigator.mozL10n.ctx.supportedLocales[0];
-      },
-      get direction() {
-        return getDirection(navigator.mozL10n.ctx.supportedLocales[0]);
-      }
-    },
-    _getInternalAPI: function() {
-      return {
-        Error: L10nError,
-        Context: Context,
-        Locale: Locale,
-        Entity: Entity,
-        getPluralRule: getPluralRule,
-        rePlaceables: rePlaceables,
-        getTranslatableChildren:  getTranslatableChildren,
-        translateDocument: translateDocument,
-        loadINI: loadINI,
-        fireLocalizedEvent: fireLocalizedEvent,
-        parse: parse,
-        compile: compile
-      };
-    }
-  };
-
-  navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n));
-
-  if (DEBUG) {
-    navigator.mozL10n.ctx.addEventListener('error', console.error);
-    navigator.mozL10n.ctx.addEventListener('warning', console.warn);
-  }
-
-  function getDirection(lang) {
-    return (rtlList.indexOf(lang) >= 0) ? 'rtl' : 'ltr';
-  }
-
-  var readyStates = {
-    'loading': 0,
-    'interactive': 1,
-    'complete': 2
-  };
-
-  function waitFor(state, callback) {
-    state = readyStates[state];
-    if (readyStates[document.readyState] >= state) {
-      callback();
-      return;
+    function initLocale () {
+        this.ctx.requestLocales(navigator.language);
+        window.addEventListener('languagechange', function l10n_langchange () {
+            navigator.mozL10n.language.code = navigator.language;
+        });
     }
 
-    document.addEventListener('readystatechange', function l10n_onrsc() {
-      if (readyStates[document.readyState] >= state) {
-        document.removeEventListener('readystatechange', l10n_onrsc);
-        callback();
-      }
-    });
-  }
-
-  if (window.document) {
-    isPretranslated = (document.documentElement.lang === navigator.language);
-
-    // this is a special case for netError bug; see https://bugzil.la/444165
-    if (document.documentElement.dataset.noCompleteBug) {
-      pretranslate.call(navigator.mozL10n);
-      return;
-    }
+    function localizeMutations (mutations) {
+        var mutation;
 
+        for (var i = 0; i < mutations.length; i++) {
+            mutation = mutations[i];
+            if (mutation.type === 'childList') {
+                var addedNode;
 
-    if (isPretranslated) {
-      waitFor('interactive', function() {
-        window.setTimeout(initResources.bind(navigator.mozL10n));
-      });
-    } else {
-      if (document.readyState === 'complete') {
-        window.setTimeout(initResources.bind(navigator.mozL10n));
-      } else {
-        waitFor('interactive', pretranslate.bind(navigator.mozL10n));
-      }
-    }
+                for (var j = 0; j < mutation.addedNodes.length; j++) {
+                    addedNode = mutation.addedNodes[j];
 
-  }
+                    if (addedNode.nodeType !== Node.ELEMENT_NODE) { // 
eslint-disable-line no-undef
+                        continue;
+                    }
 
-  function pretranslate() {
-    /* jshint -W068 */
-    if (inlineLocalization.call(this)) {
-      waitFor('interactive', (function() {
-        window.setTimeout(initResources.bind(this));
-      }).bind(this));
-    } else {
-      initResources.call(this);
-    }
-  }
-
-  function inlineLocalization() {
-    var script = document.documentElement
-                         .querySelector('script[type="application/l10n"]' +
-                         '[lang="' + navigator.language + '"]');
-    if (!script) {
-      return false;
-    }
+                    if (addedNode.childElementCount) {
+                        translateFragment.call(this, addedNode);
+                    } else if (addedNode.hasAttribute('data-l10n-id')) {
+                        translateElement.call(this, addedNode);
+                    }
+                }
+            }
 
-    var locale = this.ctx.getLocale(navigator.language);
-    // the inline localization is happenning very early, when the ctx is not
-    // yet ready and when the resources haven't been downloaded yet;  add the
-    // inlined JSON directly to the current locale
-    locale.addAST(JSON.parse(script.innerHTML));
-    // localize the visible DOM
-    var l10n = {
-      ctx: locale,
-      language: {
-        code: locale.id,
-        direction: getDirection(locale.id)
-      }
-    };
-    translateDocument.call(l10n);
-
-    // the visible DOM is now pretranslated
-    isPretranslated = true;
-    return true;
-  }
-
-  function initResources() {
-    var resLinks = document.head
-                           .querySelectorAll('link[type="application/l10n"]');
-    var iniLinks = [];
-
-    for (var i = 0; i < resLinks.length; i++) {
-      var link = resLinks[i];
-      var url = link.getAttribute('href');
-      var type = url.substr(url.lastIndexOf('.') + 1);
-      if (type === 'ini') {
-        iniLinks.push(url);
-      }
-      this.ctx.resLinks.push(url);
+            if (mutation.type === 'attributes') {
+                translateElement.call(this, mutation.target);
+            }
+        }
     }
 
-    var iniLoads = iniLinks.length;
-    if (iniLoads === 0) {
-      initLocale.call(this);
-      return;
+    function onMutations (mutations, self) {
+        self.disconnect();
+        localizeMutations.call(this, mutations);
+        self.observe(document, moConfig);
     }
 
-    function onIniLoaded(err) {
-      if (err) {
-        this.ctx._emitter.emit('error', err);
-      }
-      if (--iniLoads === 0) {
-        initLocale.call(this);
-      }
-    }
+    function onReady () {
+        if (!isPretranslated) {
+            translateDocument.call(this);
+        }
+        isPretranslated = false;
+
+        if (!nodeObserver) {
+            nodeObserver = new MutationObserver(onMutations.bind(this)); // 
eslint-disable-line no-undef
+            nodeObserver.observe(document, moConfig);
+        }
 
-    for (i = 0; i < iniLinks.length; i++) {
-      loadINI.call(this, iniLinks[i], onIniLoaded.bind(this));
+        fireLocalizedEvent.call(this);
     }
-  }
 
-  function initLocale() {
-    this.ctx.requestLocales(navigator.language);
-    window.addEventListener('languagechange', function l10n_langchange() {
-      navigator.mozL10n.language.code = navigator.language;
-    });
-  }
+    function fireLocalizedEvent () {
+        var event = new CustomEvent('localized', { // eslint-disable-line 
no-undef
+            'bubbles': false,
+            'cancelable': false,
+            'detail': {
+                'language': this.ctx.supportedLocales[0]
+            }
+        });
+        window.dispatchEvent(event);
+    }
 
-  function localizeMutations(mutations) {
-    var mutation;
+    function loadINI (url, callback) {
+        var ctx = this.ctx;
+        io.load(url, function (err, source) {
+            var pos = ctx.resLinks.indexOf(url);
 
-    for (var i = 0; i < mutations.length; i++) {
-      mutation = mutations[i];
-      if (mutation.type === 'childList') {
-        var addedNode;
+            if (err) {
+                // remove the ini link from resLinks
+                ctx.resLinks.splice(pos, 1);
+                return callback(err);
+            }
 
-        for (var j = 0; j < mutation.addedNodes.length; j++) {
-          addedNode = mutation.addedNodes[j];
+            if (!source) {
+                ctx.resLinks.splice(pos, 1);
+                return callback(new Error('Empty file: ' + url));
+            }
 
-          if (addedNode.nodeType !== Node.ELEMENT_NODE) {
-            continue;
-          }
+            var patterns = parseINI(source, url).resources.map(function (x) {
+                return x.replace('en-US', '{{locale}}');
+            });
+            ctx.resLinks.splice.apply(ctx.resLinks, [pos, 1].concat(patterns));
+            callback();
+        });
+    }
 
-          if (addedNode.childElementCount) {
-            translateFragment.call(this, addedNode);
-          } else if (addedNode.hasAttribute('data-l10n-id')) {
-            translateElement.call(this, addedNode);
-          }
+    function relativePath (baseUrl, url) {
+        if (url[0] === '/') {
+            return url;
         }
-      }
 
-      if (mutation.type === 'attributes') {
-        translateElement.call(this, mutation.target);
-      }
+        var dirs = baseUrl.split('/')
+            .slice(0, -1)
+            .concat(url.split('/'))
+            .filter(function (path) {
+                return path !== '.';
+            });
+
+        return dirs.join('/');
     }
-  }
 
-  function onMutations(mutations, self) {
-    self.disconnect();
-    localizeMutations.call(this, mutations);
-    self.observe(document, moConfig);
-  }
+    var iniPatterns = {
+        'section': /^\s*\[(.*)\]\s*$/,
+        'import': /^\s*@import\s+url\((.*)\)\s*$/i,
+        'entry': /[\r\n]+/
+    };
 
-  function onReady() {
-    if (!isPretranslated) {
-      translateDocument.call(this);
-    }
-    isPretranslated = false;
+    function parseINI (source, iniPath) {
+        var entries = source.split(iniPatterns.entry);
+        var locales = ['en-US'];
+        var genericSection = true;
+        var uris = [];
+        var match;
 
-    if (!nodeObserver) {
-      nodeObserver = new MutationObserver(onMutations.bind(this));
-      nodeObserver.observe(document, moConfig);
-    }
+        for (var i = 0; i < entries.length; i++) {
+            var line = entries[i];
+            // we only care about en-US resources
+            if (genericSection && iniPatterns['import'].test(line)) {
+                match = iniPatterns['import'].exec(line);
+                var uri = relativePath(iniPath, match[1]);
+                uris.push(uri);
+                continue;
+            }
 
-    fireLocalizedEvent.call(this);
-  }
-
-  function fireLocalizedEvent() {
-    var event = new CustomEvent('localized', {
-      'bubbles': false,
-      'cancelable': false,
-      'detail': {
-        'language': this.ctx.supportedLocales[0]
-      }
-    });
-    window.dispatchEvent(event);
-  }
-
-  /* jshint -W104 */
-
-  function loadINI(url, callback) {
-    var ctx = this.ctx;
-    io.load(url, function(err, source) {
-      var pos = ctx.resLinks.indexOf(url);
-
-      if (err) {
-        // remove the ini link from resLinks
-        ctx.resLinks.splice(pos, 1);
-        return callback(err);
-      }
-
-      if (!source) {
-        ctx.resLinks.splice(pos, 1);
-        return callback(new Error('Empty file: ' + url));
-      }
-
-      var patterns = parseINI(source, url).resources.map(function(x) {
-        return x.replace('en-US', '{{locale}}');
-      });
-      ctx.resLinks.splice.apply(ctx.resLinks, [pos, 1].concat(patterns));
-      callback();
-    });
-  }
-
-  function relativePath(baseUrl, url) {
-    if (url[0] === '/') {
-      return url;
+            // but we need the list of all locales in the ini, too
+            if (iniPatterns.section.test(line)) {
+                genericSection = false;
+                match = iniPatterns.section.exec(line);
+                locales.push(match[1]);
+            }
+        }
+        return {
+            locales: locales,
+            resources: uris
+        };
     }
 
-    var dirs = baseUrl.split('/')
-      .slice(0, -1)
-      .concat(url.split('/'))
-      .filter(function(path) {
-        return path !== '.';
-      });
-
-    return dirs.join('/');
-  }
-
-  var iniPatterns = {
-    'section': /^\s*\[(.*)\]\s*$/,
-    'import': /^\s*@import\s+url\((.*)\)\s*$/i,
-    'entry': /[\r\n]+/
-  };
-
-  function parseINI(source, iniPath) {
-    var entries = source.split(iniPatterns.entry);
-    var locales = ['en-US'];
-    var genericSection = true;
-    var uris = [];
-    var match;
-
-    for (var i = 0; i < entries.length; i++) {
-      var line = entries[i];
-      // we only care about en-US resources
-      if (genericSection && iniPatterns['import'].test(line)) {
-        match = iniPatterns['import'].exec(line);
-        var uri = relativePath(iniPath, match[1]);
-        uris.push(uri);
-        continue;
-      }
-
-      // but we need the list of all locales in the ini, too
-      if (iniPatterns.section.test(line)) {
-        genericSection = false;
-        match = iniPatterns.section.exec(line);
-        locales.push(match[1]);
-      }
+    function translateDocument () {
+        document.documentElement.lang = this.language.code;
+        document.documentElement.dir = this.language.direction;
+        translateFragment.call(this, document.documentElement);
     }
-    return {
-      locales: locales,
-      resources: uris
-    };
-  }
-
-  /* jshint -W104 */
 
-  function translateDocument() {
-    document.documentElement.lang = this.language.code;
-    document.documentElement.dir = this.language.direction;
-    translateFragment.call(this, document.documentElement);
-  }
-
-  function translateFragment(element) {
-    if (element.hasAttribute('data-l10n-id')) {
-      translateElement.call(this, element);
-    }
+    function translateFragment (element) {
+        if (element.hasAttribute('data-l10n-id')) {
+            translateElement.call(this, element);
+        }
 
-    var nodes = getTranslatableChildren(element);
-    for (var i = 0; i < nodes.length; i++ ) {
-      translateElement.call(this, nodes[i]);
+        var nodes = getTranslatableChildren(element);
+        for (var i = 0; i < nodes.length; i++) {
+            translateElement.call(this, nodes[i]);
+        }
     }
-  }
 
-  function setL10nAttributes(element, id, args) {
-    element.setAttribute('data-l10n-id', id);
-    if (args) {
-      element.setAttribute('data-l10n-args', JSON.stringify(args));
+    function setL10nAttributes (element, id, args) {
+        element.setAttribute('data-l10n-id', id);
+        if (args) {
+            element.setAttribute('data-l10n-args', JSON.stringify(args));
+        }
     }
-  }
 
-  function getL10nAttributes(element) {
-    return {
-      id: element.getAttribute('data-l10n-id'),
-      args: JSON.parse(element.getAttribute('data-l10n-args'))
-    };
-  }
-
-  function getTranslatableChildren(element) {
-    return element ? element.querySelectorAll('*[data-l10n-id]') : [];
-  }
-
-  function localizeElement(element, id, args) {
-    if (!id) {
-      element.removeAttribute('data-l10n-id');
-      element.removeAttribute('data-l10n-args');
-      setTextContent(element, '');
-      return;
+    function getL10nAttributes (element) {
+        return {
+            id: element.getAttribute('data-l10n-id'),
+            args: JSON.parse(element.getAttribute('data-l10n-args'))
+        };
     }
 
-    element.setAttribute('data-l10n-id', id);
-    if (args && typeof args === 'object') {
-      element.setAttribute('data-l10n-args', JSON.stringify(args));
-    } else {
-      element.removeAttribute('data-l10n-args');
+    function getTranslatableChildren (element) {
+        return element ? element.querySelectorAll('*[data-l10n-id]') : [];
     }
-  }
 
-  function translateElement(element) {
-    var l10n = getL10nAttributes(element);
+    function localizeElement (element, id, args) {
+        if (!id) {
+            element.removeAttribute('data-l10n-id');
+            element.removeAttribute('data-l10n-args');
+            setTextContent(element, '');
+            return;
+        }
 
-    if (!l10n.id) {
-      return false;
+        element.setAttribute('data-l10n-id', id);
+        if (args && typeof args === 'object') {
+            element.setAttribute('data-l10n-args', JSON.stringify(args));
+        } else {
+            element.removeAttribute('data-l10n-args');
+        }
     }
 
-    var entity = this.ctx.getEntity(l10n.id, l10n.args);
+    function translateElement (element) {
+        var l10n = getL10nAttributes(element);
 
-    if (!entity) {
-      return false;
-    }
+        if (!l10n.id) {
+            return false;
+        }
 
-    if (typeof entity === 'string') {
-      setTextContent(element, entity);
-      return true;
-    }
+        var entity = this.ctx.getEntity(l10n.id, l10n.args);
 
-    if (entity.value) {
-      setTextContent(element, entity.value);
-    }
+        if (!entity) {
+            return false;
+        }
 
-    for (var key in entity.attributes) {
-      if (entity.attributes.hasOwnProperty(key)) {
-        var attr = entity.attributes[key];
-        if (key === 'ariaLabel') {
-          element.setAttribute('aria-label', attr);
-        } else if (key === 'innerHTML') {
-          // XXX: to be removed once bug 994357 lands
-          element.innerHTML = attr;
-        } else {
-          element.setAttribute(key, attr);
+        if (typeof entity === 'string') {
+            setTextContent(element, entity);
+            return true;
+        }
+
+        if (entity.value) {
+            setTextContent(element, entity.value);
         }
-      }
-    }
 
-    return true;
-  }
+        for (var key in entity.attributes) {
+            if (entity.attributes.hasOwnProperty(key)) {
+                var attr = entity.attributes[key];
+                if (key === 'ariaLabel') {
+                    element.setAttribute('aria-label', attr);
+                } else if (key === 'innerHTML') {
+                    // XXX: to be removed once bug 994357 lands
+                    element.innerHTML = attr;
+                } else {
+                    element.setAttribute(key, attr);
+                }
+            }
+        }
 
-  function setTextContent(element, text) {
-    // standard case: no element children
-    if (!element.firstElementChild) {
-      element.textContent = text;
-      return;
+        return true;
     }
 
-    // this element has element children: replace the content of the first
-    // (non-blank) child textNode and clear other child textNodes
-    var found = false;
-    var reNotBlank = /\S/;
-    for (var child = element.firstChild; child; child = child.nextSibling) {
-      if (child.nodeType === Node.TEXT_NODE &&
+    function setTextContent (element, text) {
+        // standard case: no element children
+        if (!element.firstElementChild) {
+            element.textContent = text;
+            return;
+        }
+
+        // this element has element children: replace the content of the first
+        // (non-blank) child textNode and clear other child textNodes
+        var found = false;
+        var reNotBlank = /\S/;
+        for (var child = element.firstChild; child; child = child.nextSibling) 
{
+            if (child.nodeType === Node.TEXT_NODE && // eslint-disable-line 
no-undef
           reNotBlank.test(child.nodeValue)) {
-        if (found) {
-          child.nodeValue = '';
-        } else {
-          child.nodeValue = text;
-          found = true;
+                if (found) {
+                    child.nodeValue = '';
+                } else {
+                    child.nodeValue = text;
+                    found = true;
+                }
+            }
+        }
+        // if no (non-empty) textNode is found, insert a textNode before the
+        // element's first child.
+        if (!found) {
+            element.insertBefore(document.createTextNode(text), 
element.firstChild);
         }
-      }
-    }
-    // if no (non-empty) textNode is found, insert a textNode before the
-    // element's first child.
-    if (!found) {
-      element.insertBefore(document.createTextNode(text), element.firstChild);
     }
-  }
 
 })(this);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org
For additional commands, e-mail: commits-h...@cordova.apache.org

Reply via email to