Commit 420ea6ab22154b7e1bb293b248599106ece52615

Authored by Renato De Donato
1 parent 82f84018

workcicle...

Showing 39 changed files with 68 additions and 53186 deletions
alasql-utility/alasql-utility.js
1   -function alasql_selectData (data, fields, filters) {
2   - if(fields.length == 0)
3   - return [];
4   -
5   - var _fields = _addParenthesis(fields);
6   -
7   - var select = _alasql_SELECT(_fields);
8   -
9   - var where = "";
10   - if(filters && filters.length) {
11   - var _filters = _copy(filters);
12   - where = _alasql_WHERE(_filters);
13   - }
14   -
15   - var query = select + " FROM ?" + where;
16   -
17   - return alasql(query, [data]);
18   -}
19   -
20   -function alasql_complexSelectData (data, fields, filters, aggregators, orders) {
  1 +function alasql_QUERY (data, fields, filters, aggregators, orders) {
21 2 if(fields.length == 0)
22 3 return [];
23 4  
... ... @@ -134,17 +115,13 @@ function _addParenthesis (fields) {
134 115 }
135 116  
136 117 function _normalizeField (field) {
137   - /*DEPRECATED*/return "[" + field.substring(field.lastIndexOf(",") + 1, field.length) + "]";
138   - //return "[" + field + "]";
  118 + return "[" + field + "]";
139 119 }
140 120  
141   -function transformData (data, fields, truncate) {
  121 +function alasql_transformData (data, fields, truncate) {
142 122 if(!data || data.length == 0)
143 123 return [];
144 124  
145   - /*DEPRECATED*/for (var i=0; i < fields.length; i++)
146   - fields[i] = fields[i].substring(fields[i].lastIndexOf(",") + 1, fields[i].length);
147   -
148 125 var tData = [];
149 126  
150 127 for (var i in fields){
... ...
bower_components/l20n/.bower.json deleted
1   -{
2   - "name": "l20n",
3   - "description": "A natural-language localization framework",
4   - "version": "3.4.1",
5   - "homepage": "http://l20n.org",
6   - "repository": {
7   - "type": "git",
8   - "url": "git://github.com/l20n/l20n.js.git"
9   - },
10   - "authors": [
11   - "Mozilla <l10n-drivers@mozilla.org>",
12   - "Zbigniew Braniecki",
13   - "Staś Małolepszy"
14   - ],
15   - "license": "Apache 2.0",
16   - "keywords": [
17   - "localization",
18   - "l10n",
19   - "l20n"
20   - ],
21   - "main": [],
22   - "ignore": [
23   - "**/*",
24   - "!dist/**/*"
25   - ],
26   - "_release": "3.4.1",
27   - "_resolution": {
28   - "type": "version",
29   - "tag": "v3.4.1",
30   - "commit": "4b5428be54003e8dc7243665a5f844a546ddd226"
31   - },
32   - "_source": "git://github.com/l20n/l20n.js.git",
33   - "_target": "v3.x",
34   - "_originalSource": "l20n",
35   - "_direct": true
36   -}
37 0 \ No newline at end of file
bower_components/l20n/bower.json deleted
1   -{
2   - "name": "l20n",
3   - "description": "A natural-language localization framework",
4   - "version": "3.4.1",
5   - "homepage": "http://l20n.org",
6   - "repository": {
7   - "type": "git",
8   - "url": "git://github.com/l20n/l20n.js.git"
9   - },
10   - "authors": [
11   - "Mozilla <l10n-drivers@mozilla.org>",
12   - "Zbigniew Braniecki",
13   - "Staś Małolepszy"
14   - ],
15   - "license": "Apache 2.0",
16   - "keywords": [
17   - "localization",
18   - "l10n",
19   - "l20n"
20   - ],
21   - "main": [],
22   - "ignore": [
23   - "**/*",
24   - "!dist/**/*"
25   - ]
26   -}
bower_components/l20n/dist/bundle/aisle/l20n.js deleted
1   -define(['exports'], function (exports) { 'use strict';
2   -
3   - class Node {
4   - constructor() {
5   - this.type = this.constructor.name;
6   - }
7   - }
8   -
9   - class Entry extends Node {
10   - constructor() {
11   - super();
12   - }
13   - }
14   -
15   - class Identifier extends Node {
16   - constructor(name) {
17   - super();
18   - this.name = name;
19   - }
20   - }
21   -
22   - class Variable extends Node {
23   - constructor(name) {
24   - super();
25   - this.name = name;
26   - }
27   - }
28   -
29   - class Global extends Node {
30   - constructor(name) {
31   - super();
32   - this.name = name;
33   - }
34   - }
35   -
36   - class Value extends Node {
37   - constructor() {
38   - super();
39   - }
40   - }
41   -
42   - class String extends Value {
43   - constructor(source, content) {
44   - super();
45   - this.source = source;
46   - this.content = content;
47   -
48   - this._opchar = '"';
49   - }
50   - }
51   -
52   - class Hash extends Value {
53   - constructor(items) {
54   - super();
55   - this.items = items;
56   - }
57   - }
58   -
59   -
60   - class Entity extends Entry {
61   - constructor(id, value = null, index = null, attrs = []) {
62   - super();
63   - this.id = id;
64   - this.value = value;
65   - this.index = index;
66   - this.attrs = attrs;
67   - }
68   - }
69   -
70   - class Resource extends Node {
71   - constructor() {
72   - super();
73   - this.body = [];
74   - }
75   - }
76   -
77   - class Attribute extends Node {
78   - constructor(id, value, index = null) {
79   - super();
80   - this.id = id;
81   - this.value = value;
82   - this.index = index;
83   - }
84   - }
85   -
86   - class HashItem extends Node {
87   - constructor(id, value, defItem) {
88   - super();
89   - this.id = id;
90   - this.value = value;
91   - this.default = defItem;
92   - }
93   - }
94   -
95   - class Comment extends Entry {
96   - constructor(body) {
97   - super();
98   - this.body = body;
99   - }
100   - }
101   -
102   - class Expression extends Node {
103   - constructor() {
104   - super();
105   - }
106   - }
107   -
108   - class PropertyExpression extends Expression {
109   - constructor(idref, exp, computed = false) {
110   - super();
111   - this.idref = idref;
112   - this.exp = exp;
113   - this.computed = computed;
114   - }
115   - }
116   -
117   - class CallExpression extends Expression {
118   - constructor(callee, args) {
119   - super();
120   - this.callee = callee;
121   - this.args = args;
122   - }
123   - }
124   -
125   - class JunkEntry extends Entry {
126   - constructor(content) {
127   - super();
128   - this.content = content;
129   - }
130   - }
131   -
132   - var AST = {
133   - Node,
134   - Identifier,
135   - Value,
136   - String,
137   - Hash,
138   - Entity,
139   - Resource,
140   - Attribute,
141   - HashItem,
142   - Comment,
143   - Variable,
144   - Global,
145   - Expression,
146   - PropertyExpression,
147   - CallExpression,
148   - JunkEntry,
149   - };
150   -
151   - function L10nError(message, id, lang) {
152   - this.name = 'L10nError';
153   - this.message = message;
154   - this.id = id;
155   - this.lang = lang;
156   - }
157   - L10nError.prototype = Object.create(Error.prototype);
158   - L10nError.prototype.constructor = L10nError;
159   -
160   - const MAX_PLACEABLES = 100;
161   -
162   -
163   - class ParseContext {
164   - constructor(string, pos) {
165   - this._config = {
166   - pos: pos
167   - };
168   - this._source = string;
169   - this._index = 0;
170   - this._length = string.length;
171   - this._curEntryStart = 0;
172   - }
173   -
174   - setPosition(node, start, end) {
175   - if (!this._config.pos) {
176   - return;
177   - }
178   - node._pos = {start, end};
179   - }
180   -
181   - getResource() {
182   - let resource = new AST.Resource();
183   - this.setPosition(resource, 0, this._length);
184   - resource._errors = [];
185   -
186   - this.getWS();
187   - while (this._index < this._length) {
188   - try {
189   - resource.body.push(this.getEntry());
190   - } catch (e) {
191   - if (e instanceof L10nError) {
192   - resource._errors.push(e);
193   - resource.body.push(this.getJunkEntry());
194   - } else {
195   - throw e;
196   - }
197   - }
198   - if (this._index < this._length) {
199   - this.getWS();
200   - }
201   - }
202   -
203   - return resource;
204   - }
205   -
206   - getEntry() {
207   - this._curEntryStart = this._index;
208   -
209   - if (this._source[this._index] === '<') {
210   - ++this._index;
211   - const id = this.getIdentifier();
212   - if (this._source[this._index] === '[') {
213   - ++this._index;
214   - return this.getEntity(id, this.getItemList(this.getExpression, ']'));
215   - }
216   - return this.getEntity(id);
217   - }
218   -
219   - if (this._source.startsWith('/*', this._index)) {
220   - return this.getComment();
221   - }
222   -
223   - throw this.error('Invalid entry');
224   - }
225   -
226   - getEntity(id, index) {
227   - if (!this.getRequiredWS()) {
228   - throw this.error('Expected white space');
229   - }
230   -
231   - const ch = this._source.charAt(this._index);
232   - const value = this.getValue(ch, index === undefined);
233   - let attrs;
234   -
235   - if (value === null) {
236   - if (ch === '>') {
237   - throw this.error('Expected ">"');
238   - }
239   - attrs = this.getAttributes();
240   - } else {
241   - const ws1 = this.getRequiredWS();
242   - if (this._source[this._index] !== '>') {
243   - if (!ws1) {
244   - throw this.error('Expected ">"');
245   - }
246   - attrs = this.getAttributes();
247   - }
248   - }
249   -
250   - // skip '>'
251   - ++this._index;
252   -
253   - const entity = new AST.Entity(id, value, index, attrs);
254   - this.setPosition(entity, this._curEntryStart, this._index);
255   - return entity;
256   - }
257   -
258   - getValue(ch = this._source[this._index], optional = false) {
259   - switch (ch) {
260   - case '\'':
261   - case '"':
262   - return this.getString(ch, 1);
263   - case '{':
264   - return this.getHash();
265   - }
266   -
267   - if (!optional) {
268   - throw this.error('Unknown value type');
269   - }
270   - return null;
271   - }
272   -
273   - getWS() {
274   - let cc = this._source.charCodeAt(this._index);
275   - // space, \n, \t, \r
276   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
277   - cc = this._source.charCodeAt(++this._index);
278   - }
279   - }
280   -
281   - getRequiredWS() {
282   - const pos = this._index;
283   - let cc = this._source.charCodeAt(pos);
284   - // space, \n, \t, \r
285   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
286   - cc = this._source.charCodeAt(++this._index);
287   - }
288   - return this._index !== pos;
289   - }
290   -
291   - getIdentifier() {
292   - const start = this._index;
293   - let cc = this._source.charCodeAt(this._index);
294   -
295   - if ((cc >= 97 && cc <= 122) || // a-z
296   - (cc >= 65 && cc <= 90) || // A-Z
297   - cc === 95) { // _
298   - cc = this._source.charCodeAt(++this._index);
299   - } else {
300   - throw this.error('Identifier has to start with [a-zA-Z_]');
301   - }
302   -
303   - while ((cc >= 97 && cc <= 122) || // a-z
304   - (cc >= 65 && cc <= 90) || // A-Z
305   - (cc >= 48 && cc <= 57) || // 0-9
306   - cc === 95) { // _
307   - cc = this._source.charCodeAt(++this._index);
308   - }
309   -
310   - const id = new AST.Identifier(this._source.slice(start, this._index));
311   - this.setPosition(id, start, this._index);
312   - return id;
313   - }
314   -
315   - getUnicodeChar() {
316   - for (let i = 0; i < 4; i++) {
317   - let cc = this._source.charCodeAt(++this._index);
318   - if ((cc > 96 && cc < 103) || // a-f
319   - (cc > 64 && cc < 71) || // A-F
320   - (cc > 47 && cc < 58)) { // 0-9
321   - continue;
322   - }
323   - throw this.error('Illegal unicode escape sequence');
324   - }
325   - return '\\u' + this._source.slice(this._index - 3, this._index + 1);
326   - }
327   -
328   - getString(opchar, opcharLen) {
329   - let body = [];
330   - let buf = '';
331   - let placeables = 0;
332   -
333   - this._index += opcharLen - 1;
334   -
335   - const start = this._index + 1;
336   -
337   - let closed = false;
338   -
339   - while (!closed) {
340   - let ch = this._source[++this._index];
341   -
342   - switch (ch) {
343   - case '\\':
344   - const ch2 = this._source[++this._index];
345   - if (ch2 === 'u') {
346   - buf += this.getUnicodeChar();
347   - } else if (ch2 === opchar || ch2 === '\\') {
348   - buf += ch2;
349   - } else if (ch2 === '{' && this._source[this._index + 1] === '{') {
350   - buf += '{';
351   - } else {
352   - throw this.error('Illegal escape sequence');
353   - }
354   - break;
355   - case '{':
356   - if (this._source[this._index + 1] === '{') {
357   - if (placeables > MAX_PLACEABLES - 1) {
358   - throw this.error('Too many placeables, maximum allowed is ' +
359   - MAX_PLACEABLES);
360   - }
361   - if (buf.length) {
362   - body.push(buf);
363   - buf = '';
364   - }
365   - this._index += 2;
366   - this.getWS();
367   - body.push(this.getExpression());
368   - this.getWS();
369   - if (!this._source.startsWith('}}', this._index)) {
370   - throw this.error('Expected "}}"');
371   - }
372   - this._index += 1;
373   - placeables++;
374   - break;
375   - }
376   - /* falls through */
377   - default:
378   - if (ch === opchar) {
379   - this._index++;
380   - closed = true;
381   - break;
382   - }
383   -
384   - buf += ch;
385   - if (this._index + 1 >= this._length) {
386   - throw this.error('Unclosed string literal');
387   - }
388   - }
389   - }
390   -
391   - if (buf.length) {
392   - body.push(buf);
393   - }
394   -
395   - const string = new AST.String(
396   - this._source.slice(start, this._index - 1), body);
397   - this.setPosition(string, start, this._index);
398   - string._opchar = opchar;
399   -
400   - return string;
401   - }
402   -
403   - getAttributes() {
404   - const attrs = [];
405   -
406   - while (true) {
407   - const attr = this.getAttribute();
408   - attrs.push(attr);
409   - const ws1 = this.getRequiredWS();
410   - const ch = this._source.charAt(this._index);
411   - if (ch === '>') {
412   - break;
413   - } else if (!ws1) {
414   - throw this.error('Expected ">"');
415   - }
416   - }
417   - return attrs;
418   - }
419   -
420   - getAttribute() {
421   - const start = this._index;
422   - const key = this.getIdentifier();
423   - let index;
424   -
425   - if (this._source[this._index]=== '[') {
426   - ++this._index;
427   - this.getWS();
428   - index = this.getItemList(this.getExpression, ']');
429   - }
430   - this.getWS();
431   - if (this._source[this._index] !== ':') {
432   - throw this.error('Expected ":"');
433   - }
434   - ++this._index;
435   - this.getWS();
436   - const attr = new AST.Attribute(key, this.getValue(), index);
437   - this.setPosition(attr, start, this._index);
438   - return attr;
439   - }
440   -
441   - getHash() {
442   - const start = this._index;
443   - let items = [];
444   -
445   - ++this._index;
446   - this.getWS();
447   -
448   - while (true) {
449   - items.push(this.getHashItem());
450   - this.getWS();
451   -
452   - const comma = this._source[this._index] === ',';
453   - if (comma) {
454   - ++this._index;
455   - this.getWS();
456   - }
457   - if (this._source[this._index] === '}') {
458   - ++this._index;
459   - break;
460   - }
461   - if (!comma) {
462   - throw this.error('Expected "}"');
463   - }
464   - }
465   -
466   - const hash = new AST.Hash(items);
467   - this.setPosition(hash, start, this._index);
468   - return hash;
469   - }
470   -
471   - getHashItem() {
472   - const start = this._index;
473   -
474   - let defItem = false;
475   - if (this._source[this._index] === '*') {
476   - ++this._index;
477   - defItem = true;
478   - }
479   -
480   - const key = this.getIdentifier();
481   - this.getWS();
482   - if (this._source[this._index] !== ':') {
483   - throw this.error('Expected ":"');
484   - }
485   - ++this._index;
486   - this.getWS();
487   -
488   - const hashItem = new AST.HashItem(key, this.getValue(), defItem);
489   - this.setPosition(hashItem, start, this._index);
490   - return hashItem;
491   - }
492   -
493   - getComment() {
494   - this._index += 2;
495   - const start = this._index;
496   - const end = this._source.indexOf('*/', start);
497   -
498   - if (end === -1) {
499   - throw this.error('Comment without a closing tag');
500   - }
501   -
502   - this._index = end + 2;
503   - const comment = new AST.Comment(this._source.slice(start, end));
504   - this.setPosition(comment, start - 2, this._index);
505   - return comment;
506   - }
507   -
508   - getExpression() {
509   - const start = this._index;
510   - let exp = this.getPrimaryExpression();
511   -
512   - while (true) {
513   - let ch = this._source[this._index];
514   - if (ch === '.' || ch === '[') {
515   - ++this._index;
516   - exp = this.getPropertyExpression(exp, ch === '[', start);
517   - } else if (ch === '(') {
518   - ++this._index;
519   - exp = this.getCallExpression(exp, start);
520   - } else {
521   - break;
522   - }
523   - }
524   -
525   - return exp;
526   - }
527   -
528   - getPropertyExpression(idref, computed, start) {
529   - let exp;
530   -
531   - if (computed) {
532   - this.getWS();
533   - exp = this.getExpression();
534   - this.getWS();
535   - if (this._source[this._index] !== ']') {
536   - throw this.error('Expected "]"');
537   - }
538   - ++this._index;
539   - } else {
540   - exp = this.getIdentifier();
541   - }
542   -
543   - const propExpr = new AST.PropertyExpression(idref, exp, computed);
544   - this.setPosition(propExpr, start, this._index);
545   - return propExpr;
546   - }
547   -
548   - getCallExpression(callee, start) {
549   - this.getWS();
550   -
551   - const callExpr = new AST.CallExpression(callee,
552   - this.getItemList(this.getExpression, ')'));
553   - this.setPosition(callExpr, start, this._index);
554   - return callExpr;
555   - }
556   -
557   - getPrimaryExpression() {
558   - const start = this._index;
559   - const ch = this._source[this._index];
560   -
561   - switch (ch) {
562   - case '$':
563   - ++this._index;
564   - const variable = new AST.Variable(this.getIdentifier());
565   - this.setPosition(variable, start, this._index);
566   - return variable;
567   - case '@':
568   - ++this._index;
569   - const global = new AST.Global(this.getIdentifier());
570   - this.setPosition(global, start, this._index);
571   - return global;
572   - default:
573   - return this.getIdentifier();
574   - }
575   - }
576   -
577   - getItemList(callback, closeChar) {
578   - let items = [];
579   - let closed = false;
580   -
581   - this.getWS();
582   -
583   - if (this._source[this._index] === closeChar) {
584   - ++this._index;
585   - closed = true;
586   - }
587   -
588   - while (!closed) {
589   - items.push(callback.call(this));
590   - this.getWS();
591   - let ch = this._source.charAt(this._index);
592   - switch (ch) {
593   - case ',':
594   - ++this._index;
595   - this.getWS();
596   - break;
597   - case closeChar:
598   - ++this._index;
599   - closed = true;
600   - break;
601   - default:
602   - throw this.error('Expected "," or "' + closeChar + '"');
603   - }
604   - }
605   -
606   - return items;
607   - }
608   -
609   - error(message) {
610   - const pos = this._index;
611   -
612   - let start = this._source.lastIndexOf('<', pos - 1);
613   - let lastClose = this._source.lastIndexOf('>', pos - 1);
614   - start = lastClose > start ? lastClose + 1 : start;
615   - let context = this._source.slice(start, pos + 10);
616   -
617   - let msg = message + ' at pos ' + pos + ': `' + context + '`';
618   -
619   - const err = new L10nError(msg);
620   - err._pos = {start: pos, end: undefined};
621   - err.offset = pos - start;
622   - err.description = message;
623   - err.context = context;
624   - return err;
625   - }
626   -
627   - getJunkEntry() {
628   - const pos = this._index;
629   - let nextEntity = this._source.indexOf('<', pos);
630   - let nextComment = this._source.indexOf('/*', pos);
631   -
632   - if (nextEntity === -1) {
633   - nextEntity = this._length;
634   - }
635   - if (nextComment === -1) {
636   - nextComment = this._length;
637   - }
638   -
639   - let nextEntry = Math.min(nextEntity, nextComment);
640   -
641   - this._index = nextEntry;
642   -
643   - const junk = new AST.JunkEntry(
644   - this._source.slice(this._curEntryStart, nextEntry));
645   -
646   - this.setPosition(junk, this._curEntryStart, nextEntry);
647   - return junk;
648   - }
649   - }
650   -
651   - var parser = {
652   - parseResource: function(string, pos = false) {
653   - const parseContext = new ParseContext(string, pos);
654   - return parseContext.getResource();
655   - },
656   - };
657   -
658   - exports.L20nParser = parser;
659   -
660   -});
661 0 \ No newline at end of file
bower_components/l20n/dist/bundle/bridge/l20n-client.js deleted
1   -(function () { 'use strict';
2   -
3   - /* global bridge, BroadcastChannel */
4   -
5   - const Client = bridge.client;
6   - const channel = new BroadcastChannel('l20n-channel');
7   -
8   - // match the opening angle bracket (<) in HTML tags, and HTML entities like
9   - // &amp;, &#0038;, &#x0026;.
10   - const reOverlay = /<|&#?\w+;/;
11   -
12   - const allowed = {
13   - elements: [
14   - 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
15   - 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u',
16   - 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr'
17   - ],
18   - attributes: {
19   - global: [ 'title', 'aria-label', 'aria-valuetext', 'aria-moz-hint' ],
20   - a: [ 'download' ],
21   - area: [ 'download', 'alt' ],
22   - // value is special-cased in isAttrAllowed
23   - input: [ 'alt', 'placeholder' ],
24   - menuitem: [ 'label' ],
25   - menu: [ 'label' ],
26   - optgroup: [ 'label' ],
27   - option: [ 'label' ],
28   - track: [ 'label' ],
29   - img: [ 'alt' ],
30   - textarea: [ 'placeholder' ],
31   - th: [ 'abbr']
32   - }
33   - };
34   -
35   - function overlayElement(element, translation) {
36   - const value = translation.value;
37   -
38   - if (typeof value === 'string') {
39   - if (!reOverlay.test(value)) {
40   - element.textContent = value;
41   - } else {
42   - // start with an inert template element and move its children into
43   - // `element` but such that `element`'s own children are not replaced
44   - const tmpl = element.ownerDocument.createElement('template');
45   - tmpl.innerHTML = value;
46   - // overlay the node with the DocumentFragment
47   - overlay(element, tmpl.content);
48   - }
49   - }
50   -
51   - for (let key in translation.attrs) {
52   - const attrName = camelCaseToDashed(key);
53   - if (isAttrAllowed({ name: attrName }, element)) {
54   - element.setAttribute(attrName, translation.attrs[key]);
55   - }
56   - }
57   - }
58   -
59   - // The goal of overlay is to move the children of `translationElement`
60   - // into `sourceElement` such that `sourceElement`'s own children are not
61   - // replaced, but onle have their text nodes and their attributes modified.
62   - //
63   - // We want to make it possible for localizers to apply text-level semantics to
64   - // the translations and make use of HTML entities. At the same time, we
65   - // don't trust translations so we need to filter unsafe elements and
66   - // attribtues out and we don't want to break the Web by replacing elements to
67   - // which third-party code might have created references (e.g. two-way
68   - // bindings in MVC frameworks).
69   - function overlay(sourceElement, translationElement) {
70   - const result = translationElement.ownerDocument.createDocumentFragment();
71   - let k, attr;
72   -
73   - // take one node from translationElement at a time and check it against
74   - // the allowed list or try to match it with a corresponding element
75   - // in the source
76   - let childElement;
77   - while ((childElement = translationElement.childNodes[0])) {
78   - translationElement.removeChild(childElement);
79   -
80   - if (childElement.nodeType === childElement.TEXT_NODE) {
81   - result.appendChild(childElement);
82   - continue;
83   - }
84   -
85   - const index = getIndexOfType(childElement);
86   - const sourceChild = getNthElementOfType(sourceElement, childElement, index);
87   - if (sourceChild) {
88   - // there is a corresponding element in the source, let's use it
89   - overlay(sourceChild, childElement);
90   - result.appendChild(sourceChild);
91   - continue;
92   - }
93   -
94   - if (isElementAllowed(childElement)) {
95   - const sanitizedChild = childElement.ownerDocument.createElement(
96   - childElement.nodeName);
97   - overlay(sanitizedChild, childElement);
98   - result.appendChild(sanitizedChild);
99   - continue;
100   - }
101   -
102   - // otherwise just take this child's textContent
103   - result.appendChild(
104   - translationElement.ownerDocument.createTextNode(
105   - childElement.textContent));
106   - }
107   -
108   - // clear `sourceElement` and append `result` which by this time contains
109   - // `sourceElement`'s original children, overlayed with translation
110   - sourceElement.textContent = '';
111   - sourceElement.appendChild(result);
112   -
113   - // if we're overlaying a nested element, translate the allowed
114   - // attributes; top-level attributes are handled in `translateElement`
115   - // XXX attributes previously set here for another language should be
116   - // cleared if a new language doesn't use them; https://bugzil.la/922577
117   - if (translationElement.attributes) {
118   - for (k = 0, attr; (attr = translationElement.attributes[k]); k++) {
119   - if (isAttrAllowed(attr, sourceElement)) {
120   - sourceElement.setAttribute(attr.name, attr.value);
121   - }
122   - }
123   - }
124   - }
125   -
126   - // XXX the allowed list should be amendable; https://bugzil.la/922573
127   - function isElementAllowed(element) {
128   - return allowed.elements.indexOf(element.tagName.toLowerCase()) !== -1;
129   - }
130   -
131   - function isAttrAllowed(attr, element) {
132   - const attrName = attr.name.toLowerCase();
133   - const tagName = element.tagName.toLowerCase();
134   - // is it a globally safe attribute?
135   - if (allowed.attributes.global.indexOf(attrName) !== -1) {
136   - return true;
137   - }
138   - // are there no allowed attributes for this element?
139   - if (!allowed.attributes[tagName]) {
140   - return false;
141   - }
142   - // is it allowed on this element?
143   - // XXX the allowed list should be amendable; https://bugzil.la/922573
144   - if (allowed.attributes[tagName].indexOf(attrName) !== -1) {
145   - return true;
146   - }
147   - // special case for value on inputs with type button, reset, submit
148   - if (tagName === 'input' && attrName === 'value') {
149   - const type = element.type.toLowerCase();
150   - if (type === 'submit' || type === 'button' || type === 'reset') {
151   - return true;
152   - }
153   - }
154   - return false;
155   - }
156   -
157   - // Get n-th immediate child of context that is of the same type as element.
158   - // XXX Use querySelector(':scope > ELEMENT:nth-of-type(index)'), when:
159   - // 1) :scope is widely supported in more browsers and 2) it works with
160   - // DocumentFragments.
161   - function getNthElementOfType(context, element, index) {
162   - /* jshint boss:true */
163   - let nthOfType = 0;
164   - for (let i = 0, child; child = context.children[i]; i++) {
165   - if (child.nodeType === child.ELEMENT_NODE &&
166   - child.tagName === element.tagName) {
167   - if (nthOfType === index) {
168   - return child;
169   - }
170   - nthOfType++;
171   - }
172   - }
173   - return null;
174   - }
175   -
176   - // Get the index of the element among siblings of the same type.
177   - function getIndexOfType(element) {
178   - let index = 0;
179   - let child;
180   - while ((child = element.previousElementSibling)) {
181   - if (child.tagName === element.tagName) {
182   - index++;
183   - }
184   - }
185   - return index;
186   - }
187   -
188   - function camelCaseToDashed(string) {
189   - // XXX workaround for https://bugzil.la/1141934
190   - if (string === 'ariaValueText') {
191   - return 'aria-valuetext';
192   - }
193   -
194   - return string
195   - .replace(/[A-Z]/g, function (match) {
196   - return '-' + match.toLowerCase();
197   - })
198   - .replace(/^-/, '');
199   - }
200   -
201   - const reHtml = /[&<>]/g;
202   - const htmlEntities = {
203   - '&': '&amp;',
204   - '<': '&lt;',
205   - '>': '&gt;',
206   - };
207   -
208   - function getResourceLinks(head) {
209   - return Array.prototype.map.call(
210   - head.querySelectorAll('link[rel="localization"]'),
211   - el => el.getAttribute('href'));
212   - }
213   -
214   - function setAttributes(element, id, args) {
215   - element.setAttribute('data-l10n-id', id);
216   - if (args) {
217   - element.setAttribute('data-l10n-args', JSON.stringify(args));
218   - }
219   - }
220   -
221   - function getAttributes(element) {
222   - return {
223   - id: element.getAttribute('data-l10n-id'),
224   - args: JSON.parse(element.getAttribute('data-l10n-args'))
225   - };
226   - }
227   -
228   - function getTranslatables(element) {
229   - const nodes = Array.from(element.querySelectorAll('[data-l10n-id]'));
230   -
231   - if (typeof element.hasAttribute === 'function' &&
232   - element.hasAttribute('data-l10n-id')) {
233   - nodes.push(element);
234   - }
235   -
236   - return nodes;
237   - }
238   -
239   - function translateMutations(view, langs, mutations) {
240   - const targets = new Set();
241   -
242   - for (let mutation of mutations) {
243   - switch (mutation.type) {
244   - case 'attributes':
245   - targets.add(mutation.target);
246   - break;
247   - case 'childList':
248   - for (let addedNode of mutation.addedNodes) {
249   - if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
250   - if (addedNode.childElementCount) {
251   - getTranslatables(addedNode).forEach(targets.add.bind(targets));
252   - } else {
253   - if (addedNode.hasAttribute('data-l10n-id')) {
254   - targets.add(addedNode);
255   - }
256   - }
257   - }
258   - }
259   - break;
260   - }
261   - }
262   -
263   - if (targets.size === 0) {
264   - return;
265   - }
266   -
267   - translateElements(view, langs, Array.from(targets));
268   - }
269   -
270   - function translateFragment(view, langs, frag) {
271   - return translateElements(view, langs, getTranslatables(frag));
272   - }
273   -
274   - function getElementsTranslation(view, langs, elems) {
275   - const keys = elems.map(elem => {
276   - const id = elem.getAttribute('data-l10n-id');
277   - const args = elem.getAttribute('data-l10n-args');
278   - return args ? [
279   - id,
280   - JSON.parse(args.replace(reHtml, match => htmlEntities[match]))
281   - ] : id;
282   - });
283   -
284   - return view._resolveEntities(langs, keys);
285   - }
286   -
287   - function translateElements(view, langs, elements) {
288   - return getElementsTranslation(view, langs, elements).then(
289   - translations => applyTranslations(view, elements, translations));
290   - }
291   -
292   - function applyTranslations(view, elems, translations) {
293   - view._disconnect();
294   - for (let i = 0; i < elems.length; i++) {
295   - overlayElement(elems[i], translations[i]);
296   - }
297   - view._observe();
298   - }
299   -
300   - // Polyfill NodeList.prototype[Symbol.iterator] for Chrome.
301   - // See https://code.google.com/p/chromium/issues/detail?id=401699
302   - if (typeof NodeList === 'function' && !NodeList.prototype[Symbol.iterator]) {
303   - NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
304   - }
305   -
306   - // A document.ready shim
307   - // https://github.com/whatwg/html/issues/127
308   - function documentReady() {
309   - if (document.readyState !== 'loading') {
310   - return Promise.resolve();
311   - }
312   -
313   - return new Promise(resolve => {
314   - document.addEventListener('readystatechange', function onrsc() {
315   - document.removeEventListener('readystatechange', onrsc);
316   - resolve();
317   - });
318   - });
319   - }
320   -
321   - // Intl.Locale
322   - function getDirection(code) {
323   - const tag = code.split('-')[0];
324   - return ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(tag) >= 0 ?
325   - 'rtl' : 'ltr';
326   - }
327   -
328   - const observerConfig = {
329   - attributes: true,
330   - characterData: false,
331   - childList: true,
332   - subtree: true,
333   - attributeFilter: ['data-l10n-id', 'data-l10n-args']
334   - };
335   -
336   - const readiness = new WeakMap();
337   -
338   - class View {
339   - constructor(client, doc) {
340   - this._doc = doc;
341   - this.pseudo = {
342   - 'fr-x-psaccent': createPseudo(this, 'fr-x-psaccent'),
343   - 'ar-x-psbidi': createPseudo(this, 'ar-x-psbidi')
344   - };
345   -
346   - this._interactive = documentReady().then(
347   - () => init(this, client));
348   -
349   - const observer = new MutationObserver(onMutations.bind(this));
350   - this._observe = () => observer.observe(doc, observerConfig);
351   - this._disconnect = () => observer.disconnect();
352   -
353   - const translateView = langs => translateDocument(this, langs);
354   - client.on('translateDocument', translateView);
355   - this.ready = this._interactive.then(
356   - client => client.method('resolvedLanguages')).then(
357   - translateView);
358   - }
359   -
360   - requestLanguages(langs, global) {
361   - return this._interactive.then(
362   - client => client.method('requestLanguages', langs, global));
363   - }
364   -
365   - _resolveEntities(langs, keys) {
366   - return this._interactive.then(
367   - client => client.method('resolveEntities', client.id, langs, keys));
368   - }
369   -
370   - formatValue(id, args) {
371   - return this._interactive.then(
372   - client => client.method('formatValues', client.id, [[id, args]])).then(
373   - values => values[0]);
374   - }
375   -
376   - formatValues(...keys) {
377   - return this._interactive.then(
378   - client => client.method('formatValues', client.id, keys));
379   - }
380   -
381   - translateFragment(frag) {
382   - return this._interactive.then(
383   - client => client.method('resolvedLanguages')).then(
384   - langs => translateFragment(this, langs, frag));
385   - }
386   - }
387   -
388   - View.prototype.setAttributes = setAttributes;
389   - View.prototype.getAttributes = getAttributes;
390   -
391   - function createPseudo(view, code) {
392   - return {
393   - getName: () => view._interactive.then(
394   - client => client.method('getName', code)),
395   - processString: str => view._interactive.then(
396   - client => client.method('processString', code, str)),
397   - };
398   - }
399   -
400   - function init(view, client) {
401   - view._observe();
402   - return client.method(
403   - 'registerView', client.id, getResourceLinks(view._doc.head)).then(
404   - () => client);
405   - }
406   -
407   - function onMutations(mutations) {
408   - return this._interactive.then(
409   - client => client.method('resolvedLanguages')).then(
410   - langs => translateMutations(this, langs, mutations));
411   - }
412   -
413   - function translateDocument(view, langs) {
414   - const html = view._doc.documentElement;
415   -
416   - if (readiness.has(html)) {
417   - return translateFragment(view, langs, html).then(
418   - () => setAllAndEmit(html, langs));
419   - }
420   -
421   - const translated =
422   - // has the document been already pre-translated?
423   - langs[0].code === html.getAttribute('lang') ?
424   - Promise.resolve() :
425   - translateFragment(view, langs, html).then(
426   - () => setLangDir(html, langs));
427   -
428   - return translated.then(() => {
429   - setLangs(html, langs);
430   - readiness.set(html, true);
431   - });
432   - }
433   -
434   - function setLangs(html, langs) {
435   - const codes = langs.map(lang => lang.code);
436   - html.setAttribute('langs', codes.join(' '));
437   - }
438   -
439   - function setLangDir(html, langs) {
440   - const code = langs[0].code;
441   - html.setAttribute('lang', code);
442   - html.setAttribute('dir', getDirection(code));
443   - }
444   -
445   - function setAllAndEmit(html, langs) {
446   - setLangDir(html, langs);
447   - setLangs(html, langs);
448   - html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated', {
449   - bubbles: false,
450   - cancelable: false,
451   - }));
452   - }
453   -
454   - const client = new Client({
455   - service: 'l20n',
456   - endpoint: channel,
457   - timeout: false
458   - });
459   -
460   - window.addEventListener('pageshow', () => client.connect());
461   - window.addEventListener('pagehide', () => client.disconnect());
462   -
463   - document.l10n = new View(client, document);
464   -
465   - //Bug 1204660 - Temporary proxy for shared code. Will be removed once
466   - // l10n.js migration is completed.
467   - navigator.mozL10n = {
468   - setAttributes: document.l10n.setAttributes,
469   - getAttributes: document.l10n.getAttributes,
470   - formatValue: (...args) => document.l10n.formatValue(...args),
471   - translateFragment: (...args) => document.l10n.translateFragment(...args),
472   - once: cb => document.l10n.ready.then(cb),
473   - ready: cb => document.l10n.ready.then(() => {
474   - document.addEventListener('DOMRetranslated', cb);
475   - cb();
476   - }),
477   - };
478   -
479   -})();
480 0 \ No newline at end of file
bower_components/l20n/dist/bundle/bridge/l20n-service.js deleted
1   -(function () { 'use strict';
2   -
3   - const Service = bridge.service;
4   - const channel = new BroadcastChannel('l20n-channel');
5   -
6   - function broadcast(type, data) {
7   - return this.service.broadcast(type, data);
8   - }
9   -
10   - function L10nError(message, id, lang) {
11   - this.name = 'L10nError';
12   - this.message = message;
13   - this.id = id;
14   - this.lang = lang;
15   - }
16   - L10nError.prototype = Object.create(Error.prototype);
17   - L10nError.prototype.constructor = L10nError;
18   -
19   - function load(type, url) {
20   - return new Promise(function(resolve, reject) {
21   - const xhr = new XMLHttpRequest();
22   -
23   - if (xhr.overrideMimeType) {
24   - xhr.overrideMimeType(type);
25   - }
26   -
27   - xhr.open('GET', url, true);
28   -
29   - if (type === 'application/json') {
30   - xhr.responseType = 'json';
31   - }
32   -
33   - xhr.addEventListener('load', function io_onload(e) {
34   - if (e.target.status === 200 || e.target.status === 0) {
35   - // Sinon.JS's FakeXHR doesn't have the response property
36   - resolve(e.target.response || e.target.responseText);
37   - } else {
38   - reject(new L10nError('Not found: ' + url));
39   - }
40   - });
41   - xhr.addEventListener('error', reject);
42   - xhr.addEventListener('timeout', reject);
43   -
44   - // the app: protocol throws on 404, see https://bugzil.la/827243
45   - try {
46   - xhr.send(null);
47   - } catch (e) {
48   - if (e.name === 'NS_ERROR_FILE_NOT_FOUND') {
49   - // the app: protocol throws on 404, see https://bugzil.la/827243
50   - reject(new L10nError('Not found: ' + url));
51   - } else {
52   - throw e;
53   - }
54   - }
55   - });
56   - }
57   -
58   - const io = {
59   - extra: function(code, ver, path, type) {
60   - return navigator.mozApps.getLocalizationResource(
61   - code, ver, path, type);
62   - },
63   - app: function(code, ver, path, type) {
64   - switch (type) {
65   - case 'text':
66   - return load('text/plain', path);
67   - case 'json':
68   - return load('application/json', path);
69   - default:
70   - throw new L10nError('Unknown file type: ' + type);
71   - }
72   - },
73   - };
74   -
75   - function fetchResource(ver, res, lang) {
76   - const url = res.replace('{locale}', lang.code);
77   - const type = res.endsWith('.json') ? 'json' : 'text';
78   - return io[lang.src](lang.code, ver, url, type);
79   - }
80   -
81   - const MAX_PLACEABLES$1 = 100;
82   -
83   - var L20nParser = {
84   - parse: function(emit, string) {
85   - this._source = string;
86   - this._index = 0;
87   - this._length = string.length;
88   - this.entries = Object.create(null);
89   - this.emit = emit;
90   -
91   - return this.getResource();
92   - },
93   -
94   - getResource: function() {
95   - this.getWS();
96   - while (this._index < this._length) {
97   - try {
98   - this.getEntry();
99   - } catch (e) {
100   - if (e instanceof L10nError) {
101   - // we want to recover, but we don't need it in entries
102   - this.getJunkEntry();
103   - if (!this.emit) {
104   - throw e;
105   - }
106   - } else {
107   - throw e;
108   - }
109   - }
110   -
111   - if (this._index < this._length) {
112   - this.getWS();
113   - }
114   - }
115   -
116   - return this.entries;
117   - },
118   -
119   - getEntry: function() {
120   - if (this._source[this._index] === '<') {
121   - ++this._index;
122   - const id = this.getIdentifier();
123   - if (this._source[this._index] === '[') {
124   - ++this._index;
125   - return this.getEntity(id, this.getItemList(this.getExpression, ']'));
126   - }
127   - return this.getEntity(id);
128   - }
129   -
130   - if (this._source.startsWith('/*', this._index)) {
131   - return this.getComment();
132   - }
133   -
134   - throw this.error('Invalid entry');
135   - },
136   -
137   - getEntity: function(id, index) {
138   - if (!this.getRequiredWS()) {
139   - throw this.error('Expected white space');
140   - }
141   -
142   - const ch = this._source[this._index];
143   - const value = this.getValue(ch, index === undefined);
144   - let attrs;
145   -
146   - if (value === undefined) {
147   - if (ch === '>') {
148   - throw this.error('Expected ">"');
149   - }
150   - attrs = this.getAttributes();
151   - } else {
152   - const ws1 = this.getRequiredWS();
153   - if (this._source[this._index] !== '>') {
154   - if (!ws1) {
155   - throw this.error('Expected ">"');
156   - }
157   - attrs = this.getAttributes();
158   - }
159   - }
160   -
161   - // skip '>'
162   - ++this._index;
163   -
164   - if (id in this.entries) {
165   - throw this.error('Duplicate entry ID "' + id, 'duplicateerror');
166   - }
167   - if (!attrs && !index && typeof value === 'string') {
168   - this.entries[id] = value;
169   - } else {
170   - this.entries[id] = {
171   - value,
172   - attrs,
173   - index
174   - };
175   - }
176   - },
177   -
178   - getValue: function(ch = this._source[this._index], optional = false) {
179   - switch (ch) {
180   - case '\'':
181   - case '"':
182   - return this.getString(ch, 1);
183   - case '{':
184   - return this.getHash();
185   - }
186   -
187   - if (!optional) {
188   - throw this.error('Unknown value type');
189   - }
190   -
191   - return;
192   - },
193   -
194   - getWS: function() {
195   - let cc = this._source.charCodeAt(this._index);
196   - // space, \n, \t, \r
197   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
198   - cc = this._source.charCodeAt(++this._index);
199   - }
200   - },
201   -
202   - getRequiredWS: function() {
203   - const pos = this._index;
204   - let cc = this._source.charCodeAt(pos);
205   - // space, \n, \t, \r
206   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
207   - cc = this._source.charCodeAt(++this._index);
208   - }
209   - return this._index !== pos;
210   - },
211   -
212   - getIdentifier: function() {
213   - const start = this._index;
214   - let cc = this._source.charCodeAt(this._index);
215   -
216   - if ((cc >= 97 && cc <= 122) || // a-z
217   - (cc >= 65 && cc <= 90) || // A-Z
218   - cc === 95) { // _
219   - cc = this._source.charCodeAt(++this._index);
220   - } else {
221   - throw this.error('Identifier has to start with [a-zA-Z_]');
222   - }
223   -
224   - while ((cc >= 97 && cc <= 122) || // a-z
225   - (cc >= 65 && cc <= 90) || // A-Z
226   - (cc >= 48 && cc <= 57) || // 0-9
227   - cc === 95) { // _
228   - cc = this._source.charCodeAt(++this._index);
229   - }
230   -
231   - return this._source.slice(start, this._index);
232   - },
233   -
234   - getUnicodeChar: function() {
235   - for (let i = 0; i < 4; i++) {
236   - let cc = this._source.charCodeAt(++this._index);
237   - if ((cc > 96 && cc < 103) || // a-f
238   - (cc > 64 && cc < 71) || // A-F
239   - (cc > 47 && cc < 58)) { // 0-9
240   - continue;
241   - }
242   - throw this.error('Illegal unicode escape sequence');
243   - }
244   - this._index++;
245   - return String.fromCharCode(
246   - parseInt(this._source.slice(this._index - 4, this._index), 16));
247   - },
248   -
249   - stringRe: /"|'|{{|\\/g,
250   - getString: function(opchar, opcharLen) {
251   - const body = [];
252   - let placeables = 0;
253   -
254   - this._index += opcharLen;
255   - const start = this._index;
256   -
257   - let bufStart = start;
258   - let buf = '';
259   -
260   - while (true) {
261   - this.stringRe.lastIndex = this._index;
262   - const match = this.stringRe.exec(this._source);
263   -
264   - if (!match) {
265   - throw this.error('Unclosed string literal');
266   - }
267   -
268   - if (match[0] === '"' || match[0] === '\'') {
269   - if (match[0] !== opchar) {
270   - this._index += opcharLen;
271   - continue;
272   - }
273   - this._index = match.index + opcharLen;
274   - break;
275   - }
276   -
277   - if (match[0] === '{{') {
278   - if (placeables > MAX_PLACEABLES$1 - 1) {
279   - throw this.error('Too many placeables, maximum allowed is ' +
280   - MAX_PLACEABLES$1);
281   - }
282   - placeables++;
283   - if (match.index > bufStart || buf.length > 0) {
284   - body.push(buf + this._source.slice(bufStart, match.index));
285   - buf = '';
286   - }
287   - this._index = match.index + 2;
288   - this.getWS();
289   - body.push(this.getExpression());
290   - this.getWS();
291   - this._index += 2;
292   - bufStart = this._index;
293   - continue;
294   - }
295   -
296   - if (match[0] === '\\') {
297   - this._index = match.index + 1;
298   - const ch2 = this._source[this._index];
299   - if (ch2 === 'u') {
300   - buf += this._source.slice(bufStart, match.index) +
301   - this.getUnicodeChar();
302   - } else if (ch2 === opchar || ch2 === '\\') {
303   - buf += this._source.slice(bufStart, match.index) + ch2;
304   - this._index++;
305   - } else if (this._source.startsWith('{{', this._index)) {
306   - buf += this._source.slice(bufStart, match.index) + '{{';
307   - this._index += 2;
308   - } else {
309   - throw this.error('Illegal escape sequence');
310   - }
311   - bufStart = this._index;
312   - }
313   - }
314   -
315   - if (body.length === 0) {
316   - return buf + this._source.slice(bufStart, this._index - opcharLen);
317   - }
318   -
319   - if (this._index - opcharLen > bufStart || buf.length > 0) {
320   - body.push(buf + this._source.slice(bufStart, this._index - opcharLen));
321   - }
322   -
323   - return body;
324   - },
325   -
326   - getAttributes: function() {
327   - const attrs = Object.create(null);
328   -
329   - while (true) {
330   - this.getAttribute(attrs);
331   - const ws1 = this.getRequiredWS();
332   - const ch = this._source.charAt(this._index);
333   - if (ch === '>') {
334   - break;
335   - } else if (!ws1) {
336   - throw this.error('Expected ">"');
337   - }
338   - }
339   - return attrs;
340   - },
341   -
342   - getAttribute: function(attrs) {
343   - const key = this.getIdentifier();
344   - let index;
345   -
346   - if (this._source[this._index]=== '[') {
347   - ++this._index;
348   - this.getWS();
349   - index = this.getItemList(this.getExpression, ']');
350   - }
351   - this.getWS();
352   - if (this._source[this._index] !== ':') {
353   - throw this.error('Expected ":"');
354   - }
355   - ++this._index;
356   - this.getWS();
357   - const value = this.getValue();
358   -
359   - if (key in attrs) {
360   - throw this.error('Duplicate attribute "' + key, 'duplicateerror');
361   - }
362   -
363   - if (!index && typeof value === 'string') {
364   - attrs[key] = value;
365   - } else {
366   - attrs[key] = {
367   - value,
368   - index
369   - };
370   - }
371   - },
372   -
373   - getHash: function() {
374   - const items = Object.create(null);
375   -
376   - ++this._index;
377   - this.getWS();
378   -
379   - let defKey;
380   -
381   - while (true) {
382   - const [key, value, def] = this.getHashItem();
383   - items[key] = value;
384   -
385   - if (def) {
386   - if (defKey) {
387   - throw this.error('Default item redefinition forbidden');
388   - }
389   - defKey = key;
390   - }
391   - this.getWS();
392   -
393   - const comma = this._source[this._index] === ',';
394   - if (comma) {
395   - ++this._index;
396   - this.getWS();
397   - }
398   - if (this._source[this._index] === '}') {
399   - ++this._index;
400   - break;
401   - }
402   - if (!comma) {
403   - throw this.error('Expected "}"');
404   - }
405   - }
406   -
407   - if (defKey) {
408   - items.__default = defKey;
409   - }
410   -
411   - return items;
412   - },
413   -
414   - getHashItem: function() {
415   - let defItem = false;
416   - if (this._source[this._index] === '*') {
417   - ++this._index;
418   - defItem = true;
419   - }
420   -
421   - const key = this.getIdentifier();
422   - this.getWS();
423   - if (this._source[this._index] !== ':') {
424   - throw this.error('Expected ":"');
425   - }
426   - ++this._index;
427   - this.getWS();
428   -
429   - return [key, this.getValue(), defItem];
430   - },
431   -
432   - getComment: function() {
433   - this._index += 2;
434   - const start = this._index;
435   - const end = this._source.indexOf('*/', start);
436   -
437   - if (end === -1) {
438   - throw this.error('Comment without a closing tag');
439   - }
440   -
441   - this._index = end + 2;
442   - },
443   -
444   - getExpression: function () {
445   - let exp = this.getPrimaryExpression();
446   -
447   - while (true) {
448   - let ch = this._source[this._index];
449   - if (ch === '.' || ch === '[') {
450   - ++this._index;
451   - exp = this.getPropertyExpression(exp, ch === '[');
452   - } else if (ch === '(') {
453   - ++this._index;
454   - exp = this.getCallExpression(exp);
455   - } else {
456   - break;
457   - }
458   - }
459   -
460   - return exp;
461   - },
462   -
463   - getPropertyExpression: function(idref, computed) {
464   - let exp;
465   -
466   - if (computed) {
467   - this.getWS();
468   - exp = this.getExpression();
469   - this.getWS();
470   - if (this._source[this._index] !== ']') {
471   - throw this.error('Expected "]"');
472   - }
473   - ++this._index;
474   - } else {
475   - exp = this.getIdentifier();
476   - }
477   -
478   - return {
479   - type: 'prop',
480   - expr: idref,
481   - prop: exp,
482   - cmpt: computed
483   - };
484   - },
485   -
486   - getCallExpression: function(callee) {
487   - this.getWS();
488   -
489   - return {
490   - type: 'call',
491   - expr: callee,
492   - args: this.getItemList(this.getExpression, ')')
493   - };
494   - },
495   -
496   - getPrimaryExpression: function() {
497   - const ch = this._source[this._index];
498   -
499   - switch (ch) {
500   - case '$':
501   - ++this._index;
502   - return {
503   - type: 'var',
504   - name: this.getIdentifier()
505   - };
506   - case '@':
507   - ++this._index;
508   - return {
509   - type: 'glob',
510   - name: this.getIdentifier()
511   - };
512   - default:
513   - return {
514   - type: 'id',
515   - name: this.getIdentifier()
516   - };
517   - }
518   - },
519   -
520   - getItemList: function(callback, closeChar) {
521   - const items = [];
522   - let closed = false;
523   -
524   - this.getWS();
525   -
526   - if (this._source[this._index] === closeChar) {
527   - ++this._index;
528   - closed = true;
529   - }
530   -
531   - while (!closed) {
532   - items.push(callback.call(this));
533   - this.getWS();
534   - let ch = this._source.charAt(this._index);
535   - switch (ch) {
536   - case ',':
537   - ++this._index;
538   - this.getWS();
539   - break;
540   - case closeChar:
541   - ++this._index;
542   - closed = true;
543   - break;
544   - default:
545   - throw this.error('Expected "," or "' + closeChar + '"');
546   - }
547   - }
548   -
549   - return items;
550   - },
551   -
552   -
553   - getJunkEntry: function() {
554   - const pos = this._index;
555   - let nextEntity = this._source.indexOf('<', pos);
556   - let nextComment = this._source.indexOf('/*', pos);
557   -
558   - if (nextEntity === -1) {
559   - nextEntity = this._length;
560   - }
561   - if (nextComment === -1) {
562   - nextComment = this._length;
563   - }
564   -
565   - let nextEntry = Math.min(nextEntity, nextComment);
566   -
567   - this._index = nextEntry;
568   - },
569   -
570   - error: function(message, type = 'parsererror') {
571   - const pos = this._index;
572   -
573   - let start = this._source.lastIndexOf('<', pos - 1);
574   - const lastClose = this._source.lastIndexOf('>', pos - 1);
575   - start = lastClose > start ? lastClose + 1 : start;
576   - const context = this._source.slice(start, pos + 10);
577   -
578   - const msg = message + ' at pos ' + pos + ': `' + context + '`';
579   - const err = new L10nError(msg);
580   - if (this.emit) {
581   - this.emit(type, err);
582   - }
583   - return err;
584   - },
585   - };
586   -
587   - var MAX_PLACEABLES = 100;
588   -
589   - var PropertiesParser = {
590   - patterns: null,
591   - entryIds: null,
592   - emit: null,
593   -
594   - init: function() {
595   - this.patterns = {
596   - comment: /^\s*#|^\s*$/,
597   - entity: /^([^=\s]+)\s*=\s*(.*)$/,
598   - multiline: /[^\\]\\$/,
599   - index: /\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,
600   - unicode: /\\u([0-9a-fA-F]{1,4})/g,
601   - entries: /[^\r\n]+/g,
602   - controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g,
603   - placeables: /\{\{\s*([^\s]*?)\s*\}\}/,
604   - };
605   - },
606   -
607   - parse: function(emit, source) {
608   - if (!this.patterns) {
609   - this.init();
610   - }
611   - this.emit = emit;
612   -
613   - var entries = {};
614   -
615   - var lines = source.match(this.patterns.entries);
616   - if (!lines) {
617   - return entries;
618   - }
619   - for (var i = 0; i < lines.length; i++) {
620   - var line = lines[i];
621   -
622   - if (this.patterns.comment.test(line)) {
623   - continue;
624   - }
625   -
626   - while (this.patterns.multiline.test(line) && i < lines.length) {
627   - line = line.slice(0, -1) + lines[++i].trim();
628   - }
629   -
630   - var entityMatch = line.match(this.patterns.entity);
631   - if (entityMatch) {
632   - try {
633   - this.parseEntity(entityMatch[1], entityMatch[2], entries);
634   - } catch (e) {
635   - if (!this.emit) {
636   - throw e;
637   - }
638   - }
639   - }
640   - }
641   - return entries;
642   - },
643   -
644   - parseEntity: function(id, value, entries) {
645   - var name, key;
646   -
647   - var pos = id.indexOf('[');
648   - if (pos !== -1) {
649   - name = id.substr(0, pos);
650   - key = id.substring(pos + 1, id.length - 1);
651   - } else {
652   - name = id;
653   - key = null;
654   - }
655   -
656   - var nameElements = name.split('.');
657   -
658   - if (nameElements.length > 2) {
659   - throw this.error('Error in ID: "' + name + '".' +
660   - ' Nested attributes are not supported.');
661   - }
662   -
663   - var attr;
664   - if (nameElements.length > 1) {
665   - name = nameElements[0];
666   - attr = nameElements[1];
667   -
668   - if (attr[0] === '$') {
669   - throw this.error('Attribute can\'t start with "$"');
670   - }
671   - } else {
672   - attr = null;
673   - }
674   -
675   - this.setEntityValue(name, attr, key, this.unescapeString(value), entries);
676   - },
677   -
678   - setEntityValue: function(id, attr, key, rawValue, entries) {
679   - var value = rawValue.indexOf('{{') > -1 ?
680   - this.parseString(rawValue) : rawValue;
681   -
682   - var isSimpleValue = typeof value === 'string';
683   - var root = entries;
684   -
685   - var isSimpleNode = typeof entries[id] === 'string';
686   -
687   - if (!entries[id] && (attr || key || !isSimpleValue)) {
688   - entries[id] = Object.create(null);
689   - isSimpleNode = false;
690   - }
691   -
692   - if (attr) {
693   - if (isSimpleNode) {
694   - const val = entries[id];
695   - entries[id] = Object.create(null);
696   - entries[id].value = val;
697   - }
698   - if (!entries[id].attrs) {
699   - entries[id].attrs = Object.create(null);
700   - }
701   - if (!entries[id].attrs && !isSimpleValue) {
702   - entries[id].attrs[attr] = Object.create(null);
703   - }
704   - root = entries[id].attrs;
705   - id = attr;
706   - }
707   -
708   - if (key) {
709   - isSimpleNode = false;
710   - if (typeof root[id] === 'string') {
711   - const val = root[id];
712   - root[id] = Object.create(null);
713   - root[id].index = this.parseIndex(val);
714   - root[id].value = Object.create(null);
715   - }
716   - root = root[id].value;
717   - id = key;
718   - isSimpleValue = true;
719   - }
720   -
721   - if (isSimpleValue && (!entries[id] || isSimpleNode)) {
722   - if (id in root) {
723   - throw this.error();
724   - }
725   - root[id] = value;
726   - } else {
727   - if (!root[id]) {
728   - root[id] = Object.create(null);
729   - }
730   - root[id].value = value;
731   - }
732   - },
733   -
734   - parseString: function(str) {
735   - var chunks = str.split(this.patterns.placeables);
736   - var complexStr = [];
737   -
738   - var len = chunks.length;
739   - var placeablesCount = (len - 1) / 2;
740   -
741   - if (placeablesCount >= MAX_PLACEABLES) {
742   - throw this.error('Too many placeables (' + placeablesCount +
743   - ', max allowed is ' + MAX_PLACEABLES + ')');
744   - }
745   -
746   - for (var i = 0; i < chunks.length; i++) {
747   - if (chunks[i].length === 0) {
748   - continue;
749   - }
750   - if (i % 2 === 1) {
751   - complexStr.push({type: 'idOrVar', name: chunks[i]});
752   - } else {
753   - complexStr.push(chunks[i]);
754   - }
755   - }
756   - return complexStr;
757   - },
758   -
759   - unescapeString: function(str) {
760   - if (str.lastIndexOf('\\') !== -1) {
761   - str = str.replace(this.patterns.controlChars, '$1');
762   - }
763   - return str.replace(this.patterns.unicode, function(match, token) {
764   - return String.fromCodePoint(parseInt(token, 16));
765   - });
766   - },
767   -
768   - parseIndex: function(str) {
769   - var match = str.match(this.patterns.index);
770   - if (!match) {
771   - throw new L10nError('Malformed index');
772   - }
773   - if (match[2]) {
774   - return [{
775   - type: 'call',
776   - expr: {
777   - type: 'prop',
778   - expr: {
779   - type: 'glob',
780   - name: 'cldr'
781   - },
782   - prop: 'plural',
783   - cmpt: false
784   - }, args: [{
785   - type: 'idOrVar',
786   - name: match[2]
787   - }]
788   - }];
789   - } else {
790   - return [{type: 'idOrVar', name: match[1]}];
791   - }
792   - },
793   -
794   - error: function(msg, type = 'parsererror') {
795   - const err = new L10nError(msg);
796   - if (this.emit) {
797   - this.emit(type, err);
798   - }
799   - return err;
800   - }
801   - };
802   -
803   - const KNOWN_MACROS = ['plural'];
804   - const MAX_PLACEABLE_LENGTH = 2500;
805   -
806   - // Unicode bidi isolation characters
807   - const FSI = '\u2068';
808   - const PDI = '\u2069';
809   -
810   - const resolutionChain = new WeakSet();
811   -
812   - function format(ctx, lang, args, entity) {
813   - if (typeof entity === 'string') {
814   - return [{}, entity];
815   - }
816   -
817   - if (resolutionChain.has(entity)) {
818   - throw new L10nError('Cyclic reference detected');
819   - }
820   -
821   - resolutionChain.add(entity);
822   -
823   - let rv;
824   - // if format fails, we want the exception to bubble up and stop the whole
825   - // resolving process; however, we still need to remove the entity from the
826   - // resolution chain
827   - try {
828   - rv = resolveValue(
829   - {}, ctx, lang, args, entity.value, entity.index);
830   - } finally {
831   - resolutionChain.delete(entity);
832   - }
833   - return rv;
834   - }
835   -
836   - function resolveIdentifier(ctx, lang, args, id) {
837   - if (KNOWN_MACROS.indexOf(id) > -1) {
838   - return [{}, ctx._getMacro(lang, id)];
839   - }
840   -
841   - if (args && args.hasOwnProperty(id)) {
842   - if (typeof args[id] === 'string' || (typeof args[id] === 'number' &&
843   - !isNaN(args[id]))) {
844   - return [{}, args[id]];
845   - } else {
846   - throw new L10nError('Arg must be a string or a number: ' + id);
847   - }
848   - }
849   -
850   - // XXX: special case for Node.js where still:
851   - // '__proto__' in Object.create(null) => true
852   - if (id === '__proto__') {
853   - throw new L10nError('Illegal id: ' + id);
854   - }
855   -
856   - const entity = ctx._getEntity(lang, id);
857   -
858   - if (entity) {
859   - return format(ctx, lang, args, entity);
860   - }
861   -
862   - throw new L10nError('Unknown reference: ' + id);
863   - }
864   -
865   - function subPlaceable(locals, ctx, lang, args, id) {
866   - let newLocals, value;
867   -
868   - try {
869   - [newLocals, value] = resolveIdentifier(ctx, lang, args, id);
870   - } catch (err) {
871   - return [{ error: err }, FSI + '{{ ' + id + ' }}' + PDI];
872   - }
873   -
874   - if (typeof value === 'number') {
875   - const formatter = ctx._getNumberFormatter(lang);
876   - return [newLocals, formatter.format(value)];
877   - }
878   -
879   - if (typeof value === 'string') {
880   - // prevent Billion Laughs attacks
881   - if (value.length >= MAX_PLACEABLE_LENGTH) {
882   - throw new L10nError('Too many characters in placeable (' +
883   - value.length + ', max allowed is ' +
884   - MAX_PLACEABLE_LENGTH + ')');
885   - }
886   - return [newLocals, FSI + value + PDI];
887   - }
888   -
889   - return [{}, FSI + '{{ ' + id + ' }}' + PDI];
890   - }
891   -
892   - function interpolate(locals, ctx, lang, args, arr) {
893   - return arr.reduce(function([localsSeq, valueSeq], cur) {
894   - if (typeof cur === 'string') {
895   - return [localsSeq, valueSeq + cur];
896   - } else {
897   - const [, value] = subPlaceable(locals, ctx, lang, args, cur.name);
898   - // wrap the substitution in bidi isolate characters
899   - return [localsSeq, valueSeq + value];
900   - }
901   - }, [locals, '']);
902   - }
903   -
904   - function resolveSelector(ctx, lang, args, expr, index) {
905   - //XXX: Dehardcode!!!
906   - let selectorName;
907   - if (index[0].type === 'call' && index[0].expr.type === 'prop' &&
908   - index[0].expr.expr.name === 'cldr') {
909   - selectorName = 'plural';
910   - } else {
911   - selectorName = index[0].name;
912   - }
913   - const selector = resolveIdentifier(ctx, lang, args, selectorName)[1];
914   -
915   - if (typeof selector !== 'function') {
916   - // selector is a simple reference to an entity or args
917   - return selector;
918   - }
919   -
920   - const argValue = index[0].args ?
921   - resolveIdentifier(ctx, lang, args, index[0].args[0].name)[1] : undefined;
922   -
923   - if (selectorName === 'plural') {
924   - // special cases for zero, one, two if they are defined on the hash
925   - if (argValue === 0 && 'zero' in expr) {
926   - return 'zero';
927   - }
928   - if (argValue === 1 && 'one' in expr) {
929   - return 'one';
930   - }
931   - if (argValue === 2 && 'two' in expr) {
932   - return 'two';
933   - }
934   - }
935   -
936   - return selector(argValue);
937   - }
938   -
939   - function resolveValue(locals, ctx, lang, args, expr, index) {
940   - if (!expr) {
941   - return [locals, expr];
942   - }
943   -
944   - if (typeof expr === 'string' ||
945   - typeof expr === 'boolean' ||
946   - typeof expr === 'number') {
947   - return [locals, expr];
948   - }
949   -
950   - if (Array.isArray(expr)) {
951   - return interpolate(locals, ctx, lang, args, expr);
952   - }
953   -
954   - // otherwise, it's a dict
955   - if (index) {
956   - // try to use the index in order to select the right dict member
957   - const selector = resolveSelector(ctx, lang, args, expr, index);
958   - if (selector in expr) {
959   - return resolveValue(locals, ctx, lang, args, expr[selector]);
960   - }
961   - }
962   -
963   - // if there was no index or no selector was found, try the default
964   - // XXX 'other' is an artifact from Gaia
965   - const defaultKey = expr.__default || 'other';
966   - if (defaultKey in expr) {
967   - return resolveValue(locals, ctx, lang, args, expr[defaultKey]);
968   - }
969   -
970   - throw new L10nError('Unresolvable value');
971   - }
972   -
973   - const locales2rules = {
974   - 'af': 3,
975   - 'ak': 4,
976   - 'am': 4,
977   - 'ar': 1,
978   - 'asa': 3,
979   - 'az': 0,
980   - 'be': 11,
981   - 'bem': 3,
982   - 'bez': 3,
983   - 'bg': 3,
984   - 'bh': 4,
985   - 'bm': 0,
986   - 'bn': 3,
987   - 'bo': 0,
988   - 'br': 20,
989   - 'brx': 3,
990   - 'bs': 11,
991   - 'ca': 3,
992   - 'cgg': 3,
993   - 'chr': 3,
994   - 'cs': 12,
995   - 'cy': 17,
996   - 'da': 3,
997   - 'de': 3,
998   - 'dv': 3,
999   - 'dz': 0,
1000   - 'ee': 3,
1001   - 'el': 3,
1002   - 'en': 3,
1003   - 'eo': 3,
1004   - 'es': 3,
1005   - 'et': 3,
1006   - 'eu': 3,
1007   - 'fa': 0,
1008   - 'ff': 5,
1009   - 'fi': 3,
1010   - 'fil': 4,
1011   - 'fo': 3,
1012   - 'fr': 5,
1013   - 'fur': 3,
1014   - 'fy': 3,
1015   - 'ga': 8,
1016   - 'gd': 24,
1017   - 'gl': 3,
1018   - 'gsw': 3,
1019   - 'gu': 3,
1020   - 'guw': 4,
1021   - 'gv': 23,
1022   - 'ha': 3,
1023   - 'haw': 3,
1024   - 'he': 2,
1025   - 'hi': 4,
1026   - 'hr': 11,
1027   - 'hu': 0,
1028   - 'id': 0,
1029   - 'ig': 0,
1030   - 'ii': 0,
1031   - 'is': 3,
1032   - 'it': 3,
1033   - 'iu': 7,
1034   - 'ja': 0,
1035   - 'jmc': 3,
1036   - 'jv': 0,
1037   - 'ka': 0,
1038   - 'kab': 5,
1039   - 'kaj': 3,
1040   - 'kcg': 3,
1041   - 'kde': 0,
1042   - 'kea': 0,
1043   - 'kk': 3,
1044   - 'kl': 3,
1045   - 'km': 0,
1046   - 'kn': 0,
1047   - 'ko': 0,
1048   - 'ksb': 3,
1049   - 'ksh': 21,
1050   - 'ku': 3,
1051   - 'kw': 7,
1052   - 'lag': 18,
1053   - 'lb': 3,
1054   - 'lg': 3,
1055   - 'ln': 4,
1056   - 'lo': 0,
1057   - 'lt': 10,
1058   - 'lv': 6,
1059   - 'mas': 3,
1060   - 'mg': 4,
1061   - 'mk': 16,
1062   - 'ml': 3,
1063   - 'mn': 3,
1064   - 'mo': 9,
1065   - 'mr': 3,
1066   - 'ms': 0,
1067   - 'mt': 15,
1068   - 'my': 0,
1069   - 'nah': 3,
1070   - 'naq': 7,
1071   - 'nb': 3,
1072   - 'nd': 3,
1073   - 'ne': 3,
1074   - 'nl': 3,
1075   - 'nn': 3,
1076   - 'no': 3,
1077   - 'nr': 3,
1078   - 'nso': 4,
1079   - 'ny': 3,
1080   - 'nyn': 3,
1081   - 'om': 3,
1082   - 'or': 3,
1083   - 'pa': 3,
1084   - 'pap': 3,
1085   - 'pl': 13,
1086   - 'ps': 3,
1087   - 'pt': 3,
1088   - 'rm': 3,
1089   - 'ro': 9,
1090   - 'rof': 3,
1091   - 'ru': 11,
1092   - 'rwk': 3,
1093   - 'sah': 0,
1094   - 'saq': 3,
1095   - 'se': 7,
1096   - 'seh': 3,
1097   - 'ses': 0,
1098   - 'sg': 0,
1099   - 'sh': 11,
1100   - 'shi': 19,
1101   - 'sk': 12,
1102   - 'sl': 14,
1103   - 'sma': 7,
1104   - 'smi': 7,
1105   - 'smj': 7,
1106   - 'smn': 7,
1107   - 'sms': 7,
1108   - 'sn': 3,
1109   - 'so': 3,
1110   - 'sq': 3,
1111   - 'sr': 11,
1112   - 'ss': 3,
1113   - 'ssy': 3,
1114   - 'st': 3,
1115   - 'sv': 3,
1116   - 'sw': 3,
1117   - 'syr': 3,
1118   - 'ta': 3,
1119   - 'te': 3,
1120   - 'teo': 3,
1121   - 'th': 0,
1122   - 'ti': 4,
1123   - 'tig': 3,
1124   - 'tk': 3,
1125   - 'tl': 4,
1126   - 'tn': 3,
1127   - 'to': 0,
1128   - 'tr': 0,
1129   - 'ts': 3,
1130   - 'tzm': 22,
1131   - 'uk': 11,
1132   - 'ur': 3,
1133   - 've': 3,
1134   - 'vi': 0,
1135   - 'vun': 3,
1136   - 'wa': 4,
1137   - 'wae': 3,
1138   - 'wo': 0,
1139   - 'xh': 3,
1140   - 'xog': 3,
1141   - 'yo': 0,
1142   - 'zh': 0,
1143   - 'zu': 3
1144   - };
1145   -
1146   - // utility functions for plural rules methods
1147   - function isIn(n, list) {
1148   - return list.indexOf(n) !== -1;
1149   - }
1150   - function isBetween(n, start, end) {
1151   - return typeof n === typeof start && start <= n && n <= end;
1152   - }
1153   -
1154   - // list of all plural rules methods:
1155   - // map an integer to the plural form name to use
1156   - const pluralRules = {
1157   - '0': function() {
1158   - return 'other';
1159   - },
1160   - '1': function(n) {
1161   - if ((isBetween((n % 100), 3, 10))) {
1162   - return 'few';
1163   - }
1164   - if (n === 0) {
1165   - return 'zero';
1166   - }
1167   - if ((isBetween((n % 100), 11, 99))) {
1168   - return 'many';
1169   - }
1170   - if (n === 2) {
1171   - return 'two';
1172   - }
1173   - if (n === 1) {
1174   - return 'one';
1175   - }
1176   - return 'other';
1177   - },
1178   - '2': function(n) {
1179   - if (n !== 0 && (n % 10) === 0) {
1180   - return 'many';
1181   - }
1182   - if (n === 2) {
1183   - return 'two';
1184   - }
1185   - if (n === 1) {
1186   - return 'one';
1187   - }
1188   - return 'other';
1189   - },
1190   - '3': function(n) {
1191   - if (n === 1) {
1192   - return 'one';
1193   - }
1194   - return 'other';
1195   - },
1196   - '4': function(n) {
1197   - if ((isBetween(n, 0, 1))) {
1198   - return 'one';
1199   - }
1200   - return 'other';
1201   - },
1202   - '5': function(n) {
1203   - if ((isBetween(n, 0, 2)) && n !== 2) {
1204   - return 'one';
1205   - }
1206   - return 'other';
1207   - },
1208   - '6': function(n) {
1209   - if (n === 0) {
1210   - return 'zero';
1211   - }
1212   - if ((n % 10) === 1 && (n % 100) !== 11) {
1213   - return 'one';
1214   - }
1215   - return 'other';
1216   - },
1217   - '7': function(n) {
1218   - if (n === 2) {
1219   - return 'two';
1220   - }
1221   - if (n === 1) {
1222   - return 'one';
1223   - }
1224   - return 'other';
1225   - },
1226   - '8': function(n) {
1227   - if ((isBetween(n, 3, 6))) {
1228   - return 'few';
1229   - }
1230   - if ((isBetween(n, 7, 10))) {
1231   - return 'many';
1232   - }
1233   - if (n === 2) {
1234   - return 'two';
1235   - }
1236   - if (n === 1) {
1237   - return 'one';
1238   - }
1239   - return 'other';
1240   - },
1241   - '9': function(n) {
1242   - if (n === 0 || n !== 1 && (isBetween((n % 100), 1, 19))) {
1243   - return 'few';
1244   - }
1245   - if (n === 1) {
1246   - return 'one';
1247   - }
1248   - return 'other';
1249   - },
1250   - '10': function(n) {
1251   - if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) {
1252   - return 'few';
1253   - }
1254   - if ((n % 10) === 1 && !(isBetween((n % 100), 11, 19))) {
1255   - return 'one';
1256   - }
1257   - return 'other';
1258   - },
1259   - '11': function(n) {
1260   - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
1261   - return 'few';
1262   - }
1263   - if ((n % 10) === 0 ||
1264   - (isBetween((n % 10), 5, 9)) ||
1265   - (isBetween((n % 100), 11, 14))) {
1266   - return 'many';
1267   - }
1268   - if ((n % 10) === 1 && (n % 100) !== 11) {
1269   - return 'one';
1270   - }
1271   - return 'other';
1272   - },
1273   - '12': function(n) {
1274   - if ((isBetween(n, 2, 4))) {
1275   - return 'few';
1276   - }
1277   - if (n === 1) {
1278   - return 'one';
1279   - }
1280   - return 'other';
1281   - },
1282   - '13': function(n) {
1283   - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
1284   - return 'few';
1285   - }
1286   - if (n !== 1 && (isBetween((n % 10), 0, 1)) ||
1287   - (isBetween((n % 10), 5, 9)) ||
1288   - (isBetween((n % 100), 12, 14))) {
1289   - return 'many';
1290   - }
1291   - if (n === 1) {
1292   - return 'one';
1293   - }
1294   - return 'other';
1295   - },
1296   - '14': function(n) {
1297   - if ((isBetween((n % 100), 3, 4))) {
1298   - return 'few';
1299   - }
1300   - if ((n % 100) === 2) {
1301   - return 'two';
1302   - }
1303   - if ((n % 100) === 1) {
1304   - return 'one';
1305   - }
1306   - return 'other';
1307   - },
1308   - '15': function(n) {
1309   - if (n === 0 || (isBetween((n % 100), 2, 10))) {
1310   - return 'few';
1311   - }
1312   - if ((isBetween((n % 100), 11, 19))) {
1313   - return 'many';
1314   - }
1315   - if (n === 1) {
1316   - return 'one';
1317   - }
1318   - return 'other';
1319   - },
1320   - '16': function(n) {
1321   - if ((n % 10) === 1 && n !== 11) {
1322   - return 'one';
1323   - }
1324   - return 'other';
1325   - },
1326   - '17': function(n) {
1327   - if (n === 3) {
1328   - return 'few';
1329   - }
1330   - if (n === 0) {
1331   - return 'zero';
1332   - }
1333   - if (n === 6) {
1334   - return 'many';
1335   - }
1336   - if (n === 2) {
1337   - return 'two';
1338   - }
1339   - if (n === 1) {
1340   - return 'one';
1341   - }
1342   - return 'other';
1343   - },
1344   - '18': function(n) {
1345   - if (n === 0) {
1346   - return 'zero';
1347   - }
1348   - if ((isBetween(n, 0, 2)) && n !== 0 && n !== 2) {
1349   - return 'one';
1350   - }
1351   - return 'other';
1352   - },
1353   - '19': function(n) {
1354   - if ((isBetween(n, 2, 10))) {
1355   - return 'few';
1356   - }
1357   - if ((isBetween(n, 0, 1))) {
1358   - return 'one';
1359   - }
1360   - return 'other';
1361   - },
1362   - '20': function(n) {
1363   - if ((isBetween((n % 10), 3, 4) || ((n % 10) === 9)) && !(
1364   - isBetween((n % 100), 10, 19) ||
1365   - isBetween((n % 100), 70, 79) ||
1366   - isBetween((n % 100), 90, 99)
1367   - )) {
1368   - return 'few';
1369   - }
1370   - if ((n % 1000000) === 0 && n !== 0) {
1371   - return 'many';
1372   - }
1373   - if ((n % 10) === 2 && !isIn((n % 100), [12, 72, 92])) {
1374   - return 'two';
1375   - }
1376   - if ((n % 10) === 1 && !isIn((n % 100), [11, 71, 91])) {
1377   - return 'one';
1378   - }
1379   - return 'other';
1380   - },
1381   - '21': function(n) {
1382   - if (n === 0) {
1383   - return 'zero';
1384   - }
1385   - if (n === 1) {
1386   - return 'one';
1387   - }
1388   - return 'other';
1389   - },
1390   - '22': function(n) {
1391   - if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) {
1392   - return 'one';
1393   - }
1394   - return 'other';
1395   - },
1396   - '23': function(n) {
1397   - if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) {
1398   - return 'one';
1399   - }
1400   - return 'other';
1401   - },
1402   - '24': function(n) {
1403   - if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) {
1404   - return 'few';
1405   - }
1406   - if (isIn(n, [2, 12])) {
1407   - return 'two';
1408   - }
1409   - if (isIn(n, [1, 11])) {
1410   - return 'one';
1411   - }
1412   - return 'other';
1413   - }
1414   - };
1415   -
1416   - function getPluralRule(code) {
1417   - // return a function that gives the plural form name for a given integer
1418   - const index = locales2rules[code.replace(/-.*$/, '')];
1419   - if (!(index in pluralRules)) {
1420   - return function() { return 'other'; };
1421   - }
1422   - return pluralRules[index];
1423   - }
1424   -
1425   - class Context {
1426   - constructor(env) {
1427   - this._env = env;
1428   - this._numberFormatters = null;
1429   - }
1430   -
1431   - _formatTuple(lang, args, entity, id, key) {
1432   - try {
1433   - return format(this, lang, args, entity);
1434   - } catch (err) {
1435   - err.id = key ? id + '::' + key : id;
1436   - err.lang = lang;
1437   - this._env.emit('resolveerror', err, this);
1438   - return [{ error: err }, err.id];
1439   - }
1440   - }
1441   -
1442   - _formatEntity(lang, args, entity, id) {
1443   - const [, value] = this._formatTuple(lang, args, entity, id);
1444   -
1445   - const formatted = {
1446   - value,
1447   - attrs: null,
1448   - };
1449   -
1450   - if (entity.attrs) {
1451   - formatted.attrs = Object.create(null);
1452   - for (let key in entity.attrs) {
1453   - /* jshint -W089 */
1454   - const [, attrValue] = this._formatTuple(
1455   - lang, args, entity.attrs[key], id, key);
1456   - formatted.attrs[key] = attrValue;
1457   - }
1458   - }
1459   -
1460   - return formatted;
1461   - }
1462   -
1463   - _formatValue(lang, args, entity, id) {
1464   - return this._formatTuple(lang, args, entity, id)[1];
1465   - }
1466   -
1467   - fetch(langs) {
1468   - if (langs.length === 0) {
1469   - return Promise.resolve(langs);
1470   - }
1471   -
1472   - const resIds = Array.from(this._env._resLists.get(this));
1473   -
1474   - return Promise.all(
1475   - resIds.map(
1476   - this._env._getResource.bind(this._env, langs[0]))).then(
1477   - () => langs);
1478   - }
1479   -
1480   - _resolve(langs, keys, formatter, prevResolved) {
1481   - const lang = langs[0];
1482   -
1483   - if (!lang) {
1484   - return reportMissing.call(this, keys, formatter, prevResolved);
1485   - }
1486   -
1487   - let hasUnresolved = false;
1488   -
1489   - const resolved = keys.map((key, i) => {
1490   - if (prevResolved && prevResolved[i] !== undefined) {
1491   - return prevResolved[i];
1492   - }
1493   - const [id, args] = Array.isArray(key) ?
1494   - key : [key, undefined];
1495   - const entity = this._getEntity(lang, id);
1496   -
1497   - if (entity) {
1498   - return formatter.call(this, lang, args, entity, id);
1499   - }
1500   -
1501   - this._env.emit('notfounderror',
1502   - new L10nError('"' + id + '"' + ' not found in ' + lang.code,
1503   - id, lang), this);
1504   - hasUnresolved = true;
1505   - });
1506   -
1507   - if (!hasUnresolved) {
1508   - return resolved;
1509   - }
1510   -
1511   - return this.fetch(langs.slice(1)).then(
1512   - nextLangs => this._resolve(nextLangs, keys, formatter, resolved));
1513   - }
1514   -
1515   - resolveEntities(langs, keys) {
1516   - return this.fetch(langs).then(
1517   - langs => this._resolve(langs, keys, this._formatEntity));
1518   - }
1519   -
1520   - resolveValues(langs, keys) {
1521   - return this.fetch(langs).then(
1522   - langs => this._resolve(langs, keys, this._formatValue));
1523   - }
1524   -
1525   - _getEntity(lang, id) {
1526   - const cache = this._env._resCache;
1527   - const resIds = Array.from(this._env._resLists.get(this));
1528   -
1529   - // Look for `id` in every resource in order.
1530   - for (let i = 0, resId; resId = resIds[i]; i++) {
1531   - const resource = cache.get(resId + lang.code + lang.src);
1532   - if (resource instanceof L10nError) {
1533   - continue;
1534   - }
1535   - if (id in resource) {
1536   - return resource[id];
1537   - }
1538   - }
1539   - return undefined;
1540   - }
1541   -
1542   - _getNumberFormatter(lang) {
1543   - if (!this._numberFormatters) {
1544   - this._numberFormatters = new Map();
1545   - }
1546   - if (!this._numberFormatters.has(lang)) {
1547   - const formatter = Intl.NumberFormat(lang, {
1548   - useGrouping: false,
1549   - });
1550   - this._numberFormatters.set(lang, formatter);
1551   - return formatter;
1552   - }
1553   - return this._numberFormatters.get(lang);
1554   - }
1555   -
1556   - // XXX in the future macros will be stored in localization resources together
1557   - // with regular entities and this method will not be needed anymore
1558   - _getMacro(lang, id) {
1559   - switch(id) {
1560   - case 'plural':
1561   - return getPluralRule(lang.code);
1562   - default:
1563   - return undefined;
1564   - }
1565   - }
1566   -
1567   - }
1568   -
1569   - function reportMissing(keys, formatter, resolved) {
1570   - const missingIds = new Set();
1571   -
1572   - keys.forEach((key, i) => {
1573   - if (resolved && resolved[i] !== undefined) {
1574   - return;
1575   - }
1576   - const id = Array.isArray(key) ? key[0] : key;
1577   - missingIds.add(id);
1578   - resolved[i] = formatter === this._formatValue ?
1579   - id : {value: id, attrs: null};
1580   - });
1581   -
1582   - this._env.emit('notfounderror', new L10nError(
1583   - '"' + Array.from(missingIds).join(', ') + '"' +
1584   - ' not found in any language', missingIds), this);
1585   -
1586   - return resolved;
1587   - }
1588   -
1589   - // Walk an entry node searching for content leaves
1590   - function walkEntry(entry, fn) {
1591   - if (typeof entry === 'string') {
1592   - return fn(entry);
1593   - }
1594   -
1595   - const newEntry = Object.create(null);
1596   -
1597   - if (entry.value) {
1598   - newEntry.value = walkValue(entry.value, fn);
1599   - }
1600   -
1601   - if (entry.index) {
1602   - newEntry.index = entry.index;
1603   - }
1604   -
1605   - if (entry.attrs) {
1606   - newEntry.attrs = Object.create(null);
1607   - for (let key in entry.attrs) {
1608   - newEntry.attrs[key] = walkEntry(entry.attrs[key], fn);
1609   - }
1610   - }
1611   -
1612   - return newEntry;
1613   - }
1614   -
1615   - function walkValue(value, fn) {
1616   - if (typeof value === 'string') {
1617   - return fn(value);
1618   - }
1619   -
1620   - // skip expressions in placeables
1621   - if (value.type) {
1622   - return value;
1623   - }
1624   -
1625   - const newValue = Array.isArray(value) ? [] : Object.create(null);
1626   - const keys = Object.keys(value);
1627   -
1628   - for (let i = 0, key; (key = keys[i]); i++) {
1629   - newValue[key] = walkValue(value[key], fn);
1630   - }
1631   -
1632   - return newValue;
1633   - }
1634   -
1635   - /* Pseudolocalizations
1636   - *
1637   - * pseudo is a dict of strategies to be used to modify the English
1638   - * context in order to create pseudolocalizations. These can be used by
1639   - * developers to test the localizability of their code without having to
1640   - * actually speak a foreign language.
1641   - *
1642   - * Currently, the following pseudolocales are supported:
1643   - *
1644   - * fr-x-psaccent - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
1645   - *
1646   - * In Accented English all English letters are replaced by accented
1647   - * Unicode counterparts which don't impair the readability of the content.
1648   - * This allows developers to quickly test if any given string is being
1649   - * correctly displayed in its 'translated' form. Additionally, simple
1650   - * heuristics are used to make certain words longer to better simulate the
1651   - * experience of international users.
1652   - *
1653   - * ar-x-psbidi - ɥsıʅƃuƎ ıpıԐ
1654   - *
1655   - * Bidi English is a fake RTL locale. All words are surrounded by
1656   - * Unicode formatting marks forcing the RTL directionality of characters.
1657   - * In addition, to make the reversed text easier to read, individual
1658   - * letters are flipped.
1659   - *
1660   - * Note: The name above is hardcoded to be RTL in case code editors have
1661   - * trouble with the RLO and PDF Unicode marks. In reality, it should be
1662   - * surrounded by those marks as well.
1663   - *
1664   - * See https://bugzil.la/900182 for more information.
1665   - *
1666   - */
1667   -
1668   - function createGetter(id, name) {
1669   - let _pseudo = null;
1670   -
1671   - return function getPseudo() {
1672   - if (_pseudo) {
1673   - return _pseudo;
1674   - }
1675   -
1676   - const reAlphas = /[a-zA-Z]/g;
1677   - const reVowels = /[aeiouAEIOU]/g;
1678   - const reWords = /[^\W0-9_]+/g;
1679   - // strftime tokens (%a, %Eb), template {vars}, HTML entities (&#x202a;)
1680   - // and HTML tags.
1681   - const reExcluded = /(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;
1682   -
1683   - const charMaps = {
1684   - 'fr-x-psaccent':
1685   - 'ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ[\\]^_`ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ',
1686   - 'ar-x-psbidi':
1687   - // XXX Use pɟפ˥ʎ as replacements for ᗡℲ⅁⅂⅄. https://bugzil.la/1007340
1688   - '∀ԐↃpƎɟפHIſӼ˥WNOԀÒᴚS⊥∩ɅMXʎZ[\\]ᵥ_,ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz',
1689   - };
1690   -
1691   - const mods = {
1692   - 'fr-x-psaccent': val =>
1693   - val.replace(reVowels, match => match + match.toLowerCase()),
1694   -
1695   - // Surround each word with Unicode formatting codes, RLO and PDF:
1696   - // U+202E: RIGHT-TO-LEFT OVERRIDE (RLO)
1697   - // U+202C: POP DIRECTIONAL FORMATTING (PDF)
1698   - // See http://www.w3.org/International/questions/qa-bidi-controls
1699   - 'ar-x-psbidi': val =>
1700   - val.replace(reWords, match => '\u202e' + match + '\u202c'),
1701   - };
1702   -
1703   - // Replace each Latin letter with a Unicode character from map
1704   - const replaceChars =
1705   - (map, val) => val.replace(
1706   - reAlphas, match => map.charAt(match.charCodeAt(0) - 65));
1707   -
1708   - const transform =
1709   - val => replaceChars(charMaps[id], mods[id](val));
1710   -
1711   - // apply fn to translatable parts of val
1712   - const apply = (fn, val) => {
1713   - if (!val) {
1714   - return val;
1715   - }
1716   -
1717   - const parts = val.split(reExcluded);
1718   - const modified = parts.map(function(part) {
1719   - if (reExcluded.test(part)) {
1720   - return part;
1721   - }
1722   - return fn(part);
1723   - });
1724   - return modified.join('');
1725   - };
1726   -
1727   - return _pseudo = {
1728   - name: transform(name),
1729   - process: str => apply(transform, str)
1730   - };
1731   - };
1732   - }
1733   -
1734   - const pseudo = Object.defineProperties(Object.create(null), {
1735   - 'fr-x-psaccent': {
1736   - enumerable: true,
1737   - get: createGetter('fr-x-psaccent', 'Runtime Accented')
1738   - },
1739   - 'ar-x-psbidi': {
1740   - enumerable: true,
1741   - get: createGetter('ar-x-psbidi', 'Runtime Bidi')
1742   - }
1743   - });
1744   -
1745   - function emit(listeners, ...args) {
1746   - const type = args.shift();
1747   -
1748   - if (listeners['*']) {
1749   - listeners['*'].slice().forEach(
1750   - listener => listener.apply(this, args));
1751   - }
1752   -
1753   - if (listeners[type]) {
1754   - listeners[type].slice().forEach(
1755   - listener => listener.apply(this, args));
1756   - }
1757   - }
1758   -
1759   - function addEventListener(listeners, type, listener) {
1760   - if (!(type in listeners)) {
1761   - listeners[type] = [];
1762   - }
1763   - listeners[type].push(listener);
1764   - }
1765   -
1766   - function removeEventListener(listeners, type, listener) {
1767   - const typeListeners = listeners[type];
1768   - const pos = typeListeners.indexOf(listener);
1769   - if (pos === -1) {
1770   - return;
1771   - }
1772   -
1773   - typeListeners.splice(pos, 1);
1774   - }
1775   -
1776   - const parsers = {
1777   - properties: PropertiesParser,
1778   - l20n: L20nParser,
1779   - };
1780   -
1781   - class Env {
1782   - constructor(defaultLang, fetchResource) {
1783   - this.defaultLang = defaultLang;
1784   - this.fetchResource = fetchResource;
1785   -
1786   - this._resLists = new Map();
1787   - this._resCache = new Map();
1788   -
1789   - const listeners = {};
1790   - this.emit = emit.bind(this, listeners);
1791   - this.addEventListener = addEventListener.bind(this, listeners);
1792   - this.removeEventListener = removeEventListener.bind(this, listeners);
1793   - }
1794   -
1795   - createContext(resIds) {
1796   - const ctx = new Context(this);
1797   - this._resLists.set(ctx, new Set(resIds));
1798   - return ctx;
1799   - }
1800   -
1801   - destroyContext(ctx) {
1802   - const lists = this._resLists;
1803   - const resList = lists.get(ctx);
1804   -
1805   - lists.delete(ctx);
1806   - resList.forEach(
1807   - resId => deleteIfOrphan(this._resCache, lists, resId));
1808   - }
1809   -
1810   - _parse(syntax, lang, data) {
1811   - const parser = parsers[syntax];
1812   - if (!parser) {
1813   - return data;
1814   - }
1815   -
1816   - const emit = (type, err) => this.emit(type, amendError(lang, err));
1817   - return parser.parse.call(parser, emit, data);
1818   - }
1819   -
1820   - _create(lang, entries) {
1821   - if (lang.src !== 'pseudo') {
1822   - return entries;
1823   - }
1824   -
1825   - const pseudoentries = Object.create(null);
1826   - for (let key in entries) {
1827   - pseudoentries[key] = walkEntry(
1828   - entries[key], pseudo[lang.code].process);
1829   - }
1830   - return pseudoentries;
1831   - }
1832   -
1833   - _getResource(lang, res) {
1834   - const cache = this._resCache;
1835   - const id = res + lang.code + lang.src;
1836   -
1837   - if (cache.has(id)) {
1838   - return cache.get(id);
1839   - }
1840   -
1841   - const syntax = res.substr(res.lastIndexOf('.') + 1);
1842   -
1843   - const saveEntries = data => {
1844   - const entries = this._parse(syntax, lang, data);
1845   - cache.set(id, this._create(lang, entries));
1846   - };
1847   -
1848   - const recover = err => {
1849   - err.lang = lang;
1850   - this.emit('fetcherror', err);
1851   - cache.set(id, err);
1852   - };
1853   -
1854   - const langToFetch = lang.src === 'pseudo' ?
1855   - { code: this.defaultLang, src: 'app' } :
1856   - lang;
1857   -
1858   - const resource = this.fetchResource(res, langToFetch).then(
1859   - saveEntries, recover);
1860   -
1861   - cache.set(id, resource);
1862   -
1863   - return resource;
1864   - }
1865   - }
1866   -
1867   - function deleteIfOrphan(cache, lists, resId) {
1868   - const isNeeded = Array.from(lists).some(
1869   - ([ctx, resIds]) => resIds.has(resId));
1870   -
1871   - if (!isNeeded) {
1872   - cache.forEach((val, key) =>
1873   - key.startsWith(resId) ? cache.delete(key) : null);
1874   - }
1875   - }
1876   -
1877   - function amendError(lang, err) {
1878   - err.lang = lang;
1879   - return err;
1880   - }
1881   -
1882   - // Polyfill NodeList.prototype[Symbol.iterator] for Chrome.
1883   - // See https://code.google.com/p/chromium/issues/detail?id=401699
1884   - if (typeof NodeList === 'function' && !NodeList.prototype[Symbol.iterator]) {
1885   - NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
1886   - }
1887   -
1888   - // A document.ready shim
1889   - // https://github.com/whatwg/html/issues/127
1890   - function documentReady() {
1891   - if (document.readyState !== 'loading') {
1892   - return Promise.resolve();
1893   - }
1894   -
1895   - return new Promise(resolve => {
1896   - document.addEventListener('readystatechange', function onrsc() {
1897   - document.removeEventListener('readystatechange', onrsc);
1898   - resolve();
1899   - });
1900   - });
1901   - }
1902   -
1903   - function prioritizeLocales(def, availableLangs, requested) {
1904   - let supportedLocale;
1905   - // Find the first locale in the requested list that is supported.
1906   - for (let i = 0; i < requested.length; i++) {
1907   - const locale = requested[i];
1908   - if (availableLangs.indexOf(locale) !== -1) {
1909   - supportedLocale = locale;
1910   - break;
1911   - }
1912   - }
1913   - if (!supportedLocale ||
1914   - supportedLocale === def) {
1915   - return [def];
1916   - }
1917   -
1918   - return [supportedLocale, def];
1919   - }
1920   -
1921   - function getMeta(head) {
1922   - let availableLangs = Object.create(null);
1923   - let defaultLang = null;
1924   - let appVersion = null;
1925   -
1926   - // XXX take last found instead of first?
1927   - const metas = head.querySelectorAll(
1928   - 'meta[name="availableLanguages"],' +
1929   - 'meta[name="defaultLanguage"],' +
1930   - 'meta[name="appVersion"]');
1931   - for (let meta of metas) {
1932   - const name = meta.getAttribute('name');
1933   - const content = meta.getAttribute('content').trim();
1934   - switch (name) {
1935   - case 'availableLanguages':
1936   - availableLangs = getLangRevisionMap(
1937   - availableLangs, content);
1938   - break;
1939   - case 'defaultLanguage':
1940   - const [lang, rev] = getLangRevisionTuple(content);
1941   - defaultLang = lang;
1942   - if (!(lang in availableLangs)) {
1943   - availableLangs[lang] = rev;
1944   - }
1945   - break;
1946   - case 'appVersion':
1947   - appVersion = content;
1948   - }
1949   - }
1950   -
1951   - return {
1952   - defaultLang,
1953   - availableLangs,
1954   - appVersion
1955   - };
1956   - }
1957   -
1958   - function getLangRevisionMap(seq, str) {
1959   - return str.split(',').reduce((seq, cur) => {
1960   - const [lang, rev] = getLangRevisionTuple(cur);
1961   - seq[lang] = rev;
1962   - return seq;
1963   - }, seq);
1964   - }
1965   -
1966   - function getLangRevisionTuple(str) {
1967   - const [lang, rev] = str.trim().split(':');
1968   - // if revision is missing, use NaN
1969   - return [lang, parseInt(rev)];
1970   - }
1971   -
1972   - function negotiateLanguages(
1973   - fn, appVersion, defaultLang, availableLangs, additionalLangs, prevLangs,
1974   - requestedLangs) {
1975   -
1976   - const allAvailableLangs = Object.keys(availableLangs).concat(
1977   - additionalLangs || []).concat(Object.keys(pseudo));
1978   - const newLangs = prioritizeLocales(
1979   - defaultLang, allAvailableLangs, requestedLangs);
1980   -
1981   - const langs = newLangs.map(code => ({
1982   - code: code,
1983   - src: getLangSource(appVersion, availableLangs, additionalLangs, code),
1984   - }));
1985   -
1986   - if (!arrEqual(prevLangs, newLangs)) {
1987   - fn(langs);
1988   - }
1989   -
1990   - return langs;
1991   - }
1992   -
1993   - function arrEqual(arr1, arr2) {
1994   - return arr1.length === arr2.length &&
1995   - arr1.every((elem, i) => elem === arr2[i]);
1996   - }
1997   -
1998   - function getMatchingLangpack(appVersion, langpacks) {
1999   - for (let i = 0, langpack; (langpack = langpacks[i]); i++) {
2000   - if (langpack.target === appVersion) {
2001   - return langpack;
2002   - }
2003   - }
2004   - return null;
2005   - }
2006   -
2007   - function getLangSource(appVersion, availableLangs, additionalLangs, code) {
2008   - if (additionalLangs && additionalLangs[code]) {
2009   - const lp = getMatchingLangpack(appVersion, additionalLangs[code]);
2010   - if (lp &&
2011   - (!(code in availableLangs) ||
2012   - parseInt(lp.revision) > availableLangs[code])) {
2013   - return 'extra';
2014   - }
2015   - }
2016   -
2017   - if ((code in pseudo) && !(code in availableLangs)) {
2018   - return 'pseudo';
2019   - }
2020   -
2021   - return 'app';
2022   - }
2023   -
2024   - class Remote {
2025   - constructor(fetchResource, broadcast, requestedLangs) {
2026   - this.fetchResource = fetchResource;
2027   - this.broadcast = broadcast;
2028   - this.ctxs = new Map();
2029   - this.interactive = documentReady().then(
2030   - () => this.init(requestedLangs));
2031   - }
2032   -
2033   - init(requestedLangs) {
2034   - const meta = getMeta(document.head);
2035   - this.defaultLanguage = meta.defaultLang;
2036   - this.availableLanguages = meta.availableLangs;
2037   - this.appVersion = meta.appVersion;
2038   -
2039   - this.env = new Env(
2040   - this.defaultLanguage,
2041   - (...args) => this.fetchResource(this.appVersion, ...args));
2042   -
2043   - return this.requestLanguages(requestedLangs);
2044   - }
2045   -
2046   - registerView(view, resources) {
2047   - return this.interactive.then(() => {
2048   - this.ctxs.set(view, this.env.createContext(resources));
2049   - return true;
2050   - });
2051   - }
2052   -
2053   - unregisterView(view) {
2054   - return this.ctxs.delete(view);
2055   - }
2056   -
2057   - resolveEntities(view, langs, keys) {
2058   - return this.ctxs.get(view).resolveEntities(langs, keys);
2059   - }
2060   -
2061   - formatValues(view, keys) {
2062   - return this.languages.then(
2063   - langs => this.ctxs.get(view).resolveValues(langs, keys));
2064   - }
2065   -
2066   - resolvedLanguages() {
2067   - return this.languages;
2068   - }
2069   -
2070   - requestLanguages(requestedLangs) {
2071   - return changeLanguages.call(
2072   - this, getAdditionalLanguages(), requestedLangs);
2073   - }
2074   -
2075   - getName(code) {
2076   - return pseudo[code].name;
2077   - }
2078   -
2079   - processString(code, str) {
2080   - return pseudo[code].process(str);
2081   - }
2082   -
2083   - handleEvent(evt) {
2084   - return changeLanguages.call(
2085   - this, evt.detail || getAdditionalLanguages(), navigator.languages);
2086   - }
2087   - }
2088   -
2089   - function getAdditionalLanguages() {
2090   - if (navigator.mozApps && navigator.mozApps.getAdditionalLanguages) {
2091   - return navigator.mozApps.getAdditionalLanguages().catch(
2092   - () => []);
2093   - }
2094   -
2095   - return Promise.resolve([]);
2096   - }
2097   -
2098   - function changeLanguages(additionalLangs, requestedLangs) {
2099   - const prevLangs = this.languages || [];
2100   - return this.languages = Promise.all([
2101   - additionalLangs, prevLangs]).then(
2102   - ([additionalLangs, prevLangs]) => negotiateLanguages(
2103   - this.broadcast.bind(this, 'translateDocument'),
2104   - this.appVersion, this.defaultLanguage, this.availableLanguages,
2105   - additionalLangs, prevLangs, requestedLangs));
2106   - }
2107   -
2108   - const remote = new Remote(fetchResource, broadcast, navigator.languages);
2109   - window.addEventListener('languagechange', remote);
2110   - document.addEventListener('additionallanguageschange', remote);
2111   -
2112   - remote.service = new Service('l20n')
2113   - .method('registerView', (...args) => remote.registerView(...args))
2114   - .method('resolvedLanguages', (...args) => remote.resolvedLanguages(...args))
2115   - .method('requestLanguages', (...args) => remote.requestLanguages(...args))
2116   - .method('resolveEntities', (...args) => remote.resolveEntities(...args))
2117   - .method('formatValues', (...args) => remote.formatValues(...args))
2118   - .method('getName', (...args) => remote.getName(...args))
2119   - .method('processString', (...args) => remote.processString(...args))
2120   - .on('disconnect', clientId => remote.unregisterView(clientId))
2121   - .listen(channel);
2122   -
2123   -})();
2124 0 \ No newline at end of file
bower_components/l20n/dist/bundle/gaia/build/l20n.js deleted
1   -'use strict';
2   -
3   -function L10nError(message, id, lang) {
4   - this.name = 'L10nError';
5   - this.message = message;
6   - this.id = id;
7   - this.lang = lang;
8   -}
9   -L10nError.prototype = Object.create(Error.prototype);
10   -L10nError.prototype.constructor = L10nError;
11   -
12   -function fetchResource(htmloptimizer, res, lang) {
13   - // We need to decode URI because build system DOM reader
14   - // may replace `{locale}` with `%7Blocale%7D`. See bug 1098188
15   - const url = decodeURI(res).replace('{locale}', lang.code);
16   - const {file, content} = htmloptimizer.getFileByRelativePath(url);
17   - if (!file) {
18   - return Promise.reject(new L10nError('Not found: ' + url));
19   - }
20   -
21   - const parsed = res.endsWith('.json') ?
22   - JSON.parse(content) : content;
23   - return Promise.resolve(parsed);
24   -}
25   -
26   -// Walk an entry node searching for content leaves
27   -function walkEntry(entry, fn) {
28   - if (typeof entry === 'string') {
29   - return fn(entry);
30   - }
31   -
32   - const newEntry = Object.create(null);
33   -
34   - if (entry.value) {
35   - newEntry.value = walkValue$1(entry.value, fn);
36   - }
37   -
38   - if (entry.index) {
39   - newEntry.index = entry.index;
40   - }
41   -
42   - if (entry.attrs) {
43   - newEntry.attrs = Object.create(null);
44   - for (let key in entry.attrs) {
45   - newEntry.attrs[key] = walkEntry(entry.attrs[key], fn);
46   - }
47   - }
48   -
49   - return newEntry;
50   -}
51   -
52   -function walkValue$1(value, fn) {
53   - if (typeof value === 'string') {
54   - return fn(value);
55   - }
56   -
57   - // skip expressions in placeables
58   - if (value.type) {
59   - return value;
60   - }
61   -
62   - const newValue = Array.isArray(value) ? [] : Object.create(null);
63   - const keys = Object.keys(value);
64   -
65   - for (let i = 0, key; (key = keys[i]); i++) {
66   - newValue[key] = walkValue$1(value[key], fn);
67   - }
68   -
69   - return newValue;
70   -}
71   -
72   -/* Pseudolocalizations
73   - *
74   - * pseudo is a dict of strategies to be used to modify the English
75   - * context in order to create pseudolocalizations. These can be used by
76   - * developers to test the localizability of their code without having to
77   - * actually speak a foreign language.
78   - *
79   - * Currently, the following pseudolocales are supported:
80   - *
81   - * fr-x-psaccent - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
82   - *
83   - * In Accented English all English letters are replaced by accented
84   - * Unicode counterparts which don't impair the readability of the content.
85   - * This allows developers to quickly test if any given string is being
86   - * correctly displayed in its 'translated' form. Additionally, simple
87   - * heuristics are used to make certain words longer to better simulate the
88   - * experience of international users.
89   - *
90   - * ar-x-psbidi - ɥsıʅƃuƎ ıpıԐ
91   - *
92   - * Bidi English is a fake RTL locale. All words are surrounded by
93   - * Unicode formatting marks forcing the RTL directionality of characters.
94   - * In addition, to make the reversed text easier to read, individual
95   - * letters are flipped.
96   - *
97   - * Note: The name above is hardcoded to be RTL in case code editors have
98   - * trouble with the RLO and PDF Unicode marks. In reality, it should be
99   - * surrounded by those marks as well.
100   - *
101   - * See https://bugzil.la/900182 for more information.
102   - *
103   - */
104   -
105   -function createGetter(id, name) {
106   - let _pseudo = null;
107   -
108   - return function getPseudo() {
109   - if (_pseudo) {
110   - return _pseudo;
111   - }
112   -
113   - const reAlphas = /[a-zA-Z]/g;
114   - const reVowels = /[aeiouAEIOU]/g;
115   - const reWords = /[^\W0-9_]+/g;
116   - // strftime tokens (%a, %Eb), template {vars}, HTML entities (&#x202a;)
117   - // and HTML tags.
118   - const reExcluded = /(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;
119   -
120   - const charMaps = {
121   - 'fr-x-psaccent':
122   - 'ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ[\\]^_`ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ',
123   - 'ar-x-psbidi':
124   - // XXX Use pɟפ˥ʎ as replacements for ᗡℲ⅁⅂⅄. https://bugzil.la/1007340
125   - '∀ԐↃpƎɟפHIſӼ˥WNOԀÒᴚS⊥∩ɅMXʎZ[\\]ᵥ_,ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz',
126   - };
127   -
128   - const mods = {
129   - 'fr-x-psaccent': val =>
130   - val.replace(reVowels, match => match + match.toLowerCase()),
131   -
132   - // Surround each word with Unicode formatting codes, RLO and PDF:
133   - // U+202E: RIGHT-TO-LEFT OVERRIDE (RLO)
134   - // U+202C: POP DIRECTIONAL FORMATTING (PDF)
135   - // See http://www.w3.org/International/questions/qa-bidi-controls
136   - 'ar-x-psbidi': val =>
137   - val.replace(reWords, match => '\u202e' + match + '\u202c'),
138   - };
139   -
140   - // Replace each Latin letter with a Unicode character from map
141   - const replaceChars =
142   - (map, val) => val.replace(
143   - reAlphas, match => map.charAt(match.charCodeAt(0) - 65));
144   -
145   - const transform =
146   - val => replaceChars(charMaps[id], mods[id](val));
147   -
148   - // apply fn to translatable parts of val
149   - const apply = (fn, val) => {
150   - if (!val) {
151   - return val;
152   - }
153   -
154   - const parts = val.split(reExcluded);
155   - const modified = parts.map(function(part) {
156   - if (reExcluded.test(part)) {
157   - return part;
158   - }
159   - return fn(part);
160   - });
161   - return modified.join('');
162   - };
163   -
164   - return _pseudo = {
165   - name: transform(name),
166   - process: str => apply(transform, str)
167   - };
168   - };
169   -}
170   -
171   -const pseudo$1 = Object.defineProperties(Object.create(null), {
172   - 'fr-x-psaccent': {
173   - enumerable: true,
174   - get: createGetter('fr-x-psaccent', 'Runtime Accented')
175   - },
176   - 'ar-x-psbidi': {
177   - enumerable: true,
178   - get: createGetter('ar-x-psbidi', 'Runtime Bidi')
179   - }
180   -});
181   -
182   -const MAX_PLACEABLES$1 = 100;
183   -
184   -var L20nParser = {
185   - parse: function(emit, string) {
186   - this._source = string;
187   - this._index = 0;
188   - this._length = string.length;
189   - this.entries = Object.create(null);
190   - this.emit = emit;
191   -
192   - return this.getResource();
193   - },
194   -
195   - getResource: function() {
196   - this.getWS();
197   - while (this._index < this._length) {
198   - try {
199   - this.getEntry();
200   - } catch (e) {
201   - if (e instanceof L10nError) {
202   - // we want to recover, but we don't need it in entries
203   - this.getJunkEntry();
204   - if (!this.emit) {
205   - throw e;
206   - }
207   - } else {
208   - throw e;
209   - }
210   - }
211   -
212   - if (this._index < this._length) {
213   - this.getWS();
214   - }
215   - }
216   -
217   - return this.entries;
218   - },
219   -
220   - getEntry: function() {
221   - if (this._source[this._index] === '<') {
222   - ++this._index;
223   - const id = this.getIdentifier();
224   - if (this._source[this._index] === '[') {
225   - ++this._index;
226   - return this.getEntity(id, this.getItemList(this.getExpression, ']'));
227   - }
228   - return this.getEntity(id);
229   - }
230   -
231   - if (this._source.startsWith('/*', this._index)) {
232   - return this.getComment();
233   - }
234   -
235   - throw this.error('Invalid entry');
236   - },
237   -
238   - getEntity: function(id, index) {
239   - if (!this.getRequiredWS()) {
240   - throw this.error('Expected white space');
241   - }
242   -
243   - const ch = this._source[this._index];
244   - const value = this.getValue(ch, index === undefined);
245   - let attrs;
246   -
247   - if (value === undefined) {
248   - if (ch === '>') {
249   - throw this.error('Expected ">"');
250   - }
251   - attrs = this.getAttributes();
252   - } else {
253   - const ws1 = this.getRequiredWS();
254   - if (this._source[this._index] !== '>') {
255   - if (!ws1) {
256   - throw this.error('Expected ">"');
257   - }
258   - attrs = this.getAttributes();
259   - }
260   - }
261   -
262   - // skip '>'
263   - ++this._index;
264   -
265   - if (id in this.entries) {
266   - throw this.error('Duplicate entry ID "' + id, 'duplicateerror');
267   - }
268   - if (!attrs && !index && typeof value === 'string') {
269   - this.entries[id] = value;
270   - } else {
271   - this.entries[id] = {
272   - value,
273   - attrs,
274   - index
275   - };
276   - }
277   - },
278   -
279   - getValue: function(ch = this._source[this._index], optional = false) {
280   - switch (ch) {
281   - case '\'':
282   - case '"':
283   - return this.getString(ch, 1);
284   - case '{':
285   - return this.getHash();
286   - }
287   -
288   - if (!optional) {
289   - throw this.error('Unknown value type');
290   - }
291   -
292   - return;
293   - },
294   -
295   - getWS: function() {
296   - let cc = this._source.charCodeAt(this._index);
297   - // space, \n, \t, \r
298   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
299   - cc = this._source.charCodeAt(++this._index);
300   - }
301   - },
302   -
303   - getRequiredWS: function() {
304   - const pos = this._index;
305   - let cc = this._source.charCodeAt(pos);
306   - // space, \n, \t, \r
307   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
308   - cc = this._source.charCodeAt(++this._index);
309   - }
310   - return this._index !== pos;
311   - },
312   -
313   - getIdentifier: function() {
314   - const start = this._index;
315   - let cc = this._source.charCodeAt(this._index);
316   -
317   - if ((cc >= 97 && cc <= 122) || // a-z
318   - (cc >= 65 && cc <= 90) || // A-Z
319   - cc === 95) { // _
320   - cc = this._source.charCodeAt(++this._index);
321   - } else {
322   - throw this.error('Identifier has to start with [a-zA-Z_]');
323   - }
324   -
325   - while ((cc >= 97 && cc <= 122) || // a-z
326   - (cc >= 65 && cc <= 90) || // A-Z
327   - (cc >= 48 && cc <= 57) || // 0-9
328   - cc === 95) { // _
329   - cc = this._source.charCodeAt(++this._index);
330   - }
331   -
332   - return this._source.slice(start, this._index);
333   - },
334   -
335   - getUnicodeChar: function() {
336   - for (let i = 0; i < 4; i++) {
337   - let cc = this._source.charCodeAt(++this._index);
338   - if ((cc > 96 && cc < 103) || // a-f
339   - (cc > 64 && cc < 71) || // A-F
340   - (cc > 47 && cc < 58)) { // 0-9
341   - continue;
342   - }
343   - throw this.error('Illegal unicode escape sequence');
344   - }
345   - this._index++;
346   - return String.fromCharCode(
347   - parseInt(this._source.slice(this._index - 4, this._index), 16));
348   - },
349   -
350   - stringRe: /"|'|{{|\\/g,
351   - getString: function(opchar, opcharLen) {
352   - const body = [];
353   - let placeables = 0;
354   -
355   - this._index += opcharLen;
356   - const start = this._index;
357   -
358   - let bufStart = start;
359   - let buf = '';
360   -
361   - while (true) {
362   - this.stringRe.lastIndex = this._index;
363   - const match = this.stringRe.exec(this._source);
364   -
365   - if (!match) {
366   - throw this.error('Unclosed string literal');
367   - }
368   -
369   - if (match[0] === '"' || match[0] === '\'') {
370   - if (match[0] !== opchar) {
371   - this._index += opcharLen;
372   - continue;
373   - }
374   - this._index = match.index + opcharLen;
375   - break;
376   - }
377   -
378   - if (match[0] === '{{') {
379   - if (placeables > MAX_PLACEABLES$1 - 1) {
380   - throw this.error('Too many placeables, maximum allowed is ' +
381   - MAX_PLACEABLES$1);
382   - }
383   - placeables++;
384   - if (match.index > bufStart || buf.length > 0) {
385   - body.push(buf + this._source.slice(bufStart, match.index));
386   - buf = '';
387   - }
388   - this._index = match.index + 2;
389   - this.getWS();
390   - body.push(this.getExpression());
391   - this.getWS();
392   - this._index += 2;
393   - bufStart = this._index;
394   - continue;
395   - }
396   -
397   - if (match[0] === '\\') {
398   - this._index = match.index + 1;
399   - const ch2 = this._source[this._index];
400   - if (ch2 === 'u') {
401   - buf += this._source.slice(bufStart, match.index) +
402   - this.getUnicodeChar();
403   - } else if (ch2 === opchar || ch2 === '\\') {
404   - buf += this._source.slice(bufStart, match.index) + ch2;
405   - this._index++;
406   - } else if (this._source.startsWith('{{', this._index)) {
407   - buf += this._source.slice(bufStart, match.index) + '{{';
408   - this._index += 2;
409   - } else {
410   - throw this.error('Illegal escape sequence');
411   - }
412   - bufStart = this._index;
413   - }
414   - }
415   -
416   - if (body.length === 0) {
417   - return buf + this._source.slice(bufStart, this._index - opcharLen);
418   - }
419   -
420   - if (this._index - opcharLen > bufStart || buf.length > 0) {
421   - body.push(buf + this._source.slice(bufStart, this._index - opcharLen));
422   - }
423   -
424   - return body;
425   - },
426   -
427   - getAttributes: function() {
428   - const attrs = Object.create(null);
429   -
430   - while (true) {
431   - this.getAttribute(attrs);
432   - const ws1 = this.getRequiredWS();
433   - const ch = this._source.charAt(this._index);
434   - if (ch === '>') {
435   - break;
436   - } else if (!ws1) {
437   - throw this.error('Expected ">"');
438   - }
439   - }
440   - return attrs;
441   - },
442   -
443   - getAttribute: function(attrs) {
444   - const key = this.getIdentifier();
445   - let index;
446   -
447   - if (this._source[this._index]=== '[') {
448   - ++this._index;
449   - this.getWS();
450   - index = this.getItemList(this.getExpression, ']');
451   - }
452   - this.getWS();
453   - if (this._source[this._index] !== ':') {
454   - throw this.error('Expected ":"');
455   - }
456   - ++this._index;
457   - this.getWS();
458   - const value = this.getValue();
459   -
460   - if (key in attrs) {
461   - throw this.error('Duplicate attribute "' + key, 'duplicateerror');
462   - }
463   -
464   - if (!index && typeof value === 'string') {
465   - attrs[key] = value;
466   - } else {
467   - attrs[key] = {
468   - value,
469   - index
470   - };
471   - }
472   - },
473   -
474   - getHash: function() {
475   - const items = Object.create(null);
476   -
477   - ++this._index;
478   - this.getWS();
479   -
480   - let defKey;
481   -
482   - while (true) {
483   - const [key, value, def] = this.getHashItem();
484   - items[key] = value;
485   -
486   - if (def) {
487   - if (defKey) {
488   - throw this.error('Default item redefinition forbidden');
489   - }
490   - defKey = key;
491   - }
492   - this.getWS();
493   -
494   - const comma = this._source[this._index] === ',';
495   - if (comma) {
496   - ++this._index;
497   - this.getWS();
498   - }
499   - if (this._source[this._index] === '}') {
500   - ++this._index;
501   - break;
502   - }
503   - if (!comma) {
504   - throw this.error('Expected "}"');
505   - }
506   - }
507   -
508   - if (defKey) {
509   - items.__default = defKey;
510   - }
511   -
512   - return items;
513   - },
514   -
515   - getHashItem: function() {
516   - let defItem = false;
517   - if (this._source[this._index] === '*') {
518   - ++this._index;
519   - defItem = true;
520   - }
521   -
522   - const key = this.getIdentifier();
523   - this.getWS();
524   - if (this._source[this._index] !== ':') {
525   - throw this.error('Expected ":"');
526   - }
527   - ++this._index;
528   - this.getWS();
529   -
530   - return [key, this.getValue(), defItem];
531   - },
532   -
533   - getComment: function() {
534   - this._index += 2;
535   - const start = this._index;
536   - const end = this._source.indexOf('*/', start);
537   -
538   - if (end === -1) {
539   - throw this.error('Comment without a closing tag');
540   - }
541   -
542   - this._index = end + 2;
543   - },
544   -
545   - getExpression: function () {
546   - let exp = this.getPrimaryExpression();
547   -
548   - while (true) {
549   - let ch = this._source[this._index];
550   - if (ch === '.' || ch === '[') {
551   - ++this._index;
552   - exp = this.getPropertyExpression(exp, ch === '[');
553   - } else if (ch === '(') {
554   - ++this._index;
555   - exp = this.getCallExpression(exp);
556   - } else {
557   - break;
558   - }
559   - }
560   -
561   - return exp;
562   - },
563   -
564   - getPropertyExpression: function(idref, computed) {
565   - let exp;
566   -
567   - if (computed) {
568   - this.getWS();
569   - exp = this.getExpression();
570   - this.getWS();
571   - if (this._source[this._index] !== ']') {
572   - throw this.error('Expected "]"');
573   - }
574   - ++this._index;
575   - } else {
576   - exp = this.getIdentifier();
577   - }
578   -
579   - return {
580   - type: 'prop',
581   - expr: idref,
582   - prop: exp,
583   - cmpt: computed
584   - };
585   - },
586   -
587   - getCallExpression: function(callee) {
588   - this.getWS();
589   -
590   - return {
591   - type: 'call',
592   - expr: callee,
593   - args: this.getItemList(this.getExpression, ')')
594   - };
595   - },
596   -
597   - getPrimaryExpression: function() {
598   - const ch = this._source[this._index];
599   -
600   - switch (ch) {
601   - case '$':
602   - ++this._index;
603   - return {
604   - type: 'var',
605   - name: this.getIdentifier()
606   - };
607   - case '@':
608   - ++this._index;
609   - return {
610   - type: 'glob',
611   - name: this.getIdentifier()
612   - };
613   - default:
614   - return {
615   - type: 'id',
616   - name: this.getIdentifier()
617   - };
618   - }
619   - },
620   -
621   - getItemList: function(callback, closeChar) {
622   - const items = [];
623   - let closed = false;
624   -
625   - this.getWS();
626   -
627   - if (this._source[this._index] === closeChar) {
628   - ++this._index;
629   - closed = true;
630   - }
631   -
632   - while (!closed) {
633   - items.push(callback.call(this));
634   - this.getWS();
635   - let ch = this._source.charAt(this._index);
636   - switch (ch) {
637   - case ',':
638   - ++this._index;
639   - this.getWS();
640   - break;
641   - case closeChar:
642   - ++this._index;
643   - closed = true;
644   - break;
645   - default:
646   - throw this.error('Expected "," or "' + closeChar + '"');
647   - }
648   - }
649   -
650   - return items;
651   - },
652   -
653   -
654   - getJunkEntry: function() {
655   - const pos = this._index;
656   - let nextEntity = this._source.indexOf('<', pos);
657   - let nextComment = this._source.indexOf('/*', pos);
658   -
659   - if (nextEntity === -1) {
660   - nextEntity = this._length;
661   - }
662   - if (nextComment === -1) {
663   - nextComment = this._length;
664   - }
665   -
666   - let nextEntry = Math.min(nextEntity, nextComment);
667   -
668   - this._index = nextEntry;
669   - },
670   -
671   - error: function(message, type = 'parsererror') {
672   - const pos = this._index;
673   -
674   - let start = this._source.lastIndexOf('<', pos - 1);
675   - const lastClose = this._source.lastIndexOf('>', pos - 1);
676   - start = lastClose > start ? lastClose + 1 : start;
677   - const context = this._source.slice(start, pos + 10);
678   -
679   - const msg = message + ' at pos ' + pos + ': `' + context + '`';
680   - const err = new L10nError(msg);
681   - if (this.emit) {
682   - this.emit(type, err);
683   - }
684   - return err;
685   - },
686   -};
687   -
688   -var MAX_PLACEABLES = 100;
689   -
690   -var PropertiesParser = {
691   - patterns: null,
692   - entryIds: null,
693   - emit: null,
694   -
695   - init: function() {
696   - this.patterns = {
697   - comment: /^\s*#|^\s*$/,
698   - entity: /^([^=\s]+)\s*=\s*(.*)$/,
699   - multiline: /[^\\]\\$/,
700   - index: /\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,
701   - unicode: /\\u([0-9a-fA-F]{1,4})/g,
702   - entries: /[^\r\n]+/g,
703   - controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g,
704   - placeables: /\{\{\s*([^\s]*?)\s*\}\}/,
705   - };
706   - },
707   -
708   - parse: function(emit, source) {
709   - if (!this.patterns) {
710   - this.init();
711   - }
712   - this.emit = emit;
713   -
714   - var entries = {};
715   -
716   - var lines = source.match(this.patterns.entries);
717   - if (!lines) {
718   - return entries;
719   - }
720   - for (var i = 0; i < lines.length; i++) {
721   - var line = lines[i];
722   -
723   - if (this.patterns.comment.test(line)) {
724   - continue;
725   - }
726   -
727   - while (this.patterns.multiline.test(line) && i < lines.length) {
728   - line = line.slice(0, -1) + lines[++i].trim();
729   - }
730   -
731   - var entityMatch = line.match(this.patterns.entity);
732   - if (entityMatch) {
733   - try {
734   - this.parseEntity(entityMatch[1], entityMatch[2], entries);
735   - } catch (e) {
736   - if (!this.emit) {
737   - throw e;
738   - }
739   - }
740   - }
741   - }
742   - return entries;
743   - },
744   -
745   - parseEntity: function(id, value, entries) {
746   - var name, key;
747   -
748   - var pos = id.indexOf('[');
749   - if (pos !== -1) {
750   - name = id.substr(0, pos);
751   - key = id.substring(pos + 1, id.length - 1);
752   - } else {
753   - name = id;
754   - key = null;
755   - }
756   -
757   - var nameElements = name.split('.');
758   -
759   - if (nameElements.length > 2) {
760   - throw this.error('Error in ID: "' + name + '".' +
761   - ' Nested attributes are not supported.');
762   - }
763   -
764   - var attr;
765   - if (nameElements.length > 1) {
766   - name = nameElements[0];
767   - attr = nameElements[1];
768   -
769   - if (attr[0] === '$') {
770   - throw this.error('Attribute can\'t start with "$"');
771   - }
772   - } else {
773   - attr = null;
774   - }
775   -
776   - this.setEntityValue(name, attr, key, this.unescapeString(value), entries);
777   - },
778   -
779   - setEntityValue: function(id, attr, key, rawValue, entries) {
780   - var value = rawValue.indexOf('{{') > -1 ?
781   - this.parseString(rawValue) : rawValue;
782   -
783   - var isSimpleValue = typeof value === 'string';
784   - var root = entries;
785   -
786   - var isSimpleNode = typeof entries[id] === 'string';
787   -
788   - if (!entries[id] && (attr || key || !isSimpleValue)) {
789   - entries[id] = Object.create(null);
790   - isSimpleNode = false;
791   - }
792   -
793   - if (attr) {
794   - if (isSimpleNode) {
795   - const val = entries[id];
796   - entries[id] = Object.create(null);
797   - entries[id].value = val;
798   - }
799   - if (!entries[id].attrs) {
800   - entries[id].attrs = Object.create(null);
801   - }
802   - if (!entries[id].attrs && !isSimpleValue) {
803   - entries[id].attrs[attr] = Object.create(null);
804   - }
805   - root = entries[id].attrs;
806   - id = attr;
807   - }
808   -
809   - if (key) {
810   - isSimpleNode = false;
811   - if (typeof root[id] === 'string') {
812   - const val = root[id];
813   - root[id] = Object.create(null);
814   - root[id].index = this.parseIndex(val);
815   - root[id].value = Object.create(null);
816   - }
817   - root = root[id].value;
818   - id = key;
819   - isSimpleValue = true;
820   - }
821   -
822   - if (isSimpleValue && (!entries[id] || isSimpleNode)) {
823   - if (id in root) {
824   - throw this.error();
825   - }
826   - root[id] = value;
827   - } else {
828   - if (!root[id]) {
829   - root[id] = Object.create(null);
830   - }
831   - root[id].value = value;
832   - }
833   - },
834   -
835   - parseString: function(str) {
836   - var chunks = str.split(this.patterns.placeables);
837   - var complexStr = [];
838   -
839   - var len = chunks.length;
840   - var placeablesCount = (len - 1) / 2;
841   -
842   - if (placeablesCount >= MAX_PLACEABLES) {
843   - throw this.error('Too many placeables (' + placeablesCount +
844   - ', max allowed is ' + MAX_PLACEABLES + ')');
845   - }
846   -
847   - for (var i = 0; i < chunks.length; i++) {
848   - if (chunks[i].length === 0) {
849   - continue;
850   - }
851   - if (i % 2 === 1) {
852   - complexStr.push({type: 'idOrVar', name: chunks[i]});
853   - } else {
854   - complexStr.push(chunks[i]);
855   - }
856   - }
857   - return complexStr;
858   - },
859   -
860   - unescapeString: function(str) {
861   - if (str.lastIndexOf('\\') !== -1) {
862   - str = str.replace(this.patterns.controlChars, '$1');
863   - }
864   - return str.replace(this.patterns.unicode, function(match, token) {
865   - return String.fromCodePoint(parseInt(token, 16));
866   - });
867   - },
868   -
869   - parseIndex: function(str) {
870   - var match = str.match(this.patterns.index);
871   - if (!match) {
872   - throw new L10nError('Malformed index');
873   - }
874   - if (match[2]) {
875   - return [{
876   - type: 'call',
877   - expr: {
878   - type: 'prop',
879   - expr: {
880   - type: 'glob',
881   - name: 'cldr'
882   - },
883   - prop: 'plural',
884   - cmpt: false
885   - }, args: [{
886   - type: 'idOrVar',
887   - name: match[2]
888   - }]
889   - }];
890   - } else {
891   - return [{type: 'idOrVar', name: match[1]}];
892   - }
893   - },
894   -
895   - error: function(msg, type = 'parsererror') {
896   - const err = new L10nError(msg);
897   - if (this.emit) {
898   - this.emit(type, err);
899   - }
900   - return err;
901   - }
902   -};
903   -
904   -const KNOWN_MACROS$1 = ['plural'];
905   -const MAX_PLACEABLE_LENGTH$1 = 2500;
906   -
907   -// Unicode bidi isolation characters
908   -const FSI$1 = '\u2068';
909   -const PDI$1 = '\u2069';
910   -
911   -const resolutionChain$1 = new WeakSet();
912   -
913   -function format$1(ctx, lang, args, entity) {
914   - if (typeof entity === 'string') {
915   - return [{}, entity];
916   - }
917   -
918   - if (resolutionChain$1.has(entity)) {
919   - throw new L10nError('Cyclic reference detected');
920   - }
921   -
922   - resolutionChain$1.add(entity);
923   -
924   - let rv;
925   - // if format fails, we want the exception to bubble up and stop the whole
926   - // resolving process; however, we still need to remove the entity from the
927   - // resolution chain
928   - try {
929   - rv = resolveValue$1(
930   - {}, ctx, lang, args, entity.value, entity.index);
931   - } finally {
932   - resolutionChain$1.delete(entity);
933   - }
934   - return rv;
935   -}
936   -
937   -function resolveIdentifier$1(ctx, lang, args, id) {
938   - if (KNOWN_MACROS$1.indexOf(id) > -1) {
939   - return [{}, ctx._getMacro(lang, id)];
940   - }
941   -
942   - if (args && args.hasOwnProperty(id)) {
943   - if (typeof args[id] === 'string' || (typeof args[id] === 'number' &&
944   - !isNaN(args[id]))) {
945   - return [{}, args[id]];
946   - } else {
947   - throw new L10nError('Arg must be a string or a number: ' + id);
948   - }
949   - }
950   -
951   - // XXX: special case for Node.js where still:
952   - // '__proto__' in Object.create(null) => true
953   - if (id === '__proto__') {
954   - throw new L10nError('Illegal id: ' + id);
955   - }
956   -
957   - const entity = ctx._getEntity(lang, id);
958   -
959   - if (entity) {
960   - return format$1(ctx, lang, args, entity);
961   - }
962   -
963   - throw new L10nError('Unknown reference: ' + id);
964   -}
965   -
966   -function subPlaceable$1(locals, ctx, lang, args, id) {
967   - let newLocals, value;
968   -
969   - try {
970   - [newLocals, value] = resolveIdentifier$1(ctx, lang, args, id);
971   - } catch (err) {
972   - return [{ error: err }, FSI$1 + '{{ ' + id + ' }}' + PDI$1];
973   - }
974   -
975   - if (typeof value === 'number') {
976   - const formatter = ctx._getNumberFormatter(lang);
977   - return [newLocals, formatter.format(value)];
978   - }
979   -
980   - if (typeof value === 'string') {
981   - // prevent Billion Laughs attacks
982   - if (value.length >= MAX_PLACEABLE_LENGTH$1) {
983   - throw new L10nError('Too many characters in placeable (' +
984   - value.length + ', max allowed is ' +
985   - MAX_PLACEABLE_LENGTH$1 + ')');
986   - }
987   - return [newLocals, FSI$1 + value + PDI$1];
988   - }
989   -
990   - return [{}, FSI$1 + '{{ ' + id + ' }}' + PDI$1];
991   -}
992   -
993   -function interpolate$1(locals, ctx, lang, args, arr) {
994   - return arr.reduce(function([localsSeq, valueSeq], cur) {
995   - if (typeof cur === 'string') {
996   - return [localsSeq, valueSeq + cur];
997   - } else {
998   - const [, value] = subPlaceable$1(locals, ctx, lang, args, cur.name);
999   - // wrap the substitution in bidi isolate characters
1000   - return [localsSeq, valueSeq + value];
1001   - }
1002   - }, [locals, '']);
1003   -}
1004   -
1005   -function resolveSelector$1(ctx, lang, args, expr, index) {
1006   - //XXX: Dehardcode!!!
1007   - let selectorName;
1008   - if (index[0].type === 'call' && index[0].expr.type === 'prop' &&
1009   - index[0].expr.expr.name === 'cldr') {
1010   - selectorName = 'plural';
1011   - } else {
1012   - selectorName = index[0].name;
1013   - }
1014   - const selector = resolveIdentifier$1(ctx, lang, args, selectorName)[1];
1015   -
1016   - if (typeof selector !== 'function') {
1017   - // selector is a simple reference to an entity or args
1018   - return selector;
1019   - }
1020   -
1021   - const argValue = index[0].args ?
1022   - resolveIdentifier$1(ctx, lang, args, index[0].args[0].name)[1] : undefined;
1023   -
1024   - if (selectorName === 'plural') {
1025   - // special cases for zero, one, two if they are defined on the hash
1026   - if (argValue === 0 && 'zero' in expr) {
1027   - return 'zero';
1028   - }
1029   - if (argValue === 1 && 'one' in expr) {
1030   - return 'one';
1031   - }
1032   - if (argValue === 2 && 'two' in expr) {
1033   - return 'two';
1034   - }
1035   - }
1036   -
1037   - return selector(argValue);
1038   -}
1039   -
1040   -function resolveValue$1(locals, ctx, lang, args, expr, index) {
1041   - if (!expr) {
1042   - return [locals, expr];
1043   - }
1044   -
1045   - if (typeof expr === 'string' ||
1046   - typeof expr === 'boolean' ||
1047   - typeof expr === 'number') {
1048   - return [locals, expr];
1049   - }
1050   -
1051   - if (Array.isArray(expr)) {
1052   - return interpolate$1(locals, ctx, lang, args, expr);
1053   - }
1054   -
1055   - // otherwise, it's a dict
1056   - if (index) {
1057   - // try to use the index in order to select the right dict member
1058   - const selector = resolveSelector$1(ctx, lang, args, expr, index);
1059   - if (selector in expr) {
1060   - return resolveValue$1(locals, ctx, lang, args, expr[selector]);
1061   - }
1062   - }
1063   -
1064   - // if there was no index or no selector was found, try the default
1065   - // XXX 'other' is an artifact from Gaia
1066   - const defaultKey = expr.__default || 'other';
1067   - if (defaultKey in expr) {
1068   - return resolveValue$1(locals, ctx, lang, args, expr[defaultKey]);
1069   - }
1070   -
1071   - throw new L10nError('Unresolvable value');
1072   -}
1073   -
1074   -const locales2rules = {
1075   - 'af': 3,
1076   - 'ak': 4,
1077   - 'am': 4,
1078   - 'ar': 1,
1079   - 'asa': 3,
1080   - 'az': 0,
1081   - 'be': 11,
1082   - 'bem': 3,
1083   - 'bez': 3,
1084   - 'bg': 3,
1085   - 'bh': 4,
1086   - 'bm': 0,
1087   - 'bn': 3,
1088   - 'bo': 0,
1089   - 'br': 20,
1090   - 'brx': 3,
1091   - 'bs': 11,
1092   - 'ca': 3,
1093   - 'cgg': 3,
1094   - 'chr': 3,
1095   - 'cs': 12,
1096   - 'cy': 17,
1097   - 'da': 3,
1098   - 'de': 3,
1099   - 'dv': 3,
1100   - 'dz': 0,
1101   - 'ee': 3,
1102   - 'el': 3,
1103   - 'en': 3,
1104   - 'eo': 3,
1105   - 'es': 3,
1106   - 'et': 3,
1107   - 'eu': 3,
1108   - 'fa': 0,
1109   - 'ff': 5,
1110   - 'fi': 3,
1111   - 'fil': 4,
1112   - 'fo': 3,
1113   - 'fr': 5,
1114   - 'fur': 3,
1115   - 'fy': 3,
1116   - 'ga': 8,
1117   - 'gd': 24,
1118   - 'gl': 3,
1119   - 'gsw': 3,
1120   - 'gu': 3,
1121   - 'guw': 4,
1122   - 'gv': 23,
1123   - 'ha': 3,
1124   - 'haw': 3,
1125   - 'he': 2,
1126   - 'hi': 4,
1127   - 'hr': 11,
1128   - 'hu': 0,
1129   - 'id': 0,
1130   - 'ig': 0,
1131   - 'ii': 0,
1132   - 'is': 3,
1133   - 'it': 3,
1134   - 'iu': 7,
1135   - 'ja': 0,
1136   - 'jmc': 3,
1137   - 'jv': 0,
1138   - 'ka': 0,
1139   - 'kab': 5,
1140   - 'kaj': 3,
1141   - 'kcg': 3,
1142   - 'kde': 0,
1143   - 'kea': 0,
1144   - 'kk': 3,
1145   - 'kl': 3,
1146   - 'km': 0,
1147   - 'kn': 0,
1148   - 'ko': 0,
1149   - 'ksb': 3,
1150   - 'ksh': 21,
1151   - 'ku': 3,
1152   - 'kw': 7,
1153   - 'lag': 18,
1154   - 'lb': 3,
1155   - 'lg': 3,
1156   - 'ln': 4,
1157   - 'lo': 0,
1158   - 'lt': 10,
1159   - 'lv': 6,
1160   - 'mas': 3,
1161   - 'mg': 4,
1162   - 'mk': 16,
1163   - 'ml': 3,
1164   - 'mn': 3,
1165   - 'mo': 9,
1166   - 'mr': 3,
1167   - 'ms': 0,
1168   - 'mt': 15,
1169   - 'my': 0,
1170   - 'nah': 3,
1171   - 'naq': 7,
1172   - 'nb': 3,
1173   - 'nd': 3,
1174   - 'ne': 3,
1175   - 'nl': 3,
1176   - 'nn': 3,
1177   - 'no': 3,
1178   - 'nr': 3,
1179   - 'nso': 4,
1180   - 'ny': 3,
1181   - 'nyn': 3,
1182   - 'om': 3,
1183   - 'or': 3,
1184   - 'pa': 3,
1185   - 'pap': 3,
1186   - 'pl': 13,
1187   - 'ps': 3,
1188   - 'pt': 3,
1189   - 'rm': 3,
1190   - 'ro': 9,
1191   - 'rof': 3,
1192   - 'ru': 11,
1193   - 'rwk': 3,
1194   - 'sah': 0,
1195   - 'saq': 3,
1196   - 'se': 7,
1197   - 'seh': 3,
1198   - 'ses': 0,
1199   - 'sg': 0,
1200   - 'sh': 11,
1201   - 'shi': 19,
1202   - 'sk': 12,
1203   - 'sl': 14,
1204   - 'sma': 7,
1205   - 'smi': 7,
1206   - 'smj': 7,
1207   - 'smn': 7,
1208   - 'sms': 7,
1209   - 'sn': 3,
1210   - 'so': 3,
1211   - 'sq': 3,
1212   - 'sr': 11,
1213   - 'ss': 3,
1214   - 'ssy': 3,
1215   - 'st': 3,
1216   - 'sv': 3,
1217   - 'sw': 3,
1218   - 'syr': 3,
1219   - 'ta': 3,
1220   - 'te': 3,
1221   - 'teo': 3,
1222   - 'th': 0,
1223   - 'ti': 4,
1224   - 'tig': 3,
1225   - 'tk': 3,
1226   - 'tl': 4,
1227   - 'tn': 3,
1228   - 'to': 0,
1229   - 'tr': 0,
1230   - 'ts': 3,
1231   - 'tzm': 22,
1232   - 'uk': 11,
1233   - 'ur': 3,
1234   - 've': 3,
1235   - 'vi': 0,
1236   - 'vun': 3,
1237   - 'wa': 4,
1238   - 'wae': 3,
1239   - 'wo': 0,
1240   - 'xh': 3,
1241   - 'xog': 3,
1242   - 'yo': 0,
1243   - 'zh': 0,
1244   - 'zu': 3
1245   -};
1246   -
1247   -// utility functions for plural rules methods
1248   -function isIn(n, list) {
1249   - return list.indexOf(n) !== -1;
1250   -}
1251   -function isBetween(n, start, end) {
1252   - return typeof n === typeof start && start <= n && n <= end;
1253   -}
1254   -
1255   -// list of all plural rules methods:
1256   -// map an integer to the plural form name to use
1257   -const pluralRules = {
1258   - '0': function() {
1259   - return 'other';
1260   - },
1261   - '1': function(n) {
1262   - if ((isBetween((n % 100), 3, 10))) {
1263   - return 'few';
1264   - }
1265   - if (n === 0) {
1266   - return 'zero';
1267   - }
1268   - if ((isBetween((n % 100), 11, 99))) {
1269   - return 'many';
1270   - }
1271   - if (n === 2) {
1272   - return 'two';
1273   - }
1274   - if (n === 1) {
1275   - return 'one';
1276   - }
1277   - return 'other';
1278   - },
1279   - '2': function(n) {
1280   - if (n !== 0 && (n % 10) === 0) {
1281   - return 'many';
1282   - }
1283   - if (n === 2) {
1284   - return 'two';
1285   - }
1286   - if (n === 1) {
1287   - return 'one';
1288   - }
1289   - return 'other';
1290   - },
1291   - '3': function(n) {
1292   - if (n === 1) {
1293   - return 'one';
1294   - }
1295   - return 'other';
1296   - },
1297   - '4': function(n) {
1298   - if ((isBetween(n, 0, 1))) {
1299   - return 'one';
1300   - }
1301   - return 'other';
1302   - },
1303   - '5': function(n) {
1304   - if ((isBetween(n, 0, 2)) && n !== 2) {
1305   - return 'one';
1306   - }
1307   - return 'other';
1308   - },
1309   - '6': function(n) {
1310   - if (n === 0) {
1311   - return 'zero';
1312   - }
1313   - if ((n % 10) === 1 && (n % 100) !== 11) {
1314   - return 'one';
1315   - }
1316   - return 'other';
1317   - },
1318   - '7': function(n) {
1319   - if (n === 2) {
1320   - return 'two';
1321   - }
1322   - if (n === 1) {
1323   - return 'one';
1324   - }
1325   - return 'other';
1326   - },
1327   - '8': function(n) {
1328   - if ((isBetween(n, 3, 6))) {
1329   - return 'few';
1330   - }
1331   - if ((isBetween(n, 7, 10))) {
1332   - return 'many';
1333   - }
1334   - if (n === 2) {
1335   - return 'two';
1336   - }
1337   - if (n === 1) {
1338   - return 'one';
1339   - }
1340   - return 'other';
1341   - },
1342   - '9': function(n) {
1343   - if (n === 0 || n !== 1 && (isBetween((n % 100), 1, 19))) {
1344   - return 'few';
1345   - }
1346   - if (n === 1) {
1347   - return 'one';
1348   - }
1349   - return 'other';
1350   - },
1351   - '10': function(n) {
1352   - if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) {
1353   - return 'few';
1354   - }
1355   - if ((n % 10) === 1 && !(isBetween((n % 100), 11, 19))) {
1356   - return 'one';
1357   - }
1358   - return 'other';
1359   - },
1360   - '11': function(n) {
1361   - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
1362   - return 'few';
1363   - }
1364   - if ((n % 10) === 0 ||
1365   - (isBetween((n % 10), 5, 9)) ||
1366   - (isBetween((n % 100), 11, 14))) {
1367   - return 'many';
1368   - }
1369   - if ((n % 10) === 1 && (n % 100) !== 11) {
1370   - return 'one';
1371   - }
1372   - return 'other';
1373   - },
1374   - '12': function(n) {
1375   - if ((isBetween(n, 2, 4))) {
1376   - return 'few';
1377   - }
1378   - if (n === 1) {
1379   - return 'one';
1380   - }
1381   - return 'other';
1382   - },
1383   - '13': function(n) {
1384   - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
1385   - return 'few';
1386   - }
1387   - if (n !== 1 && (isBetween((n % 10), 0, 1)) ||
1388   - (isBetween((n % 10), 5, 9)) ||
1389   - (isBetween((n % 100), 12, 14))) {
1390   - return 'many';
1391   - }
1392   - if (n === 1) {
1393   - return 'one';
1394   - }
1395   - return 'other';
1396   - },
1397   - '14': function(n) {
1398   - if ((isBetween((n % 100), 3, 4))) {
1399   - return 'few';
1400   - }
1401   - if ((n % 100) === 2) {
1402   - return 'two';
1403   - }
1404   - if ((n % 100) === 1) {
1405   - return 'one';
1406   - }
1407   - return 'other';
1408   - },
1409   - '15': function(n) {
1410   - if (n === 0 || (isBetween((n % 100), 2, 10))) {
1411   - return 'few';
1412   - }
1413   - if ((isBetween((n % 100), 11, 19))) {
1414   - return 'many';
1415   - }
1416   - if (n === 1) {
1417   - return 'one';
1418   - }
1419   - return 'other';
1420   - },
1421   - '16': function(n) {
1422   - if ((n % 10) === 1 && n !== 11) {
1423   - return 'one';
1424   - }
1425   - return 'other';
1426   - },
1427   - '17': function(n) {
1428   - if (n === 3) {
1429   - return 'few';
1430   - }
1431   - if (n === 0) {
1432   - return 'zero';
1433   - }
1434   - if (n === 6) {
1435   - return 'many';
1436   - }
1437   - if (n === 2) {
1438   - return 'two';
1439   - }
1440   - if (n === 1) {
1441   - return 'one';
1442   - }
1443   - return 'other';
1444   - },
1445   - '18': function(n) {
1446   - if (n === 0) {
1447   - return 'zero';
1448   - }
1449   - if ((isBetween(n, 0, 2)) && n !== 0 && n !== 2) {
1450   - return 'one';
1451   - }
1452   - return 'other';
1453   - },
1454   - '19': function(n) {
1455   - if ((isBetween(n, 2, 10))) {
1456   - return 'few';
1457   - }
1458   - if ((isBetween(n, 0, 1))) {
1459   - return 'one';
1460   - }
1461   - return 'other';
1462   - },
1463   - '20': function(n) {
1464   - if ((isBetween((n % 10), 3, 4) || ((n % 10) === 9)) && !(
1465   - isBetween((n % 100), 10, 19) ||
1466   - isBetween((n % 100), 70, 79) ||
1467   - isBetween((n % 100), 90, 99)
1468   - )) {
1469   - return 'few';
1470   - }
1471   - if ((n % 1000000) === 0 && n !== 0) {
1472   - return 'many';
1473   - }
1474   - if ((n % 10) === 2 && !isIn((n % 100), [12, 72, 92])) {
1475   - return 'two';
1476   - }
1477   - if ((n % 10) === 1 && !isIn((n % 100), [11, 71, 91])) {
1478   - return 'one';
1479   - }
1480   - return 'other';
1481   - },
1482   - '21': function(n) {
1483   - if (n === 0) {
1484   - return 'zero';
1485   - }
1486   - if (n === 1) {
1487   - return 'one';
1488   - }
1489   - return 'other';
1490   - },
1491   - '22': function(n) {
1492   - if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) {
1493   - return 'one';
1494   - }
1495   - return 'other';
1496   - },
1497   - '23': function(n) {
1498   - if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) {
1499   - return 'one';
1500   - }
1501   - return 'other';
1502   - },
1503   - '24': function(n) {
1504   - if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) {
1505   - return 'few';
1506   - }
1507   - if (isIn(n, [2, 12])) {
1508   - return 'two';
1509   - }
1510   - if (isIn(n, [1, 11])) {
1511   - return 'one';
1512   - }
1513   - return 'other';
1514   - }
1515   -};
1516   -
1517   -function getPluralRule(code) {
1518   - // return a function that gives the plural form name for a given integer
1519   - const index = locales2rules[code.replace(/-.*$/, '')];
1520   - if (!(index in pluralRules)) {
1521   - return function() { return 'other'; };
1522   - }
1523   - return pluralRules[index];
1524   -}
1525   -
1526   -class Context {
1527   - constructor(env) {
1528   - this._env = env;
1529   - this._numberFormatters = null;
1530   - }
1531   -
1532   - _formatTuple(lang, args, entity, id, key) {
1533   - try {
1534   - return format$1(this, lang, args, entity);
1535   - } catch (err) {
1536   - err.id = key ? id + '::' + key : id;
1537   - err.lang = lang;
1538   - this._env.emit('resolveerror', err, this);
1539   - return [{ error: err }, err.id];
1540   - }
1541   - }
1542   -
1543   - _formatEntity(lang, args, entity, id) {
1544   - const [, value] = this._formatTuple(lang, args, entity, id);
1545   -
1546   - const formatted = {
1547   - value,
1548   - attrs: null,
1549   - };
1550   -
1551   - if (entity.attrs) {
1552   - formatted.attrs = Object.create(null);
1553   - for (let key in entity.attrs) {
1554   - /* jshint -W089 */
1555   - const [, attrValue] = this._formatTuple(
1556   - lang, args, entity.attrs[key], id, key);
1557   - formatted.attrs[key] = attrValue;
1558   - }
1559   - }
1560   -
1561   - return formatted;
1562   - }
1563   -
1564   - _formatValue(lang, args, entity, id) {
1565   - return this._formatTuple(lang, args, entity, id)[1];
1566   - }
1567   -
1568   - fetch(langs) {
1569   - if (langs.length === 0) {
1570   - return Promise.resolve(langs);
1571   - }
1572   -
1573   - const resIds = Array.from(this._env._resLists.get(this));
1574   -
1575   - return Promise.all(
1576   - resIds.map(
1577   - this._env._getResource.bind(this._env, langs[0]))).then(
1578   - () => langs);
1579   - }
1580   -
1581   - _resolve(langs, keys, formatter, prevResolved) {
1582   - const lang = langs[0];
1583   -
1584   - if (!lang) {
1585   - return reportMissing.call(this, keys, formatter, prevResolved);
1586   - }
1587   -
1588   - let hasUnresolved = false;
1589   -
1590   - const resolved = keys.map((key, i) => {
1591   - if (prevResolved && prevResolved[i] !== undefined) {
1592   - return prevResolved[i];
1593   - }
1594   - const [id, args] = Array.isArray(key) ?
1595   - key : [key, undefined];
1596   - const entity = this._getEntity(lang, id);
1597   -
1598   - if (entity) {
1599   - return formatter.call(this, lang, args, entity, id);
1600   - }
1601   -
1602   - this._env.emit('notfounderror',
1603   - new L10nError('"' + id + '"' + ' not found in ' + lang.code,
1604   - id, lang), this);
1605   - hasUnresolved = true;
1606   - });
1607   -
1608   - if (!hasUnresolved) {
1609   - return resolved;
1610   - }
1611   -
1612   - return this.fetch(langs.slice(1)).then(
1613   - nextLangs => this._resolve(nextLangs, keys, formatter, resolved));
1614   - }
1615   -
1616   - resolveEntities(langs, keys) {
1617   - return this.fetch(langs).then(
1618   - langs => this._resolve(langs, keys, this._formatEntity));
1619   - }
1620   -
1621   - resolveValues(langs, keys) {
1622   - return this.fetch(langs).then(
1623   - langs => this._resolve(langs, keys, this._formatValue));
1624   - }
1625   -
1626   - _getEntity(lang, id) {
1627   - const cache = this._env._resCache;
1628   - const resIds = Array.from(this._env._resLists.get(this));
1629   -
1630   - // Look for `id` in every resource in order.
1631   - for (let i = 0, resId; resId = resIds[i]; i++) {
1632   - const resource = cache.get(resId + lang.code + lang.src);
1633   - if (resource instanceof L10nError) {
1634   - continue;
1635   - }
1636   - if (id in resource) {
1637   - return resource[id];
1638   - }
1639   - }
1640   - return undefined;
1641   - }
1642   -
1643   - _getNumberFormatter(lang) {
1644   - if (!this._numberFormatters) {
1645   - this._numberFormatters = new Map();
1646   - }
1647   - if (!this._numberFormatters.has(lang)) {
1648   - const formatter = Intl.NumberFormat(lang, {
1649   - useGrouping: false,
1650   - });
1651   - this._numberFormatters.set(lang, formatter);
1652   - return formatter;
1653   - }
1654   - return this._numberFormatters.get(lang);
1655   - }
1656   -
1657   - // XXX in the future macros will be stored in localization resources together
1658   - // with regular entities and this method will not be needed anymore
1659   - _getMacro(lang, id) {
1660   - switch(id) {
1661   - case 'plural':
1662   - return getPluralRule(lang.code);
1663   - default:
1664   - return undefined;
1665   - }
1666   - }
1667   -
1668   -}
1669   -
1670   -function reportMissing(keys, formatter, resolved) {
1671   - const missingIds = new Set();
1672   -
1673   - keys.forEach((key, i) => {
1674   - if (resolved && resolved[i] !== undefined) {
1675   - return;
1676   - }
1677   - const id = Array.isArray(key) ? key[0] : key;
1678   - missingIds.add(id);
1679   - resolved[i] = formatter === this._formatValue ?
1680   - id : {value: id, attrs: null};
1681   - });
1682   -
1683   - this._env.emit('notfounderror', new L10nError(
1684   - '"' + Array.from(missingIds).join(', ') + '"' +
1685   - ' not found in any language', missingIds), this);
1686   -
1687   - return resolved;
1688   -}
1689   -
1690   -function emit(listeners, ...args) {
1691   - const type = args.shift();
1692   -
1693   - if (listeners['*']) {
1694   - listeners['*'].slice().forEach(
1695   - listener => listener.apply(this, args));
1696   - }
1697   -
1698   - if (listeners[type]) {
1699   - listeners[type].slice().forEach(
1700   - listener => listener.apply(this, args));
1701   - }
1702   -}
1703   -
1704   -function addEventListener(listeners, type, listener) {
1705   - if (!(type in listeners)) {
1706   - listeners[type] = [];
1707   - }
1708   - listeners[type].push(listener);
1709   -}
1710   -
1711   -function removeEventListener(listeners, type, listener) {
1712   - const typeListeners = listeners[type];
1713   - const pos = typeListeners.indexOf(listener);
1714   - if (pos === -1) {
1715   - return;
1716   - }
1717   -
1718   - typeListeners.splice(pos, 1);
1719   -}
1720   -
1721   -const parsers = {
1722   - properties: PropertiesParser,
1723   - l20n: L20nParser,
1724   -};
1725   -
1726   -class Env {
1727   - constructor(defaultLang, fetchResource) {
1728   - this.defaultLang = defaultLang;
1729   - this.fetchResource = fetchResource;
1730   -
1731   - this._resLists = new Map();
1732   - this._resCache = new Map();
1733   -
1734   - const listeners = {};
1735   - this.emit = emit.bind(this, listeners);
1736   - this.addEventListener = addEventListener.bind(this, listeners);
1737   - this.removeEventListener = removeEventListener.bind(this, listeners);
1738   - }
1739   -
1740   - createContext(resIds) {
1741   - const ctx = new Context(this);
1742   - this._resLists.set(ctx, new Set(resIds));
1743   - return ctx;
1744   - }
1745   -
1746   - destroyContext(ctx) {
1747   - const lists = this._resLists;
1748   - const resList = lists.get(ctx);
1749   -
1750   - lists.delete(ctx);
1751   - resList.forEach(
1752   - resId => deleteIfOrphan(this._resCache, lists, resId));
1753   - }
1754   -
1755   - _parse(syntax, lang, data) {
1756   - const parser = parsers[syntax];
1757   - if (!parser) {
1758   - return data;
1759   - }
1760   -
1761   - const emit = (type, err) => this.emit(type, amendError$1(lang, err));
1762   - return parser.parse.call(parser, emit, data);
1763   - }
1764   -
1765   - _create(lang, entries) {
1766   - if (lang.src !== 'pseudo') {
1767   - return entries;
1768   - }
1769   -
1770   - const pseudoentries = Object.create(null);
1771   - for (let key in entries) {
1772   - pseudoentries[key] = walkEntry(
1773   - entries[key], pseudo$1[lang.code].process);
1774   - }
1775   - return pseudoentries;
1776   - }
1777   -
1778   - _getResource(lang, res) {
1779   - const cache = this._resCache;
1780   - const id = res + lang.code + lang.src;
1781   -
1782   - if (cache.has(id)) {
1783   - return cache.get(id);
1784   - }
1785   -
1786   - const syntax = res.substr(res.lastIndexOf('.') + 1);
1787   -
1788   - const saveEntries = data => {
1789   - const entries = this._parse(syntax, lang, data);
1790   - cache.set(id, this._create(lang, entries));
1791   - };
1792   -
1793   - const recover = err => {
1794   - err.lang = lang;
1795   - this.emit('fetcherror', err);
1796   - cache.set(id, err);
1797   - };
1798   -
1799   - const langToFetch = lang.src === 'pseudo' ?
1800   - { code: this.defaultLang, src: 'app' } :
1801   - lang;
1802   -
1803   - const resource = this.fetchResource(res, langToFetch).then(
1804   - saveEntries, recover);
1805   -
1806   - cache.set(id, resource);
1807   -
1808   - return resource;
1809   - }
1810   -}
1811   -
1812   -function deleteIfOrphan(cache, lists, resId) {
1813   - const isNeeded = Array.from(lists).some(
1814   - ([ctx, resIds]) => resIds.has(resId));
1815   -
1816   - if (!isNeeded) {
1817   - cache.forEach((val, key) =>
1818   - key.startsWith(resId) ? cache.delete(key) : null);
1819   - }
1820   -}
1821   -
1822   -function amendError$1(lang, err) {
1823   - err.lang = lang;
1824   - return err;
1825   -}
1826   -
1827   -const KNOWN_MACROS = ['plural'];
1828   -const MAX_PLACEABLE_LENGTH = 2500;
1829   -
1830   -// Matches characters outside of the Latin-1 character set
1831   -const nonLatin1 = /[^\x01-\xFF]/;
1832   -
1833   -// Unicode bidi isolation characters
1834   -const FSI = '\u2068';
1835   -const PDI = '\u2069';
1836   -
1837   -const resolutionChain = new WeakSet();
1838   -
1839   -function createEntry(node) {
1840   - const keys = Object.keys(node);
1841   -
1842   - // the most common scenario: a simple string with no arguments
1843   - if (typeof node.$v === 'string' && keys.length === 2) {
1844   - return node.$v;
1845   - }
1846   -
1847   - let attrs;
1848   -
1849   - for (let i = 0, key; (key = keys[i]); i++) {
1850   - // skip $i (id), $v (value), $x (index)
1851   - if (key[0] === '$') {
1852   - continue;
1853   - }
1854   -
1855   - if (!attrs) {
1856   - attrs = Object.create(null);
1857   - }
1858   - attrs[key] = createAttribute(node[key]);
1859   - }
1860   -
1861   - return {
1862   - value: node.$v !== undefined ? node.$v : null,
1863   - index: node.$x || null,
1864   - attrs: attrs || null,
1865   - };
1866   -}
1867   -
1868   -function createAttribute(node) {
1869   - if (typeof node === 'string') {
1870   - return node;
1871   - }
1872   -
1873   - return {
1874   - value: node.$v || (node !== undefined ? node : null),
1875   - index: node.$x || null,
1876   - };
1877   -}
1878   -
1879   -
1880   -function format(ctx, lang, args, entity) {
1881   - if (typeof entity === 'string') {
1882   - return [{}, entity];
1883   - }
1884   -
1885   - if (resolutionChain.has(entity)) {
1886   - throw new L10nError('Cyclic reference detected');
1887   - }
1888   -
1889   - resolutionChain.add(entity);
1890   -
1891   - let rv;
1892   - // if format fails, we want the exception to bubble up and stop the whole
1893   - // resolving process; however, we still need to remove the entity from the
1894   - // resolution chain
1895   - try {
1896   - rv = resolveValue(
1897   - {}, ctx, lang, args, entity.value, entity.index);
1898   - } finally {
1899   - resolutionChain.delete(entity);
1900   - }
1901   - return rv;
1902   -}
1903   -
1904   -function resolveIdentifier(ctx, lang, args, id) {
1905   - if (KNOWN_MACROS.indexOf(id) > -1) {
1906   - return [{}, ctx._getMacro(lang, id)];
1907   - }
1908   -
1909   - if (args && args.hasOwnProperty(id)) {
1910   - if (typeof args[id] === 'string' || (typeof args[id] === 'number' &&
1911   - !isNaN(args[id]))) {
1912   - return [{}, args[id]];
1913   - } else {
1914   - throw new L10nError('Arg must be a string or a number: ' + id);
1915   - }
1916   - }
1917   -
1918   - // XXX: special case for Node.js where still:
1919   - // '__proto__' in Object.create(null) => true
1920   - if (id === '__proto__') {
1921   - throw new L10nError('Illegal id: ' + id);
1922   - }
1923   -
1924   - const entity = ctx._getEntity(lang, id);
1925   -
1926   - if (entity) {
1927   - return format(ctx, lang, args, entity);
1928   - }
1929   -
1930   - throw new L10nError('Unknown reference: ' + id);
1931   -}
1932   -
1933   -function subPlaceable(locals, ctx, lang, args, id) {
1934   - let res;
1935   -
1936   - try {
1937   - res = resolveIdentifier(ctx, lang, args, id);
1938   - } catch (err) {
1939   - return [{ error: err }, '{{ ' + id + ' }}'];
1940   - }
1941   -
1942   - const value = res[1];
1943   -
1944   - if (typeof value === 'number') {
1945   - return res;
1946   - }
1947   -
1948   - if (typeof value === 'string') {
1949   - // prevent Billion Laughs attacks
1950   - if (value.length >= MAX_PLACEABLE_LENGTH) {
1951   - throw new L10nError('Too many characters in placeable (' +
1952   - value.length + ', max allowed is ' +
1953   - MAX_PLACEABLE_LENGTH + ')');
1954   - }
1955   -
1956   - if (locals.contextIsNonLatin1 || value.match(nonLatin1)) {
1957   - // When dealing with non-Latin-1 text
1958   - // we wrap substitutions in bidi isolate characters
1959   - // to avoid bidi issues.
1960   - res[1] = FSI + value + PDI;
1961   - }
1962   -
1963   - return res;
1964   - }
1965   -
1966   - return [{}, '{{ ' + id + ' }}'];
1967   -}
1968   -
1969   -function interpolate(locals, ctx, lang, args, arr) {
1970   - return arr.reduce(function([localsSeq, valueSeq], cur) {
1971   - if (typeof cur === 'string') {
1972   - return [localsSeq, valueSeq + cur];
1973   - } else if (cur.t === 'idOrVar'){
1974   - const [, value] = subPlaceable(locals, ctx, lang, args, cur.v);
1975   - return [localsSeq, valueSeq + value];
1976   - }
1977   - }, [locals, '']);
1978   -}
1979   -
1980   -function resolveSelector(ctx, lang, args, expr, index) {
1981   - const selectorName = index[0].v;
1982   - const selector = resolveIdentifier(ctx, lang, args, selectorName)[1];
1983   -
1984   - if (typeof selector !== 'function') {
1985   - // selector is a simple reference to an entity or args
1986   - return selector;
1987   - }
1988   -
1989   - const argValue = index[1] ?
1990   - resolveIdentifier(ctx, lang, args, index[1])[1] : undefined;
1991   -
1992   - if (selectorName === 'plural') {
1993   - // special cases for zero, one, two if they are defined on the hash
1994   - if (argValue === 0 && 'zero' in expr) {
1995   - return 'zero';
1996   - }
1997   - if (argValue === 1 && 'one' in expr) {
1998   - return 'one';
1999   - }
2000   - if (argValue === 2 && 'two' in expr) {
2001   - return 'two';
2002   - }
2003   - }
2004   -
2005   - return selector(argValue);
2006   -}
2007   -
2008   -function resolveValue(locals, ctx, lang, args, expr, index) {
2009   - if (!expr) {
2010   - return [locals, expr];
2011   - }
2012   -
2013   - if (typeof expr === 'string' ||
2014   - typeof expr === 'boolean' ||
2015   - typeof expr === 'number') {
2016   - return [locals, expr];
2017   - }
2018   -
2019   - if (Array.isArray(expr)) {
2020   - locals.contextIsNonLatin1 = expr.some(function($_) {
2021   - return typeof($_) === 'string' && $_.match(nonLatin1);
2022   - });
2023   - return interpolate(locals, ctx, lang, args, expr);
2024   - }
2025   -
2026   - // otherwise, it's a dict
2027   - if (index) {
2028   - // try to use the index in order to select the right dict member
2029   - const selector = resolveSelector(ctx, lang, args, expr, index);
2030   - if (expr.hasOwnProperty(selector)) {
2031   - return resolveValue(locals, ctx, lang, args, expr[selector]);
2032   - }
2033   - }
2034   -
2035   - // if there was no index or no selector was found, try 'other'
2036   - if ('other' in expr) {
2037   - return resolveValue(locals, ctx, lang, args, expr.other);
2038   - }
2039   -
2040   - throw new L10nError('Unresolvable value');
2041   -}
2042   -
2043   -function LegacyContext(env) {
2044   - Context.call(this, env);
2045   -}
2046   -
2047   -LegacyContext.prototype = Object.create(Context.prototype);
2048   -
2049   -LegacyContext.prototype._formatTuple = function(lang, args, entity, id, key) {
2050   - try {
2051   - return format(this, lang, args, entity);
2052   - } catch (err) {
2053   - err.id = key ? id + '::' + key : id;
2054   - err.lang = lang;
2055   - this._env.emit('resolveerror', err, this);
2056   - return [{ error: err }, err.id];
2057   - }
2058   -};
2059   -
2060   -var MAX_PLACEABLES$2 = 100;
2061   -
2062   -var PropertiesParser$1 = {
2063   - patterns: null,
2064   - entryIds: null,
2065   -
2066   - init: function() {
2067   - this.patterns = {
2068   - comment: /^\s*#|^\s*$/,
2069   - entity: /^([^=\s]+)\s*=\s*(.*)$/,
2070   - multiline: /[^\\]\\$/,
2071   - index: /\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,
2072   - unicode: /\\u([0-9a-fA-F]{1,4})/g,
2073   - entries: /[^\r\n]+/g,
2074   - controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g,
2075   - placeables: /\{\{\s*([^\s]*?)\s*\}\}/,
2076   - };
2077   - },
2078   -
2079   - parse: function(emit, source) {
2080   - if (!this.patterns) {
2081   - this.init();
2082   - }
2083   -
2084   - var ast = [];
2085   - this.entryIds = Object.create(null);
2086   -
2087   - var entries = source.match(this.patterns.entries);
2088   - if (!entries) {
2089   - return ast;
2090   - }
2091   - for (var i = 0; i < entries.length; i++) {
2092   - var line = entries[i];
2093   -
2094   - if (this.patterns.comment.test(line)) {
2095   - continue;
2096   - }
2097   -
2098   - while (this.patterns.multiline.test(line) && i < entries.length) {
2099   - line = line.slice(0, -1) + entries[++i].trim();
2100   - }
2101   -
2102   - var entityMatch = line.match(this.patterns.entity);
2103   - if (entityMatch) {
2104   - try {
2105   - this.parseEntity(entityMatch[1], entityMatch[2], ast);
2106   - } catch (e) {
2107   - if (emit) {
2108   - emit('parseerror', e);
2109   - } else {
2110   - throw e;
2111   - }
2112   - }
2113   - }
2114   - }
2115   - return ast;
2116   - },
2117   -
2118   - parseEntity: function(id, value, ast) {
2119   - var name, key;
2120   -
2121   - var pos = id.indexOf('[');
2122   - if (pos !== -1) {
2123   - name = id.substr(0, pos);
2124   - key = id.substring(pos + 1, id.length - 1);
2125   - } else {
2126   - name = id;
2127   - key = null;
2128   - }
2129   -
2130   - var nameElements = name.split('.');
2131   -
2132   - if (nameElements.length > 2) {
2133   - throw new L10nError('Error in ID: "' + name + '".' +
2134   - ' Nested attributes are not supported.');
2135   - }
2136   -
2137   - var attr;
2138   - if (nameElements.length > 1) {
2139   - name = nameElements[0];
2140   - attr = nameElements[1];
2141   -
2142   - if (attr[0] === '$') {
2143   - throw new L10nError('Attribute can\'t start with "$"', id);
2144   - }
2145   - } else {
2146   - attr = null;
2147   - }
2148   -
2149   - this.setEntityValue(name, attr, key, this.unescapeString(value), ast);
2150   - },
2151   -
2152   - setEntityValue: function(id, attr, key, rawValue, ast) {
2153   - var pos, v;
2154   -
2155   - var value = rawValue.indexOf('{{') > -1 ?
2156   - this.parseString(rawValue) : rawValue;
2157   -
2158   - if (attr) {
2159   - pos = this.entryIds[id];
2160   - if (pos === undefined) {
2161   - v = {$i: id};
2162   - if (key) {
2163   - v[attr] = {$v: {}};
2164   - v[attr].$v[key] = value;
2165   - } else {
2166   - v[attr] = value;
2167   - }
2168   - ast.push(v);
2169   - this.entryIds[id] = ast.length - 1;
2170   - return;
2171   - }
2172   - if (key) {
2173   - if (typeof(ast[pos][attr]) === 'string') {
2174   - ast[pos][attr] = {
2175   - $x: this.parseIndex(ast[pos][attr]),
2176   - $v: {}
2177   - };
2178   - }
2179   - ast[pos][attr].$v[key] = value;
2180   - return;
2181   - }
2182   - ast[pos][attr] = value;
2183   - return;
2184   - }
2185   -
2186   - // Hash value
2187   - if (key) {
2188   - pos = this.entryIds[id];
2189   - if (pos === undefined) {
2190   - v = {};
2191   - v[key] = value;
2192   - ast.push({$i: id, $v: v});
2193   - this.entryIds[id] = ast.length - 1;
2194   - return;
2195   - }
2196   - if (typeof(ast[pos].$v) === 'string') {
2197   - ast[pos].$x = this.parseIndex(ast[pos].$v);
2198   - ast[pos].$v = {};
2199   - }
2200   - ast[pos].$v[key] = value;
2201   - return;
2202   - }
2203   -
2204   - // simple value
2205   - ast.push({$i: id, $v: value});
2206   - this.entryIds[id] = ast.length - 1;
2207   - },
2208   -
2209   - parseString: function(str) {
2210   - var chunks = str.split(this.patterns.placeables);
2211   - var complexStr = [];
2212   -
2213   - var len = chunks.length;
2214   - var placeablesCount = (len - 1) / 2;
2215   -
2216   - if (placeablesCount >= MAX_PLACEABLES$2) {
2217   - throw new L10nError('Too many placeables (' + placeablesCount +
2218   - ', max allowed is ' + MAX_PLACEABLES$2 + ')');
2219   - }
2220   -
2221   - for (var i = 0; i < chunks.length; i++) {
2222   - if (chunks[i].length === 0) {
2223   - continue;
2224   - }
2225   - if (i % 2 === 1) {
2226   - complexStr.push({t: 'idOrVar', v: chunks[i]});
2227   - } else {
2228   - complexStr.push(chunks[i]);
2229   - }
2230   - }
2231   - return complexStr;
2232   - },
2233   -
2234   - unescapeString: function(str) {
2235   - if (str.lastIndexOf('\\') !== -1) {
2236   - str = str.replace(this.patterns.controlChars, '$1');
2237   - }
2238   - return str.replace(this.patterns.unicode, function(match, token) {
2239   - return String.fromCodePoint(parseInt(token, 16));
2240   - });
2241   - },
2242   -
2243   - parseIndex: function(str) {
2244   - var match = str.match(this.patterns.index);
2245   - if (!match) {
2246   - throw new L10nError('Malformed index');
2247   - }
2248   - if (match[2]) {
2249   - return [{t: 'idOrVar', v: match[1]}, match[2]];
2250   - } else {
2251   - return [{t: 'idOrVar', v: match[1]}];
2252   - }
2253   - }
2254   -};
2255   -
2256   -// Recursively walk an AST node searching for content leaves
2257   -function walkContent(node, fn) {
2258   - if (typeof node === 'string') {
2259   - return fn(node);
2260   - }
2261   -
2262   - if (node.t === 'idOrVar') {
2263   - return node;
2264   - }
2265   -
2266   - const rv = Array.isArray(node) ? [] : {};
2267   - const keys = Object.keys(node);
2268   -
2269   - for (let i = 0, key; (key = keys[i]); i++) {
2270   - // don't change identifier ($i) nor indices ($x)
2271   - if (key === '$i' || key === '$x') {
2272   - rv[key] = node[key];
2273   - } else {
2274   - rv[key] = walkContent(node[key], fn);
2275   - }
2276   - }
2277   - return rv;
2278   -}
2279   -
2280   -// XXX babel's inheritance code triggers JavaScript warnings about modifying
2281   -// the prototype object so we use regular prototypal inheritance here
2282   -function LegacyEnv(defaultLang, fetchResource) {
2283   - Env.call(this, defaultLang, fetchResource);
2284   -}
2285   -
2286   -LegacyEnv.prototype = Object.create(Env.prototype);
2287   -
2288   -LegacyEnv.prototype.createContext = function(resIds) {
2289   - const ctx = new LegacyContext(this);
2290   - this._resLists.set(ctx, new Set(resIds));
2291   - return ctx;
2292   -};
2293   -
2294   -LegacyEnv.prototype._parse = function(syntax, lang, data) {
2295   - const emit = (type, err) => this.emit(type, amendError$1(lang, err));
2296   - return PropertiesParser$1.parse.call(PropertiesParser$1, emit, data);
2297   -};
2298   -
2299   -LegacyEnv.prototype._create = function(lang, ast) {
2300   - const entries = Object.create(null);
2301   - const create = lang.src === 'pseudo' ?
2302   - createPseudoEntry : createEntry;
2303   -
2304   - for (let i = 0, node; node = ast[i]; i++) {
2305   - const id = node.$i;
2306   - if (id in entries) {
2307   - this.emit('duplicateerror', new L10nError(
2308   - 'Duplicate string "' + id + '" found in ' + lang.code, id, lang));
2309   - }
2310   - entries[id] = create(node, lang);
2311   - }
2312   -
2313   - return entries;
2314   -};
2315   -
2316   -function createPseudoEntry(node, lang) {
2317   - return createEntry(walkContent(node, pseudo$1[lang.code].process));
2318   -}
2319   -
2320   -// match the opening angle bracket (<) in HTML tags, and HTML entities like
2321   -// &amp;, &#0038;, &#x0026;.
2322   -const reOverlay = /<|&#?\w+;/;
2323   -
2324   -const allowed = {
2325   - elements: [
2326   - 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
2327   - 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u',
2328   - 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr'
2329   - ],
2330   - attributes: {
2331   - global: [ 'title', 'aria-label', 'aria-valuetext', 'aria-moz-hint' ],
2332   - a: [ 'download' ],
2333   - area: [ 'download', 'alt' ],
2334   - // value is special-cased in isAttrAllowed
2335   - input: [ 'alt', 'placeholder' ],
2336   - menuitem: [ 'label' ],
2337   - menu: [ 'label' ],
2338   - optgroup: [ 'label' ],
2339   - option: [ 'label' ],
2340   - track: [ 'label' ],
2341   - img: [ 'alt' ],
2342   - textarea: [ 'placeholder' ],
2343   - th: [ 'abbr']
2344   - }
2345   -};
2346   -
2347   -function overlayElement(element, translation) {
2348   - const value = translation.value;
2349   -
2350   - if (typeof value === 'string') {
2351   - if (!reOverlay.test(value)) {
2352   - element.textContent = value;
2353   - } else {
2354   - // start with an inert template element and move its children into
2355   - // `element` but such that `element`'s own children are not replaced
2356   - const tmpl = element.ownerDocument.createElement('template');
2357   - tmpl.innerHTML = value;
2358   - // overlay the node with the DocumentFragment
2359   - overlay(element, tmpl.content);
2360   - }
2361   - }
2362   -
2363   - for (let key in translation.attrs) {
2364   - const attrName = camelCaseToDashed(key);
2365   - if (isAttrAllowed({ name: attrName }, element)) {
2366   - element.setAttribute(attrName, translation.attrs[key]);
2367   - }
2368   - }
2369   -}
2370   -
2371   -// The goal of overlay is to move the children of `translationElement`
2372   -// into `sourceElement` such that `sourceElement`'s own children are not
2373   -// replaced, but onle have their text nodes and their attributes modified.
2374   -//
2375   -// We want to make it possible for localizers to apply text-level semantics to
2376   -// the translations and make use of HTML entities. At the same time, we
2377   -// don't trust translations so we need to filter unsafe elements and
2378   -// attribtues out and we don't want to break the Web by replacing elements to
2379   -// which third-party code might have created references (e.g. two-way
2380   -// bindings in MVC frameworks).
2381   -function overlay(sourceElement, translationElement) {
2382   - const result = translationElement.ownerDocument.createDocumentFragment();
2383   - let k, attr;
2384   -
2385   - // take one node from translationElement at a time and check it against
2386   - // the allowed list or try to match it with a corresponding element
2387   - // in the source
2388   - let childElement;
2389   - while ((childElement = translationElement.childNodes[0])) {
2390   - translationElement.removeChild(childElement);
2391   -
2392   - if (childElement.nodeType === childElement.TEXT_NODE) {
2393   - result.appendChild(childElement);
2394   - continue;
2395   - }
2396   -
2397   - const index = getIndexOfType(childElement);
2398   - const sourceChild = getNthElementOfType(sourceElement, childElement, index);
2399   - if (sourceChild) {
2400   - // there is a corresponding element in the source, let's use it
2401   - overlay(sourceChild, childElement);
2402   - result.appendChild(sourceChild);
2403   - continue;
2404   - }
2405   -
2406   - if (isElementAllowed(childElement)) {
2407   - const sanitizedChild = childElement.ownerDocument.createElement(
2408   - childElement.nodeName);
2409   - overlay(sanitizedChild, childElement);
2410   - result.appendChild(sanitizedChild);
2411   - continue;
2412   - }
2413   -
2414   - // otherwise just take this child's textContent
2415   - result.appendChild(
2416   - translationElement.ownerDocument.createTextNode(
2417   - childElement.textContent));
2418   - }
2419   -
2420   - // clear `sourceElement` and append `result` which by this time contains
2421   - // `sourceElement`'s original children, overlayed with translation
2422   - sourceElement.textContent = '';
2423   - sourceElement.appendChild(result);
2424   -
2425   - // if we're overlaying a nested element, translate the allowed
2426   - // attributes; top-level attributes are handled in `translateElement`
2427   - // XXX attributes previously set here for another language should be
2428   - // cleared if a new language doesn't use them; https://bugzil.la/922577
2429   - if (translationElement.attributes) {
2430   - for (k = 0, attr; (attr = translationElement.attributes[k]); k++) {
2431   - if (isAttrAllowed(attr, sourceElement)) {
2432   - sourceElement.setAttribute(attr.name, attr.value);
2433   - }
2434   - }
2435   - }
2436   -}
2437   -
2438   -// XXX the allowed list should be amendable; https://bugzil.la/922573
2439   -function isElementAllowed(element) {
2440   - return allowed.elements.indexOf(element.tagName.toLowerCase()) !== -1;
2441   -}
2442   -
2443   -function isAttrAllowed(attr, element) {
2444   - const attrName = attr.name.toLowerCase();
2445   - const tagName = element.tagName.toLowerCase();
2446   - // is it a globally safe attribute?
2447   - if (allowed.attributes.global.indexOf(attrName) !== -1) {
2448   - return true;
2449   - }
2450   - // are there no allowed attributes for this element?
2451   - if (!allowed.attributes[tagName]) {
2452   - return false;
2453   - }
2454   - // is it allowed on this element?
2455   - // XXX the allowed list should be amendable; https://bugzil.la/922573
2456   - if (allowed.attributes[tagName].indexOf(attrName) !== -1) {
2457   - return true;
2458   - }
2459   - // special case for value on inputs with type button, reset, submit
2460   - if (tagName === 'input' && attrName === 'value') {
2461   - const type = element.type.toLowerCase();
2462   - if (type === 'submit' || type === 'button' || type === 'reset') {
2463   - return true;
2464   - }
2465   - }
2466   - return false;
2467   -}
2468   -
2469   -// Get n-th immediate child of context that is of the same type as element.
2470   -// XXX Use querySelector(':scope > ELEMENT:nth-of-type(index)'), when:
2471   -// 1) :scope is widely supported in more browsers and 2) it works with
2472   -// DocumentFragments.
2473   -function getNthElementOfType(context, element, index) {
2474   - /* jshint boss:true */
2475   - let nthOfType = 0;
2476   - for (let i = 0, child; child = context.children[i]; i++) {
2477   - if (child.nodeType === child.ELEMENT_NODE &&
2478   - child.tagName === element.tagName) {
2479   - if (nthOfType === index) {
2480   - return child;
2481   - }
2482   - nthOfType++;
2483   - }
2484   - }
2485   - return null;
2486   -}
2487   -
2488   -// Get the index of the element among siblings of the same type.
2489   -function getIndexOfType(element) {
2490   - let index = 0;
2491   - let child;
2492   - while ((child = element.previousElementSibling)) {
2493   - if (child.tagName === element.tagName) {
2494   - index++;
2495   - }
2496   - }
2497   - return index;
2498   -}
2499   -
2500   -function camelCaseToDashed(string) {
2501   - // XXX workaround for https://bugzil.la/1141934
2502   - if (string === 'ariaValueText') {
2503   - return 'aria-valuetext';
2504   - }
2505   -
2506   - return string
2507   - .replace(/[A-Z]/g, function (match) {
2508   - return '-' + match.toLowerCase();
2509   - })
2510   - .replace(/^-/, '');
2511   -}
2512   -
2513   -const reHtml = /[&<>]/g;
2514   -const htmlEntities = {
2515   - '&': '&amp;',
2516   - '<': '&lt;',
2517   - '>': '&gt;',
2518   -};
2519   -
2520   -function getResourceLinks(head) {
2521   - return Array.prototype.map.call(
2522   - head.querySelectorAll('link[rel="localization"]'),
2523   - el => el.getAttribute('href'));
2524   -}
2525   -
2526   -function getTranslatables(element) {
2527   - const nodes = Array.from(element.querySelectorAll('[data-l10n-id]'));
2528   -
2529   - if (typeof element.hasAttribute === 'function' &&
2530   - element.hasAttribute('data-l10n-id')) {
2531   - nodes.push(element);
2532   - }
2533   -
2534   - return nodes;
2535   -}
2536   -
2537   -function translateFragment(view, langs, frag) {
2538   - return translateElements(view, langs, getTranslatables(frag));
2539   -}
2540   -
2541   -function getElementsTranslation(view, langs, elems) {
2542   - const keys = elems.map(elem => {
2543   - const id = elem.getAttribute('data-l10n-id');
2544   - const args = elem.getAttribute('data-l10n-args');
2545   - return args ? [
2546   - id,
2547   - JSON.parse(args.replace(reHtml, match => htmlEntities[match]))
2548   - ] : id;
2549   - });
2550   -
2551   - return view._resolveEntities(langs, keys);
2552   -}
2553   -
2554   -function translateElements(view, langs, elements) {
2555   - return getElementsTranslation(view, langs, elements).then(
2556   - translations => applyTranslations(view, elements, translations));
2557   -}
2558   -
2559   -function applyTranslations(view, elems, translations) {
2560   - view._disconnect();
2561   - for (let i = 0; i < elems.length; i++) {
2562   - overlayElement(elems[i], translations[i]);
2563   - }
2564   - view._observe();
2565   -}
2566   -
2567   -// Polyfill NodeList.prototype[Symbol.iterator] for Chrome.
2568   -// See https://code.google.com/p/chromium/issues/detail?id=401699
2569   -if (typeof NodeList === 'function' && !NodeList.prototype[Symbol.iterator]) {
2570   - NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
2571   -}
2572   -
2573   -// Intl.Locale
2574   -function getDirection(code) {
2575   - const tag = code.split('-')[0];
2576   - return ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(tag) >= 0 ?
2577   - 'rtl' : 'ltr';
2578   -}
2579   -
2580   -function serializeContext(ctx, lang) {
2581   - const cache = ctx._env._resCache;
2582   - const resIds = Array.from(ctx._env._resLists.get(ctx));
2583   - return resIds.reduceRight(([errorsSeq, entriesSeq], cur) => {
2584   - const sourceRes = cache.get(cur + 'en-USapp');
2585   - const langRes = cache.get(cur + lang.code + lang.src);
2586   - const [errors, entries] = serializeEntries(
2587   - lang,
2588   - langRes instanceof L10nError ? {} : langRes,
2589   - sourceRes instanceof L10nError ? {} : sourceRes);
2590   - return [errorsSeq.concat(errors), Object.assign(entriesSeq, entries)];
2591   - }, [[], Object.create(null)]);
2592   -}
2593   -
2594   -function serializeEntries(lang, langEntries, sourceEntries) {
2595   - const errors = [];
2596   - const entries = Object.create(null);
2597   -
2598   - for (let id in sourceEntries) {
2599   - const sourceEntry = sourceEntries[id];
2600   - const langEntry = langEntries[id];
2601   -
2602   - if (!langEntry) {
2603   - errors.push(new L10nError(
2604   - '"' + id + '"' + ' not found in ' + lang.code, id, lang));
2605   - entries[id] = sourceEntry;
2606   - continue;
2607   - }
2608   -
2609   - if (!areEntityStructsEqual(sourceEntry, langEntry)) {
2610   - errors.push(new L10nError(
2611   - '"' + id + '"' + ' is malformed in ' + lang.code, id, lang));
2612   - entries[id] = sourceEntry;
2613   - continue;
2614   - }
2615   -
2616   - entries[id] = langEntry;
2617   - }
2618   -
2619   - return [errors, entries];
2620   -}
2621   -
2622   -function resolvesToString(entity) {
2623   - return typeof entity === 'string' || // a simple string
2624   - typeof entity.value === 'string' || // a simple string, entity with attrs
2625   - Array.isArray(entity.value) || // a complex string
2626   - typeof entity.value === 'object' && // a dict with an index
2627   - entity.index !== null;
2628   -}
2629   -
2630   -function areAttrsEqual(attrs1, attrs2) {
2631   - const keys1 = Object.keys(attrs1 || Object.create(null));
2632   - const keys2 = Object.keys(attrs2 || Object.create(null));
2633   -
2634   - if (keys1.length !== keys2.length) {
2635   - return false;
2636   - }
2637   -
2638   - for (let i = 0; i < keys1.length; i++) {
2639   - if (keys2.indexOf(keys1[i]) === -1) {
2640   - return false;
2641   - }
2642   - }
2643   -
2644   - return true;
2645   -}
2646   -
2647   -function areEntityStructsEqual(source, translation) {
2648   - if (resolvesToString(source) && !resolvesToString(translation)) {
2649   - return false;
2650   - }
2651   -
2652   - if (source.attrs || translation.attrs) {
2653   - return areAttrsEqual(source.attrs, translation.attrs);
2654   - }
2655   -
2656   - return true;
2657   -}
2658   -
2659   -function serializeLegacyContext(ctx, lang) {
2660   - const cache = ctx._env._resCache;
2661   - const resIds = Array.from(ctx._env._resLists.get(ctx));
2662   - return resIds.reduce(([errorsSeq, entriesSeq], cur) => {
2663   - const sourceRes = cache.get(cur + 'en-USapp');
2664   - const langRes = cache.get(cur + lang.code + lang.src);
2665   - const [errors, entries] = serializeEntries$1(
2666   - lang,
2667   - langRes instanceof L10nError ? {} : langRes,
2668   - sourceRes instanceof L10nError ? {} : sourceRes);
2669   - return [errorsSeq.concat(errors), entriesSeq.concat(entries)];
2670   - }, [[], []]);
2671   -}
2672   -
2673   -function serializeEntries$1(lang, langEntries, sourceEntries) {
2674   - const errors = [];
2675   - const entries = Object.keys(sourceEntries).map(id => {
2676   - const sourceEntry = sourceEntries[id];
2677   - const langEntry = langEntries[id];
2678   -
2679   - if (!langEntry) {
2680   - errors.push(new L10nError(
2681   - '"' + id + '"' + ' not found in ' + lang.code, id, lang));
2682   - return serializeEntry(sourceEntry, id);
2683   - }
2684   -
2685   - if (!areEntityStructsEqual$1(sourceEntry, langEntry)) {
2686   - errors.push(new L10nError(
2687   - '"' + id + '"' + ' is malformed in ' + lang.code, id, lang));
2688   - return serializeEntry(sourceEntry, id);
2689   - }
2690   -
2691   - return serializeEntry(langEntry, id);
2692   - });
2693   -
2694   - return [errors, entries];
2695   -}
2696   -
2697   -function serializeEntry(entry, id) {
2698   - if (typeof entry === 'string') {
2699   - return { $i: id, $v: entry };
2700   - }
2701   -
2702   - const node = {
2703   - $i: id,
2704   - };
2705   -
2706   - if (entry.value !== null) {
2707   - node.$v = entry.value;
2708   - }
2709   -
2710   - if (entry.index !== null) {
2711   - node.$x = entry.index;
2712   - }
2713   -
2714   - for (let key in entry.attrs) {
2715   - node[key] = serializeAttribute(entry.attrs[key]);
2716   - }
2717   -
2718   - return node;
2719   -}
2720   -
2721   -function serializeAttribute(attr) {
2722   - if (typeof attr === 'string') {
2723   - return attr;
2724   - }
2725   -
2726   - const node = {};
2727   -
2728   - if (attr.value !== null) {
2729   - node.$v = attr.value;
2730   - }
2731   -
2732   - if (attr.index !== null) {
2733   - node.$x = attr.index;
2734   - }
2735   -
2736   - return node;
2737   -}
2738   -
2739   -function resolvesToString$1(entity) {
2740   - return typeof entity === 'string' || // a simple string
2741   - typeof entity.value === 'string' || // a simple string, entity with attrs
2742   - Array.isArray(entity.value) || // a complex string
2743   - typeof entity.value === 'object' && // a dict with an index
2744   - entity.index !== null;
2745   -}
2746   -
2747   -function areAttrsEqual$1(attrs1, attrs2) {
2748   - const keys1 = Object.keys(attrs1 || Object.create(null));
2749   - const keys2 = Object.keys(attrs2 || Object.create(null));
2750   -
2751   - if (keys1.length !== keys2.length) {
2752   - return false;
2753   - }
2754   -
2755   - for (let i = 0; i < keys1.length; i++) {
2756   - if (keys2.indexOf(keys1[i]) === -1) {
2757   - return false;
2758   - }
2759   - }
2760   -
2761   - return true;
2762   -}
2763   -
2764   -function areEntityStructsEqual$1(source, translation) {
2765   - if (resolvesToString$1(source) && !resolvesToString$1(translation)) {
2766   - return false;
2767   - }
2768   -
2769   - if (source.attrs || translation.attrs) {
2770   - return areAttrsEqual$1(source.attrs, translation.attrs);
2771   - }
2772   -
2773   - return true;
2774   -}
2775   -
2776   -class View {
2777   - constructor(htmloptimizer, fetchResource) {
2778   - this.htmloptimizer = htmloptimizer;
2779   - this.doc = htmloptimizer.document;
2780   -
2781   - this.isEnabled = this.doc.querySelector('link[rel="localization"]');
2782   - // XXX we should check if the app uses l10n.js instead, but due to lazy
2783   - // loading we can't rely on querySelector.
2784   - this.isLegacy = !this.doc.querySelector('script[src*="l20n"]');
2785   -
2786   - const EnvClass = this.isLegacy ? LegacyEnv : Env;
2787   - this.env = new EnvClass(
2788   - htmloptimizer.config.GAIA_DEFAULT_LOCALE, fetchResource);
2789   - this.ctx = this.env.createContext(getResourceLinks(this.doc.head));
2790   -
2791   - // add the url of the currently processed webapp to all errors
2792   - this.env.addEventListener('*', amendError.bind(this));
2793   -
2794   - this.stopBuildError = null;
2795   - const log = logError.bind(this);
2796   - const stop = stopBuild.bind(this);
2797   -
2798   - // stop the build if these errors happen for en-US
2799   - // XXX tv_apps break the build https://bugzil.la/1179833
2800   - // this.env.addEventListener('fetcherror', stop);
2801   - this.env.addEventListener('parseerror', stop);
2802   - this.env.addEventListener('duplicateerror', stop);
2803   - this.env.addEventListener('notfounderror', stop);
2804   - // XXX sms breaks the build https://bugzil.la/1178187
2805   - // this.env.addEventListener('resolveerror', stop);
2806   -
2807   - this.env.addEventListener('deprecatewarning', log);
2808   -
2809   - // if LOCALE_BASEDIR is set alert about missing strings
2810   - if (htmloptimizer.config.LOCALE_BASEDIR !== '') {
2811   - this.env.addEventListener('fetcherror', log);
2812   - this.env.addEventListener('parseerror', log);
2813   - this.env.addEventListener('duplicateerror', log);
2814   - }
2815   - }
2816   -
2817   - _observe() {}
2818   - _disconnect() {}
2819   -
2820   - _resolveEntities(langs, keys) {
2821   - return this.ctx.resolveEntities(langs, keys);
2822   - }
2823   -
2824   - translateDocument(code) {
2825   - const dir = getDirection(code);
2826   - const langs = [{ code, src: 'app' }];
2827   - const setDocLang = () => {
2828   - this.doc.documentElement.lang = code;
2829   - this.doc.documentElement.dir = dir;
2830   - };
2831   - return this.ctx.fetch(langs).then(
2832   - langs => translateFragment(this, langs, this.doc.documentElement)).then(
2833   - setDocLang);
2834   - }
2835   -
2836   - serializeResources(code) {
2837   - const lang = {
2838   - code,
2839   - src: code in pseudo$1 ? 'pseudo' : 'app'
2840   - };
2841   - return fetchContext(this.ctx, lang).then(() => {
2842   - const [errors, entries] = this.isLegacy ?
2843   - serializeLegacyContext(this.ctx, lang) :
2844   - serializeContext(this.ctx, lang);
2845   -
2846   - if (errors.length) {
2847   - const notFoundErrors = errors.filter(
2848   - err => err.message.indexOf('not found') > -1).map(
2849   - err => err.id);
2850   - const malformedErrors = errors.filter(
2851   - err => err.message.indexOf('malformed') > -1).map(
2852   - err => err.id);
2853   -
2854   - if (notFoundErrors.length) {
2855   - this.htmloptimizer.dump(
2856   - '[l10n] [' + lang.code + ']: ' + notFoundErrors.length +
2857   - ' missing compared to en-US: ' + notFoundErrors.join(', '));
2858   - }
2859   - if (malformedErrors.length) {
2860   - this.htmloptimizer.dump(
2861   - '[l10n] [' + lang.code + ']: ' + malformedErrors.length +
2862   - ' malformed compared to en-US: ' + malformedErrors.join(', '));
2863   - }
2864   - }
2865   -
2866   - return entries;
2867   - });
2868   - }
2869   -
2870   - checkError() {
2871   - return {
2872   - wait: false,
2873   - error: this.stopBuildError
2874   - };
2875   - }
2876   -}
2877   -
2878   -function amendError(err) {
2879   - err.message = err.message + ' (' + this.htmloptimizer.webapp.url + ')';
2880   -}
2881   -
2882   -function logError(err) {
2883   - this.htmloptimizer.dump('[l10n] ' + err);
2884   -}
2885   -
2886   -function stopBuild(err) {
2887   - if (err.lang && err.lang.code === 'en-US' && !this.stopBuildError) {
2888   - this.stopBuildError = err;
2889   - }
2890   -}
2891   -
2892   -function fetchContext(ctx, lang) {
2893   - const sourceLang = { code: 'en-US', src: 'app' };
2894   - return Promise.all(
2895   - [sourceLang, lang].map(lang => ctx.fetch([lang])));
2896   -}
2897   -
2898   -function getView(htmloptimizer) {
2899   - const htmlFetch = (...args) => fetchResource(htmloptimizer, ...args);
2900   - return new View(htmloptimizer, htmlFetch);
2901   -}
2902   -
2903   -exports.getView = getView;
2904   -exports.pseudo = pseudo$1;
2905   -exports.walkValue = walkValue$1;
2906 0 \ No newline at end of file
bower_components/l20n/dist/bundle/gaia/l20n.js deleted
1   -(function () { 'use strict';
2   -
3   - function emit(listeners, ...args) {
4   - const type = args.shift();
5   -
6   - if (listeners['*']) {
7   - listeners['*'].slice().forEach(
8   - listener => listener.apply(this, args));
9   - }
10   -
11   - if (listeners[type]) {
12   - listeners[type].slice().forEach(
13   - listener => listener.apply(this, args));
14   - }
15   - }
16   -
17   - function addEventListener(listeners, type, listener) {
18   - if (!(type in listeners)) {
19   - listeners[type] = [];
20   - }
21   - listeners[type].push(listener);
22   - }
23   -
24   - function removeEventListener(listeners, type, listener) {
25   - const typeListeners = listeners[type];
26   - const pos = typeListeners.indexOf(listener);
27   - if (pos === -1) {
28   - return;
29   - }
30   -
31   - typeListeners.splice(pos, 1);
32   - }
33   -
34   - class Client {
35   - constructor(remote) {
36   - this.id = this;
37   - this.remote = remote;
38   -
39   - const listeners = {};
40   - this.on = (...args) => addEventListener(listeners, ...args);
41   - this.emit = (...args) => emit(listeners, ...args);
42   - }
43   -
44   - method(name, ...args) {
45   - return this.remote[name](...args);
46   - }
47   - }
48   -
49   - function broadcast(type, data) {
50   - Array.from(this.ctxs.keys()).forEach(
51   - client => client.emit(type, data));
52   - }
53   -
54   - function L10nError(message, id, lang) {
55   - this.name = 'L10nError';
56   - this.message = message;
57   - this.id = id;
58   - this.lang = lang;
59   - }
60   - L10nError.prototype = Object.create(Error.prototype);
61   - L10nError.prototype.constructor = L10nError;
62   -
63   - function load(type, url) {
64   - return new Promise(function(resolve, reject) {
65   - const xhr = new XMLHttpRequest();
66   -
67   - if (xhr.overrideMimeType) {
68   - xhr.overrideMimeType(type);
69   - }
70   -
71   - xhr.open('GET', url, true);
72   -
73   - if (type === 'application/json') {
74   - xhr.responseType = 'json';
75   - }
76   -
77   - xhr.addEventListener('load', function io_onload(e) {
78   - if (e.target.status === 200 || e.target.status === 0) {
79   - // Sinon.JS's FakeXHR doesn't have the response property
80   - resolve(e.target.response || e.target.responseText);
81   - } else {
82   - reject(new L10nError('Not found: ' + url));
83   - }
84   - });
85   - xhr.addEventListener('error', reject);
86   - xhr.addEventListener('timeout', reject);
87   -
88   - // the app: protocol throws on 404, see https://bugzil.la/827243
89   - try {
90   - xhr.send(null);
91   - } catch (e) {
92   - if (e.name === 'NS_ERROR_FILE_NOT_FOUND') {
93   - // the app: protocol throws on 404, see https://bugzil.la/827243
94   - reject(new L10nError('Not found: ' + url));
95   - } else {
96   - throw e;
97   - }
98   - }
99   - });
100   - }
101   -
102   - const io = {
103   - extra: function(code, ver, path, type) {
104   - return navigator.mozApps.getLocalizationResource(
105   - code, ver, path, type);
106   - },
107   - app: function(code, ver, path, type) {
108   - switch (type) {
109   - case 'text':
110   - return load('text/plain', path);
111   - case 'json':
112   - return load('application/json', path);
113   - default:
114   - throw new L10nError('Unknown file type: ' + type);
115   - }
116   - },
117   - };
118   -
119   - function fetchResource(ver, res, lang) {
120   - const url = res.replace('{locale}', lang.code);
121   - const type = res.endsWith('.json') ? 'json' : 'text';
122   - return io[lang.src](lang.code, ver, url, type);
123   - }
124   -
125   - const MAX_PLACEABLES$1 = 100;
126   -
127   - var L20nParser = {
128   - parse: function(emit, string) {
129   - this._source = string;
130   - this._index = 0;
131   - this._length = string.length;
132   - this.entries = Object.create(null);
133   - this.emit = emit;
134   -
135   - return this.getResource();
136   - },
137   -
138   - getResource: function() {
139   - this.getWS();
140   - while (this._index < this._length) {
141   - try {
142   - this.getEntry();
143   - } catch (e) {
144   - if (e instanceof L10nError) {
145   - // we want to recover, but we don't need it in entries
146   - this.getJunkEntry();
147   - if (!this.emit) {
148   - throw e;
149   - }
150   - } else {
151   - throw e;
152   - }
153   - }
154   -
155   - if (this._index < this._length) {
156   - this.getWS();
157   - }
158   - }
159   -
160   - return this.entries;
161   - },
162   -
163   - getEntry: function() {
164   - if (this._source[this._index] === '<') {
165   - ++this._index;
166   - const id = this.getIdentifier();
167   - if (this._source[this._index] === '[') {
168   - ++this._index;
169   - return this.getEntity(id, this.getItemList(this.getExpression, ']'));
170   - }
171   - return this.getEntity(id);
172   - }
173   -
174   - if (this._source.startsWith('/*', this._index)) {
175   - return this.getComment();
176   - }
177   -
178   - throw this.error('Invalid entry');
179   - },
180   -
181   - getEntity: function(id, index) {
182   - if (!this.getRequiredWS()) {
183   - throw this.error('Expected white space');
184   - }
185   -
186   - const ch = this._source[this._index];
187   - const value = this.getValue(ch, index === undefined);
188   - let attrs;
189   -
190   - if (value === undefined) {
191   - if (ch === '>') {
192   - throw this.error('Expected ">"');
193   - }
194   - attrs = this.getAttributes();
195   - } else {
196   - const ws1 = this.getRequiredWS();
197   - if (this._source[this._index] !== '>') {
198   - if (!ws1) {
199   - throw this.error('Expected ">"');
200   - }
201   - attrs = this.getAttributes();
202   - }
203   - }
204   -
205   - // skip '>'
206   - ++this._index;
207   -
208   - if (id in this.entries) {
209   - throw this.error('Duplicate entry ID "' + id, 'duplicateerror');
210   - }
211   - if (!attrs && !index && typeof value === 'string') {
212   - this.entries[id] = value;
213   - } else {
214   - this.entries[id] = {
215   - value,
216   - attrs,
217   - index
218   - };
219   - }
220   - },
221   -
222   - getValue: function(ch = this._source[this._index], optional = false) {
223   - switch (ch) {
224   - case '\'':
225   - case '"':
226   - return this.getString(ch, 1);
227   - case '{':
228   - return this.getHash();
229   - }
230   -
231   - if (!optional) {
232   - throw this.error('Unknown value type');
233   - }
234   -
235   - return;
236   - },
237   -
238   - getWS: function() {
239   - let cc = this._source.charCodeAt(this._index);
240   - // space, \n, \t, \r
241   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
242   - cc = this._source.charCodeAt(++this._index);
243   - }
244   - },
245   -
246   - getRequiredWS: function() {
247   - const pos = this._index;
248   - let cc = this._source.charCodeAt(pos);
249   - // space, \n, \t, \r
250   - while (cc === 32 || cc === 10 || cc === 9 || cc === 13) {
251   - cc = this._source.charCodeAt(++this._index);
252   - }
253   - return this._index !== pos;
254   - },
255   -
256   - getIdentifier: function() {
257   - const start = this._index;
258   - let cc = this._source.charCodeAt(this._index);
259   -
260   - if ((cc >= 97 && cc <= 122) || // a-z
261   - (cc >= 65 && cc <= 90) || // A-Z
262   - cc === 95) { // _
263   - cc = this._source.charCodeAt(++this._index);
264   - } else {
265   - throw this.error('Identifier has to start with [a-zA-Z_]');
266   - }
267   -
268   - while ((cc >= 97 && cc <= 122) || // a-z
269   - (cc >= 65 && cc <= 90) || // A-Z
270   - (cc >= 48 && cc <= 57) || // 0-9
271   - cc === 95) { // _
272   - cc = this._source.charCodeAt(++this._index);
273   - }
274   -
275   - return this._source.slice(start, this._index);
276   - },
277   -
278   - getUnicodeChar: function() {
279   - for (let i = 0; i < 4; i++) {
280   - let cc = this._source.charCodeAt(++this._index);
281   - if ((cc > 96 && cc < 103) || // a-f
282   - (cc > 64 && cc < 71) || // A-F
283   - (cc > 47 && cc < 58)) { // 0-9
284   - continue;
285   - }
286   - throw this.error('Illegal unicode escape sequence');
287   - }
288   - this._index++;
289   - return String.fromCharCode(
290   - parseInt(this._source.slice(this._index - 4, this._index), 16));
291   - },
292   -
293   - stringRe: /"|'|{{|\\/g,
294   - getString: function(opchar, opcharLen) {
295   - const body = [];
296   - let placeables = 0;
297   -
298   - this._index += opcharLen;
299   - const start = this._index;
300   -
301   - let bufStart = start;
302   - let buf = '';
303   -
304   - while (true) {
305   - this.stringRe.lastIndex = this._index;
306   - const match = this.stringRe.exec(this._source);
307   -
308   - if (!match) {
309   - throw this.error('Unclosed string literal');
310   - }
311   -
312   - if (match[0] === '"' || match[0] === '\'') {
313   - if (match[0] !== opchar) {
314   - this._index += opcharLen;
315   - continue;
316   - }
317   - this._index = match.index + opcharLen;
318   - break;
319   - }
320   -
321   - if (match[0] === '{{') {
322   - if (placeables > MAX_PLACEABLES$1 - 1) {
323   - throw this.error('Too many placeables, maximum allowed is ' +
324   - MAX_PLACEABLES$1);
325   - }
326   - placeables++;
327   - if (match.index > bufStart || buf.length > 0) {
328   - body.push(buf + this._source.slice(bufStart, match.index));
329   - buf = '';
330   - }
331   - this._index = match.index + 2;
332   - this.getWS();
333   - body.push(this.getExpression());
334   - this.getWS();
335   - this._index += 2;
336   - bufStart = this._index;
337   - continue;
338   - }
339   -
340   - if (match[0] === '\\') {
341   - this._index = match.index + 1;
342   - const ch2 = this._source[this._index];
343   - if (ch2 === 'u') {
344   - buf += this._source.slice(bufStart, match.index) +
345   - this.getUnicodeChar();
346   - } else if (ch2 === opchar || ch2 === '\\') {
347   - buf += this._source.slice(bufStart, match.index) + ch2;
348   - this._index++;
349   - } else if (this._source.startsWith('{{', this._index)) {
350   - buf += this._source.slice(bufStart, match.index) + '{{';
351   - this._index += 2;
352   - } else {
353   - throw this.error('Illegal escape sequence');
354   - }
355   - bufStart = this._index;
356   - }
357   - }
358   -
359   - if (body.length === 0) {
360   - return buf + this._source.slice(bufStart, this._index - opcharLen);
361   - }
362   -
363   - if (this._index - opcharLen > bufStart || buf.length > 0) {
364   - body.push(buf + this._source.slice(bufStart, this._index - opcharLen));
365   - }
366   -
367   - return body;
368   - },
369   -
370   - getAttributes: function() {
371   - const attrs = Object.create(null);
372   -
373   - while (true) {
374   - this.getAttribute(attrs);
375   - const ws1 = this.getRequiredWS();
376   - const ch = this._source.charAt(this._index);
377   - if (ch === '>') {
378   - break;
379   - } else if (!ws1) {
380   - throw this.error('Expected ">"');
381   - }
382   - }
383   - return attrs;
384   - },
385   -
386   - getAttribute: function(attrs) {
387   - const key = this.getIdentifier();
388   - let index;
389   -
390   - if (this._source[this._index]=== '[') {
391   - ++this._index;
392   - this.getWS();
393   - index = this.getItemList(this.getExpression, ']');
394   - }
395   - this.getWS();
396   - if (this._source[this._index] !== ':') {
397   - throw this.error('Expected ":"');
398   - }
399   - ++this._index;
400   - this.getWS();
401   - const value = this.getValue();
402   -
403   - if (key in attrs) {
404   - throw this.error('Duplicate attribute "' + key, 'duplicateerror');
405   - }
406   -
407   - if (!index && typeof value === 'string') {
408   - attrs[key] = value;
409   - } else {
410   - attrs[key] = {
411   - value,
412   - index
413   - };
414   - }
415   - },
416   -
417   - getHash: function() {
418   - const items = Object.create(null);
419   -
420   - ++this._index;
421   - this.getWS();
422   -
423   - let defKey;
424   -
425   - while (true) {
426   - const [key, value, def] = this.getHashItem();
427   - items[key] = value;
428   -
429   - if (def) {
430   - if (defKey) {
431   - throw this.error('Default item redefinition forbidden');
432   - }
433   - defKey = key;
434   - }
435   - this.getWS();
436   -
437   - const comma = this._source[this._index] === ',';
438   - if (comma) {
439   - ++this._index;
440   - this.getWS();
441   - }
442   - if (this._source[this._index] === '}') {
443   - ++this._index;
444   - break;
445   - }
446   - if (!comma) {
447   - throw this.error('Expected "}"');
448   - }
449   - }
450   -
451   - if (defKey) {
452   - items.__default = defKey;
453   - }
454   -
455   - return items;
456   - },
457   -
458   - getHashItem: function() {
459   - let defItem = false;
460   - if (this._source[this._index] === '*') {
461   - ++this._index;
462   - defItem = true;
463   - }
464   -
465   - const key = this.getIdentifier();
466   - this.getWS();
467   - if (this._source[this._index] !== ':') {
468   - throw this.error('Expected ":"');
469   - }
470   - ++this._index;
471   - this.getWS();
472   -
473   - return [key, this.getValue(), defItem];
474   - },
475   -
476   - getComment: function() {
477   - this._index += 2;
478   - const start = this._index;
479   - const end = this._source.indexOf('*/', start);
480   -
481   - if (end === -1) {
482   - throw this.error('Comment without a closing tag');
483   - }
484   -
485   - this._index = end + 2;
486   - },
487   -
488   - getExpression: function () {
489   - let exp = this.getPrimaryExpression();
490   -
491   - while (true) {
492   - let ch = this._source[this._index];
493   - if (ch === '.' || ch === '[') {
494   - ++this._index;
495   - exp = this.getPropertyExpression(exp, ch === '[');
496   - } else if (ch === '(') {
497   - ++this._index;
498   - exp = this.getCallExpression(exp);
499   - } else {
500   - break;
501   - }
502   - }
503   -
504   - return exp;
505   - },
506   -
507   - getPropertyExpression: function(idref, computed) {
508   - let exp;
509   -
510   - if (computed) {
511   - this.getWS();
512   - exp = this.getExpression();
513   - this.getWS();
514   - if (this._source[this._index] !== ']') {
515   - throw this.error('Expected "]"');
516   - }
517   - ++this._index;
518   - } else {
519   - exp = this.getIdentifier();
520   - }
521   -
522   - return {
523   - type: 'prop',
524   - expr: idref,
525   - prop: exp,
526   - cmpt: computed
527   - };
528   - },
529   -
530   - getCallExpression: function(callee) {
531   - this.getWS();
532   -
533   - return {
534   - type: 'call',
535   - expr: callee,
536   - args: this.getItemList(this.getExpression, ')')
537   - };
538   - },
539   -
540   - getPrimaryExpression: function() {
541   - const ch = this._source[this._index];
542   -
543   - switch (ch) {
544   - case '$':
545   - ++this._index;
546   - return {
547   - type: 'var',
548   - name: this.getIdentifier()
549   - };
550   - case '@':
551   - ++this._index;
552   - return {
553   - type: 'glob',
554   - name: this.getIdentifier()
555   - };
556   - default:
557   - return {
558   - type: 'id',
559   - name: this.getIdentifier()
560   - };
561   - }
562   - },
563   -
564   - getItemList: function(callback, closeChar) {
565   - const items = [];
566   - let closed = false;
567   -
568   - this.getWS();
569   -
570   - if (this._source[this._index] === closeChar) {
571   - ++this._index;
572   - closed = true;
573   - }
574   -
575   - while (!closed) {
576   - items.push(callback.call(this));
577   - this.getWS();
578   - let ch = this._source.charAt(this._index);
579   - switch (ch) {
580   - case ',':
581   - ++this._index;
582   - this.getWS();
583   - break;
584   - case closeChar:
585   - ++this._index;
586   - closed = true;
587   - break;
588   - default:
589   - throw this.error('Expected "," or "' + closeChar + '"');
590   - }
591   - }
592   -
593   - return items;
594   - },
595   -
596   -
597   - getJunkEntry: function() {
598   - const pos = this._index;
599   - let nextEntity = this._source.indexOf('<', pos);
600   - let nextComment = this._source.indexOf('/*', pos);
601   -
602   - if (nextEntity === -1) {
603   - nextEntity = this._length;
604   - }
605   - if (nextComment === -1) {
606   - nextComment = this._length;
607   - }
608   -
609   - let nextEntry = Math.min(nextEntity, nextComment);
610   -
611   - this._index = nextEntry;
612   - },
613   -
614   - error: function(message, type = 'parsererror') {
615   - const pos = this._index;
616   -
617   - let start = this._source.lastIndexOf('<', pos - 1);
618   - const lastClose = this._source.lastIndexOf('>', pos - 1);
619   - start = lastClose > start ? lastClose + 1 : start;
620   - const context = this._source.slice(start, pos + 10);
621   -
622   - const msg = message + ' at pos ' + pos + ': `' + context + '`';
623   - const err = new L10nError(msg);
624   - if (this.emit) {
625   - this.emit(type, err);
626   - }
627   - return err;
628   - },
629   - };
630   -
631   - var MAX_PLACEABLES = 100;
632   -
633   - var PropertiesParser = {
634   - patterns: null,
635   - entryIds: null,
636   - emit: null,
637   -
638   - init: function() {
639   - this.patterns = {
640   - comment: /^\s*#|^\s*$/,
641   - entity: /^([^=\s]+)\s*=\s*(.*)$/,
642   - multiline: /[^\\]\\$/,
643   - index: /\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,
644   - unicode: /\\u([0-9a-fA-F]{1,4})/g,
645   - entries: /[^\r\n]+/g,
646   - controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g,
647   - placeables: /\{\{\s*([^\s]*?)\s*\}\}/,
648   - };
649   - },
650   -
651   - parse: function(emit, source) {
652   - if (!this.patterns) {
653   - this.init();
654   - }
655   - this.emit = emit;
656   -
657   - var entries = {};
658   -
659   - var lines = source.match(this.patterns.entries);
660   - if (!lines) {
661   - return entries;
662   - }
663   - for (var i = 0; i < lines.length; i++) {
664   - var line = lines[i];
665   -
666   - if (this.patterns.comment.test(line)) {
667   - continue;
668   - }
669   -
670   - while (this.patterns.multiline.test(line) && i < lines.length) {
671   - line = line.slice(0, -1) + lines[++i].trim();
672   - }
673   -
674   - var entityMatch = line.match(this.patterns.entity);
675   - if (entityMatch) {
676   - try {
677   - this.parseEntity(entityMatch[1], entityMatch[2], entries);
678   - } catch (e) {
679   - if (!this.emit) {
680   - throw e;
681   - }
682   - }
683   - }
684   - }
685   - return entries;
686   - },
687   -
688   - parseEntity: function(id, value, entries) {
689   - var name, key;
690   -
691   - var pos = id.indexOf('[');
692   - if (pos !== -1) {
693   - name = id.substr(0, pos);
694   - key = id.substring(pos + 1, id.length - 1);
695   - } else {
696   - name = id;
697   - key = null;
698   - }
699   -
700   - var nameElements = name.split('.');
701   -
702   - if (nameElements.length > 2) {
703   - throw this.error('Error in ID: "' + name + '".' +
704   - ' Nested attributes are not supported.');
705   - }
706   -
707   - var attr;
708   - if (nameElements.length > 1) {
709   - name = nameElements[0];
710   - attr = nameElements[1];
711   -
712   - if (attr[0] === '$') {
713   - throw this.error('Attribute can\'t start with "$"');
714   - }
715   - } else {
716   - attr = null;
717   - }
718   -
719   - this.setEntityValue(name, attr, key, this.unescapeString(value), entries);
720   - },
721   -
722   - setEntityValue: function(id, attr, key, rawValue, entries) {
723   - var value = rawValue.indexOf('{{') > -1 ?
724   - this.parseString(rawValue) : rawValue;
725   -
726   - var isSimpleValue = typeof value === 'string';
727   - var root = entries;
728   -
729   - var isSimpleNode = typeof entries[id] === 'string';
730   -
731   - if (!entries[id] && (attr || key || !isSimpleValue)) {
732   - entries[id] = Object.create(null);
733   - isSimpleNode = false;
734   - }
735   -
736   - if (attr) {
737   - if (isSimpleNode) {
738   - const val = entries[id];
739   - entries[id] = Object.create(null);
740   - entries[id].value = val;
741   - }
742   - if (!entries[id].attrs) {
743   - entries[id].attrs = Object.create(null);
744   - }
745   - if (!entries[id].attrs && !isSimpleValue) {
746   - entries[id].attrs[attr] = Object.create(null);
747   - }
748   - root = entries[id].attrs;
749   - id = attr;
750   - }
751   -
752   - if (key) {
753   - isSimpleNode = false;
754   - if (typeof root[id] === 'string') {
755   - const val = root[id];
756   - root[id] = Object.create(null);
757   - root[id].index = this.parseIndex(val);
758   - root[id].value = Object.create(null);
759   - }
760   - root = root[id].value;
761   - id = key;
762   - isSimpleValue = true;
763   - }
764   -
765   - if (isSimpleValue && (!entries[id] || isSimpleNode)) {
766   - if (id in root) {
767   - throw this.error();
768   - }
769   - root[id] = value;
770   - } else {
771   - if (!root[id]) {
772   - root[id] = Object.create(null);
773   - }
774   - root[id].value = value;
775   - }
776   - },
777   -
778   - parseString: function(str) {
779   - var chunks = str.split(this.patterns.placeables);
780   - var complexStr = [];
781   -
782   - var len = chunks.length;
783   - var placeablesCount = (len - 1) / 2;
784   -
785   - if (placeablesCount >= MAX_PLACEABLES) {
786   - throw this.error('Too many placeables (' + placeablesCount +
787   - ', max allowed is ' + MAX_PLACEABLES + ')');
788   - }
789   -
790   - for (var i = 0; i < chunks.length; i++) {
791   - if (chunks[i].length === 0) {
792   - continue;
793   - }
794   - if (i % 2 === 1) {
795   - complexStr.push({type: 'idOrVar', name: chunks[i]});
796   - } else {
797   - complexStr.push(chunks[i]);
798   - }
799   - }
800   - return complexStr;
801   - },
802   -
803   - unescapeString: function(str) {
804   - if (str.lastIndexOf('\\') !== -1) {
805   - str = str.replace(this.patterns.controlChars, '$1');
806   - }
807   - return str.replace(this.patterns.unicode, function(match, token) {
808   - return String.fromCodePoint(parseInt(token, 16));
809   - });
810   - },
811   -
812   - parseIndex: function(str) {
813   - var match = str.match(this.patterns.index);
814   - if (!match) {
815   - throw new L10nError('Malformed index');
816   - }
817   - if (match[2]) {
818   - return [{
819   - type: 'call',
820   - expr: {
821   - type: 'prop',
822   - expr: {
823   - type: 'glob',
824   - name: 'cldr'
825   - },
826   - prop: 'plural',
827   - cmpt: false
828   - }, args: [{
829   - type: 'idOrVar',
830   - name: match[2]
831   - }]
832   - }];
833   - } else {
834   - return [{type: 'idOrVar', name: match[1]}];
835   - }
836   - },
837   -
838   - error: function(msg, type = 'parsererror') {
839   - const err = new L10nError(msg);
840   - if (this.emit) {
841   - this.emit(type, err);
842   - }
843   - return err;
844   - }
845   - };
846   -
847   - const KNOWN_MACROS = ['plural'];
848   - const MAX_PLACEABLE_LENGTH = 2500;
849   -
850   - // Unicode bidi isolation characters
851   - const FSI = '\u2068';
852   - const PDI = '\u2069';
853   -
854   - const resolutionChain = new WeakSet();
855   -
856   - function format(ctx, lang, args, entity) {
857   - if (typeof entity === 'string') {
858   - return [{}, entity];
859   - }
860   -
861   - if (resolutionChain.has(entity)) {
862   - throw new L10nError('Cyclic reference detected');
863   - }
864   -
865   - resolutionChain.add(entity);
866   -
867   - let rv;
868   - // if format fails, we want the exception to bubble up and stop the whole
869   - // resolving process; however, we still need to remove the entity from the
870   - // resolution chain
871   - try {
872   - rv = resolveValue(
873   - {}, ctx, lang, args, entity.value, entity.index);
874   - } finally {
875   - resolutionChain.delete(entity);
876   - }
877   - return rv;
878   - }
879   -
880   - function resolveIdentifier(ctx, lang, args, id) {
881   - if (KNOWN_MACROS.indexOf(id) > -1) {
882   - return [{}, ctx._getMacro(lang, id)];
883   - }
884   -
885   - if (args && args.hasOwnProperty(id)) {
886   - if (typeof args[id] === 'string' || (typeof args[id] === 'number' &&
887   - !isNaN(args[id]))) {
888   - return [{}, args[id]];
889   - } else {
890   - throw new L10nError('Arg must be a string or a number: ' + id);
891   - }
892   - }
893   -
894   - // XXX: special case for Node.js where still:
895   - // '__proto__' in Object.create(null) => true
896   - if (id === '__proto__') {
897   - throw new L10nError('Illegal id: ' + id);
898   - }
899   -
900   - const entity = ctx._getEntity(lang, id);
901   -
902   - if (entity) {
903   - return format(ctx, lang, args, entity);
904   - }
905   -
906   - throw new L10nError('Unknown reference: ' + id);
907   - }
908   -
909   - function subPlaceable(locals, ctx, lang, args, id) {
910   - let newLocals, value;
911   -
912   - try {
913   - [newLocals, value] = resolveIdentifier(ctx, lang, args, id);
914   - } catch (err) {
915   - return [{ error: err }, FSI + '{{ ' + id + ' }}' + PDI];
916   - }
917   -
918   - if (typeof value === 'number') {
919   - const formatter = ctx._getNumberFormatter(lang);
920   - return [newLocals, formatter.format(value)];
921   - }
922   -
923   - if (typeof value === 'string') {
924   - // prevent Billion Laughs attacks
925   - if (value.length >= MAX_PLACEABLE_LENGTH) {
926   - throw new L10nError('Too many characters in placeable (' +
927   - value.length + ', max allowed is ' +
928   - MAX_PLACEABLE_LENGTH + ')');
929   - }
930   - return [newLocals, FSI + value + PDI];
931   - }
932   -
933   - return [{}, FSI + '{{ ' + id + ' }}' + PDI];
934   - }
935   -
936   - function interpolate(locals, ctx, lang, args, arr) {
937   - return arr.reduce(function([localsSeq, valueSeq], cur) {
938   - if (typeof cur === 'string') {
939   - return [localsSeq, valueSeq + cur];
940   - } else {
941   - const [, value] = subPlaceable(locals, ctx, lang, args, cur.name);
942   - // wrap the substitution in bidi isolate characters
943   - return [localsSeq, valueSeq + value];
944   - }
945   - }, [locals, '']);
946   - }
947   -
948   - function resolveSelector(ctx, lang, args, expr, index) {
949   - //XXX: Dehardcode!!!
950   - let selectorName;
951   - if (index[0].type === 'call' && index[0].expr.type === 'prop' &&
952   - index[0].expr.expr.name === 'cldr') {
953   - selectorName = 'plural';
954   - } else {
955   - selectorName = index[0].name;
956   - }
957   - const selector = resolveIdentifier(ctx, lang, args, selectorName)[1];
958   -
959   - if (typeof selector !== 'function') {
960   - // selector is a simple reference to an entity or args
961   - return selector;
962   - }
963   -
964   - const argValue = index[0].args ?
965   - resolveIdentifier(ctx, lang, args, index[0].args[0].name)[1] : undefined;
966   -
967   - if (selectorName === 'plural') {
968   - // special cases for zero, one, two if they are defined on the hash
969   - if (argValue === 0 && 'zero' in expr) {
970   - return 'zero';
971   - }
972   - if (argValue === 1 && 'one' in expr) {
973   - return 'one';
974   - }
975   - if (argValue === 2 && 'two' in expr) {
976   - return 'two';
977   - }
978   - }
979   -
980   - return selector(argValue);
981   - }
982   -
983   - function resolveValue(locals, ctx, lang, args, expr, index) {
984   - if (!expr) {
985   - return [locals, expr];
986   - }
987   -
988   - if (typeof expr === 'string' ||
989   - typeof expr === 'boolean' ||
990   - typeof expr === 'number') {
991   - return [locals, expr];
992   - }
993   -
994   - if (Array.isArray(expr)) {
995   - return interpolate(locals, ctx, lang, args, expr);
996   - }
997   -
998   - // otherwise, it's a dict
999   - if (index) {
1000   - // try to use the index in order to select the right dict member
1001   - const selector = resolveSelector(ctx, lang, args, expr, index);
1002   - if (selector in expr) {
1003   - return resolveValue(locals, ctx, lang, args, expr[selector]);
1004   - }
1005   - }
1006   -
1007   - // if there was no index or no selector was found, try the default
1008   - // XXX 'other' is an artifact from Gaia
1009   - const defaultKey = expr.__default || 'other';
1010   - if (defaultKey in expr) {
1011   - return resolveValue(locals, ctx, lang, args, expr[defaultKey]);
1012   - }
1013   -
1014   - throw new L10nError('Unresolvable value');
1015   - }
1016   -
1017   - const locales2rules = {
1018   - 'af': 3,
1019   - 'ak': 4,
1020   - 'am': 4,
1021   - 'ar': 1,
1022   - 'asa': 3,
1023   - 'az': 0,
1024   - 'be': 11,
1025   - 'bem': 3,
1026   - 'bez': 3,
1027   - 'bg': 3,
1028   - 'bh': 4,
1029   - 'bm': 0,
1030   - 'bn': 3,
1031   - 'bo': 0,
1032   - 'br': 20,
1033   - 'brx': 3,
1034   - 'bs': 11,
1035   - 'ca': 3,
1036   - 'cgg': 3,
1037   - 'chr': 3,
1038   - 'cs': 12,
1039   - 'cy': 17,
1040   - 'da': 3,
1041   - 'de': 3,
1042   - 'dv': 3,
1043   - 'dz': 0,
1044   - 'ee': 3,
1045   - 'el': 3,
1046   - 'en': 3,
1047   - 'eo': 3,
1048   - 'es': 3,
1049   - 'et': 3,
1050   - 'eu': 3,
1051   - 'fa': 0,
1052   - 'ff': 5,
1053   - 'fi': 3,
1054   - 'fil': 4,
1055   - 'fo': 3,
1056   - 'fr': 5,
1057   - 'fur': 3,
1058   - 'fy': 3,
1059   - 'ga': 8,
1060   - 'gd': 24,
1061   - 'gl': 3,
1062   - 'gsw': 3,
1063   - 'gu': 3,
1064   - 'guw': 4,
1065   - 'gv': 23,
1066   - 'ha': 3,
1067   - 'haw': 3,
1068   - 'he': 2,
1069   - 'hi': 4,
1070   - 'hr': 11,
1071   - 'hu': 0,
1072   - 'id': 0,
1073   - 'ig': 0,
1074   - 'ii': 0,
1075   - 'is': 3,
1076   - 'it': 3,
1077   - 'iu': 7,
1078   - 'ja': 0,
1079   - 'jmc': 3,
1080   - 'jv': 0,
1081   - 'ka': 0,
1082   - 'kab': 5,
1083   - 'kaj': 3,
1084   - 'kcg': 3,
1085   - 'kde': 0,
1086   - 'kea': 0,
1087   - 'kk': 3,
1088   - 'kl': 3,
1089   - 'km': 0,
1090   - 'kn': 0,
1091   - 'ko': 0,
1092   - 'ksb': 3,
1093   - 'ksh': 21,
1094   - 'ku': 3,
1095   - 'kw': 7,
1096   - 'lag': 18,
1097   - 'lb': 3,
1098   - 'lg': 3,
1099   - 'ln': 4,
1100   - 'lo': 0,
1101   - 'lt': 10,
1102   - 'lv': 6,
1103   - 'mas': 3,
1104   - 'mg': 4,
1105   - 'mk': 16,
1106   - 'ml': 3,
1107   - 'mn': 3,
1108   - 'mo': 9,
1109   - 'mr': 3,
1110   - 'ms': 0,
1111   - 'mt': 15,
1112   - 'my': 0,
1113   - 'nah': 3,
1114   - 'naq': 7,
1115   - 'nb': 3,
1116   - 'nd': 3,
1117   - 'ne': 3,
1118   - 'nl': 3,
1119   - 'nn': 3,
1120   - 'no': 3,
1121   - 'nr': 3,
1122   - 'nso': 4,
1123   - 'ny': 3,
1124   - 'nyn': 3,
1125   - 'om': 3,
1126   - 'or': 3,
1127   - 'pa': 3,
1128   - 'pap': 3,
1129   - 'pl': 13,
1130   - 'ps': 3,
1131   - 'pt': 3,
1132   - 'rm': 3,
1133   - 'ro': 9,
1134   - 'rof': 3,
1135   - 'ru': 11,
1136   - 'rwk': 3,
1137   - 'sah': 0,
1138   - 'saq': 3,
1139   - 'se': 7,
1140   - 'seh': 3,
1141   - 'ses': 0,
1142   - 'sg': 0,
1143   - 'sh': 11,
1144   - 'shi': 19,
1145   - 'sk': 12,
1146   - 'sl': 14,
1147   - 'sma': 7,
1148   - 'smi': 7,
1149   - 'smj': 7,
1150   - 'smn': 7,
1151   - 'sms': 7,
1152   - 'sn': 3,
1153   - 'so': 3,
1154   - 'sq': 3,
1155   - 'sr': 11,
1156   - 'ss': 3,
1157   - 'ssy': 3,
1158   - 'st': 3,
1159   - 'sv': 3,
1160   - 'sw': 3,
1161   - 'syr': 3,
1162   - 'ta': 3,
1163   - 'te': 3,
1164   - 'teo': 3,
1165   - 'th': 0,
1166   - 'ti': 4,
1167   - 'tig': 3,
1168   - 'tk': 3,
1169   - 'tl': 4,
1170   - 'tn': 3,
1171   - 'to': 0,
1172   - 'tr': 0,
1173   - 'ts': 3,
1174   - 'tzm': 22,
1175   - 'uk': 11,
1176   - 'ur': 3,
1177   - 've': 3,
1178   - 'vi': 0,
1179   - 'vun': 3,
1180   - 'wa': 4,
1181   - 'wae': 3,
1182   - 'wo': 0,
1183   - 'xh': 3,
1184   - 'xog': 3,
1185   - 'yo': 0,
1186   - 'zh': 0,
1187   - 'zu': 3
1188   - };
1189   -
1190   - // utility functions for plural rules methods
1191   - function isIn(n, list) {
1192   - return list.indexOf(n) !== -1;
1193   - }
1194   - function isBetween(n, start, end) {
1195   - return typeof n === typeof start && start <= n && n <= end;
1196   - }
1197   -
1198   - // list of all plural rules methods:
1199   - // map an integer to the plural form name to use
1200   - const pluralRules = {
1201   - '0': function() {
1202   - return 'other';
1203   - },
1204   - '1': function(n) {
1205   - if ((isBetween((n % 100), 3, 10))) {
1206   - return 'few';
1207   - }
1208   - if (n === 0) {
1209   - return 'zero';
1210   - }
1211   - if ((isBetween((n % 100), 11, 99))) {
1212   - return 'many';
1213   - }
1214   - if (n === 2) {
1215   - return 'two';
1216   - }
1217   - if (n === 1) {
1218   - return 'one';
1219   - }
1220   - return 'other';
1221   - },
1222   - '2': function(n) {
1223   - if (n !== 0 && (n % 10) === 0) {
1224   - return 'many';
1225   - }
1226   - if (n === 2) {
1227   - return 'two';
1228   - }
1229   - if (n === 1) {
1230   - return 'one';
1231   - }
1232   - return 'other';
1233   - },
1234   - '3': function(n) {
1235   - if (n === 1) {
1236   - return 'one';
1237   - }
1238   - return 'other';
1239   - },
1240   - '4': function(n) {
1241   - if ((isBetween(n, 0, 1))) {
1242   - return 'one';
1243   - }
1244   - return 'other';
1245   - },
1246   - '5': function(n) {
1247   - if ((isBetween(n, 0, 2)) && n !== 2) {
1248   - return 'one';
1249   - }
1250   - return 'other';
1251   - },
1252   - '6': function(n) {
1253   - if (n === 0) {
1254   - return 'zero';
1255   - }
1256   - if ((n % 10) === 1 && (n % 100) !== 11) {
1257   - return 'one';
1258   - }
1259   - return 'other';
1260   - },
1261   - '7': function(n) {
1262   - if (n === 2) {
1263   - return 'two';
1264   - }
1265   - if (n === 1) {
1266   - return 'one';
1267   - }
1268   - return 'other';
1269   - },
1270   - '8': function(n) {
1271   - if ((isBetween(n, 3, 6))) {
1272   - return 'few';
1273   - }
1274   - if ((isBetween(n, 7, 10))) {
1275   - return 'many';
1276   - }
1277   - if (n === 2) {
1278   - return 'two';
1279   - }
1280   - if (n === 1) {
1281   - return 'one';
1282   - }
1283   - return 'other';
1284   - },
1285   - '9': function(n) {
1286   - if (n === 0 || n !== 1 && (isBetween((n % 100), 1, 19))) {
1287   - return 'few';
1288   - }
1289   - if (n === 1) {
1290   - return 'one';
1291   - }
1292   - return 'other';
1293   - },
1294   - '10': function(n) {
1295   - if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) {
1296   - return 'few';
1297   - }
1298   - if ((n % 10) === 1 && !(isBetween((n % 100), 11, 19))) {
1299   - return 'one';
1300   - }
1301   - return 'other';
1302   - },
1303   - '11': function(n) {
1304   - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
1305   - return 'few';
1306   - }
1307   - if ((n % 10) === 0 ||
1308   - (isBetween((n % 10), 5, 9)) ||
1309   - (isBetween((n % 100), 11, 14))) {
1310   - return 'many';
1311   - }
1312   - if ((n % 10) === 1 && (n % 100) !== 11) {
1313   - return 'one';
1314   - }
1315   - return 'other';
1316   - },
1317   - '12': function(n) {
1318   - if ((isBetween(n, 2, 4))) {
1319   - return 'few';
1320   - }
1321   - if (n === 1) {
1322   - return 'one';
1323   - }
1324   - return 'other';
1325   - },
1326   - '13': function(n) {
1327   - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
1328   - return 'few';
1329   - }
1330   - if (n !== 1 && (isBetween((n % 10), 0, 1)) ||
1331   - (isBetween((n % 10), 5, 9)) ||
1332   - (isBetween((n % 100), 12, 14))) {
1333   - return 'many';
1334   - }
1335   - if (n === 1) {
1336   - return 'one';
1337   - }
1338   - return 'other';
1339   - },
1340   - '14': function(n) {
1341   - if ((isBetween((n % 100), 3, 4))) {
1342   - return 'few';
1343   - }
1344   - if ((n % 100) === 2) {
1345   - return 'two';
1346   - }
1347   - if ((n % 100) === 1) {
1348   - return 'one';
1349   - }
1350   - return 'other';
1351   - },
1352   - '15': function(n) {
1353   - if (n === 0 || (isBetween((n % 100), 2, 10))) {
1354   - return 'few';
1355   - }
1356   - if ((isBetween((n % 100), 11, 19))) {
1357   - return 'many';
1358   - }
1359   - if (n === 1) {
1360   - return 'one';
1361   - }
1362   - return 'other';
1363   - },
1364   - '16': function(n) {
1365   - if ((n % 10) === 1 && n !== 11) {
1366   - return 'one';
1367   - }
1368   - return 'other';
1369   - },
1370   - '17': function(n) {
1371   - if (n === 3) {
1372   - return 'few';
1373   - }
1374   - if (n === 0) {
1375   - return 'zero';
1376   - }
1377   - if (n === 6) {
1378   - return 'many';
1379   - }
1380   - if (n === 2) {
1381   - return 'two';
1382   - }
1383   - if (n === 1) {
1384   - return 'one';
1385   - }
1386   - return 'other';
1387   - },
1388   - '18': function(n) {
1389   - if (n === 0) {
1390   - return 'zero';
1391   - }
1392   - if ((isBetween(n, 0, 2)) && n !== 0 && n !== 2) {
1393   - return 'one';
1394   - }
1395   - return 'other';
1396   - },
1397   - '19': function(n) {
1398   - if ((isBetween(n, 2, 10))) {
1399   - return 'few';
1400   - }
1401   - if ((isBetween(n, 0, 1))) {
1402   - return 'one';
1403   - }
1404   - return 'other';
1405   - },
1406   - '20': function(n) {
1407   - if ((isBetween((n % 10), 3, 4) || ((n % 10) === 9)) && !(
1408   - isBetween((n % 100), 10, 19) ||
1409   - isBetween((n % 100), 70, 79) ||
1410   - isBetween((n % 100), 90, 99)
1411   - )) {
1412   - return 'few';
1413   - }
1414   - if ((n % 1000000) === 0 && n !== 0) {
1415   - return 'many';
1416   - }
1417   - if ((n % 10) === 2 && !isIn((n % 100), [12, 72, 92])) {
1418   - return 'two';
1419   - }
1420   - if ((n % 10) === 1 && !isIn((n % 100), [11, 71, 91])) {
1421   - return 'one';
1422   - }
1423   - return 'other';
1424   - },
1425   - '21': function(n) {
1426   - if (n === 0) {
1427   - return 'zero';
1428   - }
1429   - if (n === 1) {
1430   - return 'one';
1431   - }
1432   - return 'other';
1433   - },
1434   - '22': function(n) {
1435   - if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) {
1436   - return 'one';
1437   - }
1438   - return 'other';
1439   - },
1440   - '23': function(n) {
1441   - if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) {
1442   - return 'one';
1443   - }
1444   - return 'other';
1445   - },
1446   - '24': function(n) {
1447   - if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) {
1448   - return 'few';
1449   - }
1450   - if (isIn(n, [2, 12])) {
1451   - return 'two';
1452   - }
1453   - if (isIn(n, [1, 11])) {
1454   - return 'one';
1455   - }
1456   - return 'other';
1457   - }
1458   - };
1459   -
1460   - function getPluralRule(code) {
1461   - // return a function that gives the plural form name for a given integer
1462   - const index = locales2rules[code.replace(/-.*$/, '')];
1463   - if (!(index in pluralRules)) {
1464   - return function() { return 'other'; };
1465   - }
1466   - return pluralRules[index];
1467   - }
1468   -
1469   - class Context {
1470   - constructor(env) {
1471   - this._env = env;
1472   - this._numberFormatters = null;
1473   - }
1474   -
1475   - _formatTuple(lang, args, entity, id, key) {
1476   - try {
1477   - return format(this, lang, args, entity);
1478   - } catch (err) {
1479   - err.id = key ? id + '::' + key : id;
1480   - err.lang = lang;
1481   - this._env.emit('resolveerror', err, this);
1482   - return [{ error: err }, err.id];
1483   - }
1484   - }
1485   -
1486   - _formatEntity(lang, args, entity, id) {
1487   - const [, value] = this._formatTuple(lang, args, entity, id);
1488   -
1489   - const formatted = {
1490   - value,
1491   - attrs: null,
1492   - };
1493   -
1494   - if (entity.attrs) {
1495   - formatted.attrs = Object.create(null);
1496   - for (let key in entity.attrs) {
1497   - /* jshint -W089 */
1498   - const [, attrValue] = this._formatTuple(
1499   - lang, args, entity.attrs[key], id, key);
1500   - formatted.attrs[key] = attrValue;
1501   - }
1502   - }
1503   -
1504   - return formatted;
1505   - }
1506   -
1507   - _formatValue(lang, args, entity, id) {
1508   - return this._formatTuple(lang, args, entity, id)[1];
1509   - }
1510   -
1511   - fetch(langs) {
1512   - if (langs.length === 0) {
1513   - return Promise.resolve(langs);
1514   - }
1515   -
1516   - const resIds = Array.from(this._env._resLists.get(this));
1517   -
1518   - return Promise.all(
1519   - resIds.map(
1520   - this._env._getResource.bind(this._env, langs[0]))).then(
1521   - () => langs);
1522   - }
1523   -
1524   - _resolve(langs, keys, formatter, prevResolved) {
1525   - const lang = langs[0];
1526   -
1527   - if (!lang) {
1528   - return reportMissing.call(this, keys, formatter, prevResolved);
1529   - }
1530   -
1531   - let hasUnresolved = false;
1532   -
1533   - const resolved = keys.map((key, i) => {
1534   - if (prevResolved && prevResolved[i] !== undefined) {
1535   - return prevResolved[i];
1536   - }
1537   - const [id, args] = Array.isArray(key) ?
1538   - key : [key, undefined];
1539   - const entity = this._getEntity(lang, id);
1540   -
1541   - if (entity) {
1542   - return formatter.call(this, lang, args, entity, id);
1543   - }
1544   -
1545   - this._env.emit('notfounderror',
1546   - new L10nError('"' + id + '"' + ' not found in ' + lang.code,
1547   - id, lang), this);
1548   - hasUnresolved = true;
1549   - });
1550   -
1551   - if (!hasUnresolved) {
1552   - return resolved;
1553   - }
1554   -
1555   - return this.fetch(langs.slice(1)).then(
1556   - nextLangs => this._resolve(nextLangs, keys, formatter, resolved));
1557   - }
1558   -
1559   - resolveEntities(langs, keys) {
1560   - return this.fetch(langs).then(
1561   - langs => this._resolve(langs, keys, this._formatEntity));
1562   - }
1563   -
1564   - resolveValues(langs, keys) {
1565   - return this.fetch(langs).then(
1566   - langs => this._resolve(langs, keys, this._formatValue));
1567   - }
1568   -
1569   - _getEntity(lang, id) {
1570   - const cache = this._env._resCache;
1571   - const resIds = Array.from(this._env._resLists.get(this));
1572   -
1573   - // Look for `id` in every resource in order.
1574   - for (let i = 0, resId; resId = resIds[i]; i++) {
1575   - const resource = cache.get(resId + lang.code + lang.src);
1576   - if (resource instanceof L10nError) {
1577   - continue;
1578   - }
1579   - if (id in resource) {
1580   - return resource[id];
1581   - }
1582   - }
1583   - return undefined;
1584   - }
1585   -
1586   - _getNumberFormatter(lang) {
1587   - if (!this._numberFormatters) {
1588   - this._numberFormatters = new Map();
1589   - }
1590   - if (!this._numberFormatters.has(lang)) {
1591   - const formatter = Intl.NumberFormat(lang, {
1592   - useGrouping: false,
1593   - });
1594   - this._numberFormatters.set(lang, formatter);
1595   - return formatter;
1596   - }
1597   - return this._numberFormatters.get(lang);
1598   - }
1599   -
1600   - // XXX in the future macros will be stored in localization resources together
1601   - // with regular entities and this method will not be needed anymore
1602   - _getMacro(lang, id) {
1603   - switch(id) {
1604   - case 'plural':
1605   - return getPluralRule(lang.code);
1606   - default:
1607   - return undefined;
1608   - }
1609   - }
1610   -
1611   - }
1612   -
1613   - function reportMissing(keys, formatter, resolved) {
1614   - const missingIds = new Set();
1615   -
1616   - keys.forEach((key, i) => {
1617   - if (resolved && resolved[i] !== undefined) {
1618   - return;
1619   - }
1620   - const id = Array.isArray(key) ? key[0] : key;
1621   - missingIds.add(id);
1622   - resolved[i] = formatter === this._formatValue ?
1623   - id : {value: id, attrs: null};
1624   - });
1625   -
1626   - this._env.emit('notfounderror', new L10nError(
1627   - '"' + Array.from(missingIds).join(', ') + '"' +
1628   - ' not found in any language', missingIds), this);
1629   -
1630   - return resolved;
1631   - }
1632   -
1633   - // Walk an entry node searching for content leaves
1634   - function walkEntry(entry, fn) {
1635   - if (typeof entry === 'string') {
1636   - return fn(entry);
1637   - }
1638   -
1639   - const newEntry = Object.create(null);
1640   -
1641   - if (entry.value) {
1642   - newEntry.value = walkValue(entry.value, fn);
1643   - }
1644   -
1645   - if (entry.index) {
1646   - newEntry.index = entry.index;
1647   - }
1648   -
1649   - if (entry.attrs) {
1650   - newEntry.attrs = Object.create(null);
1651   - for (let key in entry.attrs) {
1652   - newEntry.attrs[key] = walkEntry(entry.attrs[key], fn);
1653   - }
1654   - }
1655   -
1656   - return newEntry;
1657   - }
1658   -
1659   - function walkValue(value, fn) {
1660   - if (typeof value === 'string') {
1661   - return fn(value);
1662   - }
1663   -
1664   - // skip expressions in placeables
1665   - if (value.type) {
1666   - return value;
1667   - }
1668   -
1669   - const newValue = Array.isArray(value) ? [] : Object.create(null);
1670   - const keys = Object.keys(value);
1671   -
1672   - for (let i = 0, key; (key = keys[i]); i++) {
1673   - newValue[key] = walkValue(value[key], fn);
1674   - }
1675   -
1676   - return newValue;
1677   - }
1678   -
1679   - /* Pseudolocalizations
1680   - *
1681   - * pseudo is a dict of strategies to be used to modify the English
1682   - * context in order to create pseudolocalizations. These can be used by
1683   - * developers to test the localizability of their code without having to
1684   - * actually speak a foreign language.
1685   - *
1686   - * Currently, the following pseudolocales are supported:
1687   - *
1688   - * fr-x-psaccent - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
1689   - *
1690   - * In Accented English all English letters are replaced by accented
1691   - * Unicode counterparts which don't impair the readability of the content.
1692   - * This allows developers to quickly test if any given string is being
1693   - * correctly displayed in its 'translated' form. Additionally, simple
1694   - * heuristics are used to make certain words longer to better simulate the
1695   - * experience of international users.
1696   - *
1697   - * ar-x-psbidi - ɥsıʅƃuƎ ıpıԐ
1698   - *
1699   - * Bidi English is a fake RTL locale. All words are surrounded by
1700   - * Unicode formatting marks forcing the RTL directionality of characters.
1701   - * In addition, to make the reversed text easier to read, individual
1702   - * letters are flipped.
1703   - *
1704   - * Note: The name above is hardcoded to be RTL in case code editors have
1705   - * trouble with the RLO and PDF Unicode marks. In reality, it should be
1706   - * surrounded by those marks as well.
1707   - *
1708   - * See https://bugzil.la/900182 for more information.
1709   - *
1710   - */
1711   -
1712   - function createGetter(id, name) {
1713   - let _pseudo = null;
1714   -
1715   - return function getPseudo() {
1716   - if (_pseudo) {
1717   - return _pseudo;
1718   - }
1719   -
1720   - const reAlphas = /[a-zA-Z]/g;
1721   - const reVowels = /[aeiouAEIOU]/g;
1722   - const reWords = /[^\W0-9_]+/g;
1723   - // strftime tokens (%a, %Eb), template {vars}, HTML entities (&#x202a;)
1724   - // and HTML tags.
1725   - const reExcluded = /(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;
1726   -
1727   - const charMaps = {
1728   - 'fr-x-psaccent':
1729   - 'ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ[\\]^_`ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ',
1730   - 'ar-x-psbidi':
1731   - // XXX Use pɟפ˥ʎ as replacements for ᗡℲ⅁⅂⅄. https://bugzil.la/1007340
1732   - '∀ԐↃpƎɟפHIſӼ˥WNOԀÒᴚS⊥∩ɅMXʎZ[\\]ᵥ_,ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz',
1733   - };
1734   -
1735   - const mods = {
1736   - 'fr-x-psaccent': val =>
1737   - val.replace(reVowels, match => match + match.toLowerCase()),
1738   -
1739   - // Surround each word with Unicode formatting codes, RLO and PDF:
1740   - // U+202E: RIGHT-TO-LEFT OVERRIDE (RLO)
1741   - // U+202C: POP DIRECTIONAL FORMATTING (PDF)
1742   - // See http://www.w3.org/International/questions/qa-bidi-controls
1743   - 'ar-x-psbidi': val =>
1744   - val.replace(reWords, match => '\u202e' + match + '\u202c'),
1745   - };
1746   -
1747   - // Replace each Latin letter with a Unicode character from map
1748   - const replaceChars =
1749   - (map, val) => val.replace(
1750   - reAlphas, match => map.charAt(match.charCodeAt(0) - 65));
1751   -
1752   - const transform =
1753   - val => replaceChars(charMaps[id], mods[id](val));
1754   -
1755   - // apply fn to translatable parts of val
1756   - const apply = (fn, val) => {
1757   - if (!val) {
1758   - return val;
1759   - }
1760   -
1761   - const parts = val.split(reExcluded);
1762   - const modified = parts.map(function(part) {
1763   - if (reExcluded.test(part)) {
1764   - return part;
1765   - }
1766   - return fn(part);
1767   - });
1768   - return modified.join('');
1769   - };
1770   -
1771   - return _pseudo = {
1772   - name: transform(name),
1773   - process: str => apply(transform, str)
1774   - };
1775   - };
1776   - }
1777   -
1778   - const pseudo = Object.defineProperties(Object.create(null), {
1779   - 'fr-x-psaccent': {
1780   - enumerable: true,
1781   - get: createGetter('fr-x-psaccent', 'Runtime Accented')
1782   - },
1783   - 'ar-x-psbidi': {
1784   - enumerable: true,
1785   - get: createGetter('ar-x-psbidi', 'Runtime Bidi')
1786   - }
1787   - });
1788   -
1789   - const parsers = {
1790   - properties: PropertiesParser,
1791   - l20n: L20nParser,
1792   - };
1793   -
1794   - class Env {
1795   - constructor(defaultLang, fetchResource) {
1796   - this.defaultLang = defaultLang;
1797   - this.fetchResource = fetchResource;
1798   -
1799   - this._resLists = new Map();
1800   - this._resCache = new Map();
1801   -
1802   - const listeners = {};
1803   - this.emit = emit.bind(this, listeners);
1804   - this.addEventListener = addEventListener.bind(this, listeners);
1805   - this.removeEventListener = removeEventListener.bind(this, listeners);
1806   - }
1807   -
1808   - createContext(resIds) {
1809   - const ctx = new Context(this);
1810   - this._resLists.set(ctx, new Set(resIds));
1811   - return ctx;
1812   - }
1813   -
1814   - destroyContext(ctx) {
1815   - const lists = this._resLists;
1816   - const resList = lists.get(ctx);
1817   -
1818   - lists.delete(ctx);
1819   - resList.forEach(
1820   - resId => deleteIfOrphan(this._resCache, lists, resId));
1821   - }
1822   -
1823   - _parse(syntax, lang, data) {
1824   - const parser = parsers[syntax];
1825   - if (!parser) {
1826   - return data;
1827   - }
1828   -
1829   - const emit = (type, err) => this.emit(type, amendError(lang, err));
1830   - return parser.parse.call(parser, emit, data);
1831   - }
1832   -
1833   - _create(lang, entries) {
1834   - if (lang.src !== 'pseudo') {
1835   - return entries;
1836   - }
1837   -
1838   - const pseudoentries = Object.create(null);
1839   - for (let key in entries) {
1840   - pseudoentries[key] = walkEntry(
1841   - entries[key], pseudo[lang.code].process);
1842   - }
1843   - return pseudoentries;
1844   - }
1845   -
1846   - _getResource(lang, res) {
1847   - const cache = this._resCache;
1848   - const id = res + lang.code + lang.src;
1849   -
1850   - if (cache.has(id)) {
1851   - return cache.get(id);
1852   - }
1853   -
1854   - const syntax = res.substr(res.lastIndexOf('.') + 1);
1855   -
1856   - const saveEntries = data => {
1857   - const entries = this._parse(syntax, lang, data);
1858   - cache.set(id, this._create(lang, entries));
1859   - };
1860   -
1861   - const recover = err => {
1862   - err.lang = lang;
1863   - this.emit('fetcherror', err);
1864   - cache.set(id, err);
1865   - };
1866   -
1867   - const langToFetch = lang.src === 'pseudo' ?
1868   - { code: this.defaultLang, src: 'app' } :
1869   - lang;
1870   -
1871   - const resource = this.fetchResource(res, langToFetch).then(
1872   - saveEntries, recover);
1873   -
1874   - cache.set(id, resource);
1875   -
1876   - return resource;
1877   - }
1878   - }
1879   -
1880   - function deleteIfOrphan(cache, lists, resId) {
1881   - const isNeeded = Array.from(lists).some(
1882   - ([ctx, resIds]) => resIds.has(resId));
1883   -
1884   - if (!isNeeded) {
1885   - cache.forEach((val, key) =>
1886   - key.startsWith(resId) ? cache.delete(key) : null);
1887   - }
1888   - }
1889   -
1890   - function amendError(lang, err) {
1891   - err.lang = lang;
1892   - return err;
1893   - }
1894   -
1895   - // Polyfill NodeList.prototype[Symbol.iterator] for Chrome.
1896   - // See https://code.google.com/p/chromium/issues/detail?id=401699
1897   - if (typeof NodeList === 'function' && !NodeList.prototype[Symbol.iterator]) {
1898   - NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
1899   - }
1900   -
1901   - // A document.ready shim
1902   - // https://github.com/whatwg/html/issues/127
1903   - function documentReady() {
1904   - if (document.readyState !== 'loading') {
1905   - return Promise.resolve();
1906   - }
1907   -
1908   - return new Promise(resolve => {
1909   - document.addEventListener('readystatechange', function onrsc() {
1910   - document.removeEventListener('readystatechange', onrsc);
1911   - resolve();
1912   - });
1913   - });
1914   - }
1915   -
1916   - // Intl.Locale
1917   - function getDirection(code) {
1918   - const tag = code.split('-')[0];
1919   - return ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(tag) >= 0 ?
1920   - 'rtl' : 'ltr';
1921   - }
1922   -
1923   - function prioritizeLocales(def, availableLangs, requested) {
1924   - let supportedLocale;
1925   - // Find the first locale in the requested list that is supported.
1926   - for (let i = 0; i < requested.length; i++) {
1927   - const locale = requested[i];
1928   - if (availableLangs.indexOf(locale) !== -1) {
1929   - supportedLocale = locale;
1930   - break;
1931   - }
1932   - }
1933   - if (!supportedLocale ||
1934   - supportedLocale === def) {
1935   - return [def];
1936   - }
1937   -
1938   - return [supportedLocale, def];
1939   - }
1940   -
1941   - function getMeta(head) {
1942   - let availableLangs = Object.create(null);
1943   - let defaultLang = null;
1944   - let appVersion = null;
1945   -
1946   - // XXX take last found instead of first?
1947   - const metas = head.querySelectorAll(
1948   - 'meta[name="availableLanguages"],' +
1949   - 'meta[name="defaultLanguage"],' +
1950   - 'meta[name="appVersion"]');
1951   - for (let meta of metas) {
1952   - const name = meta.getAttribute('name');
1953   - const content = meta.getAttribute('content').trim();
1954   - switch (name) {
1955   - case 'availableLanguages':
1956   - availableLangs = getLangRevisionMap(
1957   - availableLangs, content);
1958   - break;
1959   - case 'defaultLanguage':
1960   - const [lang, rev] = getLangRevisionTuple(content);
1961   - defaultLang = lang;
1962   - if (!(lang in availableLangs)) {
1963   - availableLangs[lang] = rev;
1964   - }
1965   - break;
1966   - case 'appVersion':
1967   - appVersion = content;
1968   - }
1969   - }
1970   -
1971   - return {
1972   - defaultLang,
1973   - availableLangs,
1974   - appVersion
1975   - };
1976   - }
1977   -
1978   - function getLangRevisionMap(seq, str) {
1979   - return str.split(',').reduce((seq, cur) => {
1980   - const [lang, rev] = getLangRevisionTuple(cur);
1981   - seq[lang] = rev;
1982   - return seq;
1983   - }, seq);
1984   - }
1985   -
1986   - function getLangRevisionTuple(str) {
1987   - const [lang, rev] = str.trim().split(':');
1988   - // if revision is missing, use NaN
1989   - return [lang, parseInt(rev)];
1990   - }
1991   -
1992   - function negotiateLanguages(
1993   - fn, appVersion, defaultLang, availableLangs, additionalLangs, prevLangs,
1994   - requestedLangs) {
1995   -
1996   - const allAvailableLangs = Object.keys(availableLangs).concat(
1997   - additionalLangs || []).concat(Object.keys(pseudo));
1998   - const newLangs = prioritizeLocales(
1999   - defaultLang, allAvailableLangs, requestedLangs);
2000   -
2001   - const langs = newLangs.map(code => ({
2002   - code: code,
2003   - src: getLangSource(appVersion, availableLangs, additionalLangs, code),
2004   - }));
2005   -
2006   - if (!arrEqual(prevLangs, newLangs)) {
2007   - fn(langs);
2008   - }
2009   -
2010   - return langs;
2011   - }
2012   -
2013   - function arrEqual(arr1, arr2) {
2014   - return arr1.length === arr2.length &&
2015   - arr1.every((elem, i) => elem === arr2[i]);
2016   - }
2017   -
2018   - function getMatchingLangpack(appVersion, langpacks) {
2019   - for (let i = 0, langpack; (langpack = langpacks[i]); i++) {
2020   - if (langpack.target === appVersion) {
2021   - return langpack;
2022   - }
2023   - }
2024   - return null;
2025   - }
2026   -
2027   - function getLangSource(appVersion, availableLangs, additionalLangs, code) {
2028   - if (additionalLangs && additionalLangs[code]) {
2029   - const lp = getMatchingLangpack(appVersion, additionalLangs[code]);
2030   - if (lp &&
2031   - (!(code in availableLangs) ||
2032   - parseInt(lp.revision) > availableLangs[code])) {
2033   - return 'extra';
2034   - }
2035   - }
2036   -
2037   - if ((code in pseudo) && !(code in availableLangs)) {
2038   - return 'pseudo';
2039   - }
2040   -
2041   - return 'app';
2042   - }
2043   -
2044   - class Remote {
2045   - constructor(fetchResource, broadcast, requestedLangs) {
2046   - this.fetchResource = fetchResource;
2047   - this.broadcast = broadcast;
2048   - this.ctxs = new Map();
2049   - this.interactive = documentReady().then(
2050   - () => this.init(requestedLangs));
2051   - }
2052   -
2053   - init(requestedLangs) {
2054   - const meta = getMeta(document.head);
2055   - this.defaultLanguage = meta.defaultLang;
2056   - this.availableLanguages = meta.availableLangs;
2057   - this.appVersion = meta.appVersion;
2058   -
2059   - this.env = new Env(
2060   - this.defaultLanguage,
2061   - (...args) => this.fetchResource(this.appVersion, ...args));
2062   -
2063   - return this.requestLanguages(requestedLangs);
2064   - }
2065   -
2066   - registerView(view, resources) {
2067   - return this.interactive.then(() => {
2068   - this.ctxs.set(view, this.env.createContext(resources));
2069   - return true;
2070   - });
2071   - }
2072   -
2073   - unregisterView(view) {
2074   - return this.ctxs.delete(view);
2075   - }
2076   -
2077   - resolveEntities(view, langs, keys) {
2078   - return this.ctxs.get(view).resolveEntities(langs, keys);
2079   - }
2080   -
2081   - formatValues(view, keys) {
2082   - return this.languages.then(
2083   - langs => this.ctxs.get(view).resolveValues(langs, keys));
2084   - }
2085   -
2086   - resolvedLanguages() {
2087   - return this.languages;
2088   - }
2089   -
2090   - requestLanguages(requestedLangs) {
2091   - return changeLanguages.call(
2092   - this, getAdditionalLanguages(), requestedLangs);
2093   - }
2094   -
2095   - getName(code) {
2096   - return pseudo[code].name;
2097   - }
2098   -
2099   - processString(code, str) {
2100   - return pseudo[code].process(str);
2101   - }
2102   -
2103   - handleEvent(evt) {
2104   - return changeLanguages.call(
2105   - this, evt.detail || getAdditionalLanguages(), navigator.languages);
2106   - }
2107   - }
2108   -
2109   - function getAdditionalLanguages() {
2110   - if (navigator.mozApps && navigator.mozApps.getAdditionalLanguages) {
2111   - return navigator.mozApps.getAdditionalLanguages().catch(
2112   - () => []);
2113   - }
2114   -
2115   - return Promise.resolve([]);
2116   - }
2117   -
2118   - function changeLanguages(additionalLangs, requestedLangs) {
2119   - const prevLangs = this.languages || [];
2120   - return this.languages = Promise.all([
2121   - additionalLangs, prevLangs]).then(
2122   - ([additionalLangs, prevLangs]) => negotiateLanguages(
2123   - this.broadcast.bind(this, 'translateDocument'),
2124   - this.appVersion, this.defaultLanguage, this.availableLanguages,
2125   - additionalLangs, prevLangs, requestedLangs));
2126   - }
2127   -
2128   - // match the opening angle bracket (<) in HTML tags, and HTML entities like
2129   - // &amp;, &#0038;, &#x0026;.
2130   - const reOverlay = /<|&#?\w+;/;
2131   -
2132   - const allowed = {
2133   - elements: [
2134   - 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
2135   - 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u',
2136   - 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr'
2137   - ],
2138   - attributes: {
2139   - global: [ 'title', 'aria-label', 'aria-valuetext', 'aria-moz-hint' ],
2140   - a: [ 'download' ],
2141   - area: [ 'download', 'alt' ],
2142   - // value is special-cased in isAttrAllowed
2143   - input: [ 'alt', 'placeholder' ],
2144   - menuitem: [ 'label' ],
2145   - menu: [ 'label' ],
2146   - optgroup: [ 'label' ],
2147   - option: [ 'label' ],
2148   - track: [ 'label' ],
2149   - img: [ 'alt' ],
2150   - textarea: [ 'placeholder' ],
2151   - th: [ 'abbr']
2152   - }
2153   - };
2154   -
2155   - function overlayElement(element, translation) {
2156   - const value = translation.value;
2157   -
2158   - if (typeof value === 'string') {
2159   - if (!reOverlay.test(value)) {
2160   - element.textContent = value;
2161   - } else {
2162   - // start with an inert template element and move its children into
2163   - // `element` but such that `element`'s own children are not replaced
2164   - const tmpl = element.ownerDocument.createElement('template');
2165   - tmpl.innerHTML = value;
2166   - // overlay the node with the DocumentFragment
2167   - overlay(element, tmpl.content);
2168   - }
2169   - }
2170   -
2171   - for (let key in translation.attrs) {
2172   - const attrName = camelCaseToDashed(key);
2173   - if (isAttrAllowed({ name: attrName }, element)) {
2174   - element.setAttribute(attrName, translation.attrs[key]);
2175   - }
2176   - }
2177   - }
2178   -
2179   - // The goal of overlay is to move the children of `translationElement`
2180   - // into `sourceElement` such that `sourceElement`'s own children are not
2181   - // replaced, but onle have their text nodes and their attributes modified.
2182   - //
2183   - // We want to make it possible for localizers to apply text-level semantics to
2184   - // the translations and make use of HTML entities. At the same time, we
2185   - // don't trust translations so we need to filter unsafe elements and
2186   - // attribtues out and we don't want to break the Web by replacing elements to
2187   - // which third-party code might have created references (e.g. two-way
2188   - // bindings in MVC frameworks).
2189   - function overlay(sourceElement, translationElement) {
2190   - const result = translationElement.ownerDocument.createDocumentFragment();
2191   - let k, attr;
2192   -
2193   - // take one node from translationElement at a time and check it against
2194   - // the allowed list or try to match it with a corresponding element
2195   - // in the source
2196   - let childElement;
2197   - while ((childElement = translationElement.childNodes[0])) {
2198   - translationElement.removeChild(childElement);
2199   -
2200   - if (childElement.nodeType === childElement.TEXT_NODE) {
2201   - result.appendChild(childElement);
2202   - continue;
2203   - }
2204   -
2205   - const index = getIndexOfType(childElement);
2206   - const sourceChild = getNthElementOfType(sourceElement, childElement, index);
2207   - if (sourceChild) {
2208   - // there is a corresponding element in the source, let's use it
2209   - overlay(sourceChild, childElement);
2210   - result.appendChild(sourceChild);
2211   - continue;
2212   - }
2213   -
2214   - if (isElementAllowed(childElement)) {
2215   - const sanitizedChild = childElement.ownerDocument.createElement(
2216   - childElement.nodeName);
2217   - overlay(sanitizedChild, childElement);
2218   - result.appendChild(sanitizedChild);
2219   - continue;
2220   - }
2221   -
2222   - // otherwise just take this child's textContent
2223   - result.appendChild(
2224   - translationElement.ownerDocument.createTextNode(
2225   - childElement.textContent));
2226   - }
2227   -
2228   - // clear `sourceElement` and append `result` which by this time contains
2229   - // `sourceElement`'s original children, overlayed with translation
2230   - sourceElement.textContent = '';
2231   - sourceElement.appendChild(result);
2232   -
2233   - // if we're overlaying a nested element, translate the allowed
2234   - // attributes; top-level attributes are handled in `translateElement`
2235   - // XXX attributes previously set here for another language should be
2236   - // cleared if a new language doesn't use them; https://bugzil.la/922577
2237   - if (translationElement.attributes) {
2238   - for (k = 0, attr; (attr = translationElement.attributes[k]); k++) {
2239   - if (isAttrAllowed(attr, sourceElement)) {
2240   - sourceElement.setAttribute(attr.name, attr.value);
2241   - }
2242   - }
2243   - }
2244   - }
2245   -
2246   - // XXX the allowed list should be amendable; https://bugzil.la/922573
2247   - function isElementAllowed(element) {
2248   - return allowed.elements.indexOf(element.tagName.toLowerCase()) !== -1;
2249   - }
2250   -
2251   - function isAttrAllowed(attr, element) {
2252   - const attrName = attr.name.toLowerCase();
2253   - const tagName = element.tagName.toLowerCase();
2254   - // is it a globally safe attribute?
2255   - if (allowed.attributes.global.indexOf(attrName) !== -1) {
2256   - return true;
2257   - }
2258   - // are there no allowed attributes for this element?
2259   - if (!allowed.attributes[tagName]) {
2260   - return false;
2261   - }
2262   - // is it allowed on this element?
2263   - // XXX the allowed list should be amendable; https://bugzil.la/922573
2264   - if (allowed.attributes[tagName].indexOf(attrName) !== -1) {
2265   - return true;
2266   - }
2267   - // special case for value on inputs with type button, reset, submit
2268   - if (tagName === 'input' && attrName === 'value') {
2269   - const type = element.type.toLowerCase();
2270   - if (type === 'submit' || type === 'button' || type === 'reset') {
2271   - return true;
2272   - }
2273   - }
2274   - return false;
2275   - }
2276   -
2277   - // Get n-th immediate child of context that is of the same type as element.
2278   - // XXX Use querySelector(':scope > ELEMENT:nth-of-type(index)'), when:
2279   - // 1) :scope is widely supported in more browsers and 2) it works with
2280   - // DocumentFragments.
2281   - function getNthElementOfType(context, element, index) {
2282   - /* jshint boss:true */
2283   - let nthOfType = 0;
2284   - for (let i = 0, child; child = context.children[i]; i++) {
2285   - if (child.nodeType === child.ELEMENT_NODE &&
2286   - child.tagName === element.tagName) {
2287   - if (nthOfType === index) {
2288   - return child;
2289   - }
2290   - nthOfType++;
2291   - }
2292   - }
2293   - return null;
2294   - }
2295   -
2296   - // Get the index of the element among siblings of the same type.
2297   - function getIndexOfType(element) {
2298   - let index = 0;
2299   - let child;
2300   - while ((child = element.previousElementSibling)) {
2301   - if (child.tagName === element.tagName) {
2302   - index++;
2303   - }
2304   - }
2305   - return index;
2306   - }
2307   -
2308   - function camelCaseToDashed(string) {
2309   - // XXX workaround for https://bugzil.la/1141934
2310   - if (string === 'ariaValueText') {
2311   - return 'aria-valuetext';
2312   - }
2313   -
2314   - return string
2315   - .replace(/[A-Z]/g, function (match) {
2316   - return '-' + match.toLowerCase();
2317   - })
2318   - .replace(/^-/, '');
2319   - }
2320   -
2321   - const reHtml = /[&<>]/g;
2322   - const htmlEntities = {
2323   - '&': '&amp;',
2324   - '<': '&lt;',
2325   - '>': '&gt;',
2326   - };
2327   -
2328   - function getResourceLinks(head) {
2329   - return Array.prototype.map.call(
2330   - head.querySelectorAll('link[rel="localization"]'),
2331   - el => el.getAttribute('href'));
2332   - }
2333   -
2334   - function setAttributes(element, id, args) {
2335   - element.setAttribute('data-l10n-id', id);
2336   - if (args) {
2337   - element.setAttribute('data-l10n-args', JSON.stringify(args));
2338   - }
2339   - }
2340   -
2341   - function getAttributes(element) {
2342   - return {
2343   - id: element.getAttribute('data-l10n-id'),
2344   - args: JSON.parse(element.getAttribute('data-l10n-args'))
2345   - };
2346   - }
2347   -
2348   - function getTranslatables(element) {
2349   - const nodes = Array.from(element.querySelectorAll('[data-l10n-id]'));
2350   -
2351   - if (typeof element.hasAttribute === 'function' &&
2352   - element.hasAttribute('data-l10n-id')) {
2353   - nodes.push(element);
2354   - }
2355   -
2356   - return nodes;
2357   - }
2358   -
2359   - function translateMutations(view, langs, mutations) {
2360   - const targets = new Set();
2361   -
2362   - for (let mutation of mutations) {
2363   - switch (mutation.type) {
2364   - case 'attributes':
2365   - targets.add(mutation.target);
2366   - break;
2367   - case 'childList':
2368   - for (let addedNode of mutation.addedNodes) {
2369   - if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
2370   - if (addedNode.childElementCount) {
2371   - getTranslatables(addedNode).forEach(targets.add.bind(targets));
2372   - } else {
2373   - if (addedNode.hasAttribute('data-l10n-id')) {
2374   - targets.add(addedNode);
2375   - }
2376   - }
2377   - }
2378   - }
2379   - break;
2380   - }
2381   - }
2382   -
2383   - if (targets.size === 0) {
2384   - return;
2385   - }
2386   -
2387   - translateElements(view, langs, Array.from(targets));
2388   - }
2389   -
2390   - function translateFragment(view, langs, frag) {
2391   - return translateElements(view, langs, getTranslatables(frag));
2392   - }
2393   -
2394   - function getElementsTranslation(view, langs, elems) {
2395   - const keys = elems.map(elem => {
2396   - const id = elem.getAttribute('data-l10n-id');
2397   - const args = elem.getAttribute('data-l10n-args');
2398   - return args ? [
2399   - id,
2400   - JSON.parse(args.replace(reHtml, match => htmlEntities[match]))
2401   - ] : id;
2402   - });
2403   -
2404   - return view._resolveEntities(langs, keys);
2405   - }
2406   -
2407   - function translateElements(view, langs, elements) {
2408   - return getElementsTranslation(view, langs, elements).then(
2409   - translations => applyTranslations(view, elements, translations));
2410   - }
2411   -
2412   - function applyTranslations(view, elems, translations) {
2413   - view._disconnect();
2414   - for (let i = 0; i < elems.length; i++) {
2415   - overlayElement(elems[i], translations[i]);
2416   - }
2417   - view._observe();
2418   - }
2419   -
2420   - const observerConfig = {
2421   - attributes: true,
2422   - characterData: false,
2423   - childList: true,
2424   - subtree: true,
2425   - attributeFilter: ['data-l10n-id', 'data-l10n-args']
2426   - };
2427   -
2428   - const readiness = new WeakMap();
2429   -
2430   - class View {
2431   - constructor(client, doc) {
2432   - this._doc = doc;
2433   - this.pseudo = {
2434   - 'fr-x-psaccent': createPseudo(this, 'fr-x-psaccent'),
2435   - 'ar-x-psbidi': createPseudo(this, 'ar-x-psbidi')
2436   - };
2437   -
2438   - this._interactive = documentReady().then(
2439   - () => init(this, client));
2440   -
2441   - const observer = new MutationObserver(onMutations.bind(this));
2442   - this._observe = () => observer.observe(doc, observerConfig);
2443   - this._disconnect = () => observer.disconnect();
2444   -
2445   - const translateView = langs => translateDocument(this, langs);
2446   - client.on('translateDocument', translateView);
2447   - this.ready = this._interactive.then(
2448   - client => client.method('resolvedLanguages')).then(
2449   - translateView);
2450   - }
2451   -
2452   - requestLanguages(langs, global) {
2453   - return this._interactive.then(
2454   - client => client.method('requestLanguages', langs, global));
2455   - }
2456   -
2457   - _resolveEntities(langs, keys) {
2458   - return this._interactive.then(
2459   - client => client.method('resolveEntities', client.id, langs, keys));
2460   - }
2461   -
2462   - formatValue(id, args) {
2463   - return this._interactive.then(
2464   - client => client.method('formatValues', client.id, [[id, args]])).then(
2465   - values => values[0]);
2466   - }
2467   -
2468   - formatValues(...keys) {
2469   - return this._interactive.then(
2470   - client => client.method('formatValues', client.id, keys));
2471   - }
2472   -
2473   - translateFragment(frag) {
2474   - return this._interactive.then(
2475   - client => client.method('resolvedLanguages')).then(
2476   - langs => translateFragment(this, langs, frag));
2477   - }
2478   - }
2479   -
2480   - View.prototype.setAttributes = setAttributes;
2481   - View.prototype.getAttributes = getAttributes;
2482   -
2483   - function createPseudo(view, code) {
2484   - return {
2485   - getName: () => view._interactive.then(
2486   - client => client.method('getName', code)),
2487   - processString: str => view._interactive.then(
2488   - client => client.method('processString', code, str)),
2489   - };
2490   - }
2491   -
2492   - function init(view, client) {
2493   - view._observe();
2494   - return client.method(
2495   - 'registerView', client.id, getResourceLinks(view._doc.head)).then(
2496   - () => client);
2497   - }
2498   -
2499   - function onMutations(mutations) {
2500   - return this._interactive.then(
2501   - client => client.method('resolvedLanguages')).then(
2502   - langs => translateMutations(this, langs, mutations));
2503   - }
2504   -
2505   - function translateDocument(view, langs) {
2506   - const html = view._doc.documentElement;
2507   -
2508   - if (readiness.has(html)) {
2509   - return translateFragment(view, langs, html).then(
2510   - () => setAllAndEmit(html, langs));
2511   - }
2512   -
2513   - const translated =
2514   - // has the document been already pre-translated?
2515   - langs[0].code === html.getAttribute('lang') ?
2516   - Promise.resolve() :
2517   - translateFragment(view, langs, html).then(
2518   - () => setLangDir(html, langs));
2519   -
2520   - return translated.then(() => {
2521   - setLangs(html, langs);
2522   - readiness.set(html, true);
2523   - });
2524   - }
2525   -
2526   - function setLangs(html, langs) {
2527   - const codes = langs.map(lang => lang.code);
2528   - html.setAttribute('langs', codes.join(' '));
2529   - }
2530   -
2531   - function setLangDir(html, langs) {
2532   - const code = langs[0].code;
2533   - html.setAttribute('lang', code);
2534   - html.setAttribute('dir', getDirection(code));
2535   - }
2536   -
2537   - function setAllAndEmit(html, langs) {
2538   - setLangDir(html, langs);
2539   - setLangs(html, langs);
2540   - html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated', {
2541   - bubbles: false,
2542   - cancelable: false,
2543   - }));
2544   - }
2545   -
2546   - const remote = new Remote(fetchResource, broadcast, navigator.languages);
2547   - window.addEventListener('languagechange', remote);
2548   - document.addEventListener('additionallanguageschange', remote);
2549   -
2550   - document.l10n = new View(
2551   - new Client(remote), document);
2552   -
2553   - //Bug 1204660 - Temporary proxy for shared code. Will be removed once
2554   - // l10n.js migration is completed.
2555   - navigator.mozL10n = {
2556   - setAttributes: document.l10n.setAttributes,
2557   - getAttributes: document.l10n.getAttributes,
2558   - formatValue: (...args) => document.l10n.formatValue(...args),
2559   - translateFragment: (...args) => document.l10n.translateFragment(...args),
2560   - once: cb => document.l10n.ready.then(cb),
2561   - ready: cb => document.l10n.ready.then(() => {
2562   - document.addEventListener('DOMRetranslated', cb);
2563   - cb();
2564   - }),
2565   - };
2566   -
2567   -})();
2568 0 \ No newline at end of file
bower_components/l20n/dist/bundle/jsshell/l20n.js deleted
1   -(function () { 'use strict';
2   -
3   - function L10nError(message, id, lang) {
4   - this.name = 'L10nError';
5   - this.message = message;
6   - this.id = id;
7   - this.lang = lang;
8   - }
9   - L10nError.prototype = Object.create(Error.prototype);
10   - L10nError.prototype.constructor = L10nError;
11   -
12   - const MAX_PLACEABLES = 100;
13   -
14   - var L20nParser = {
15   - parse: function(emit, string) {
16   - this._source = string;
17   - this._index = 0;
18   - this._length = string.length;
19   - this.entries = Object.create(null);
20   - this.emit = emit;
21   -
22   - return this.getResource();
23   - },
24   -
25   - getResource: function() {
26   - this.getWS();
27   - while (this._index < this._length) {
28   - try {
29   - this.getEntry();
30   - } catch (e) {
31   - if (e instanceof L10nError) {
32   - // we want to recover, but we don't need it in entries
33   - this.getJunkEntry();