diff --git a/gulpfile.js b/gulpfile.js
index cf432cb..e774ed1 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -25,15 +25,9 @@ var packageFilesWhitelist = [
// include.js not included since it is written straight to package folder.
// Locale files will be dynamically whitelisted later.
'src/index.html',
- 'src/manifest.appcache',
'src/app-icons/*.png',
'src/media/css/include.css',
- 'src/media/fonts/FiraSans/firasansot-light-webfont.*',
'src/media/fonts/FiraSans/firasansot-regular-webfont.*',
- 'src/offline/*',
- 'src/offline/css/*',
- 'src/offline/js/*',
- 'src/offline/locales/*',
'src/tutorial/*',
'src/tutorial/css/*',
'src/tutorial/img/*',
diff --git a/package/templates/manifest.webapp b/package/templates/manifest.webapp
index f7bc3ba..e11562e 100644
--- a/package/templates/manifest.webapp
+++ b/package/templates/manifest.webapp
@@ -9,7 +9,6 @@
"336": "media/img/app-icons/appic_web_apps.png"
},
"launch_path": "index.html",
- "appcache_path": "manifest.appcache",
"name": "{name}",
"origin": "{origin}",
"type": "web",
diff --git a/src/dev.html b/src/dev.html
index a73f2b0..3de92ae 100644
--- a/src/dev.html
+++ b/src/dev.html
@@ -1,5 +1,5 @@
-
+
{% endif %}
-
-
diff --git a/src/manifest.appcache b/src/manifest.appcache
deleted file mode 100644
index 3129a28..0000000
--- a/src/manifest.appcache
+++ /dev/null
@@ -1,10 +0,0 @@
-CACHE MANIFEST
-index.html
-offline/index.html
-offline/css/index.css
-offline/js/l10n.js
-offline/js/index.js
-offline/locales/marketplace-offline.en-US.properties
-
-NETWORK:
-*
diff --git a/src/media/fonts/FiraSans/firasansot-light-webfont.eot b/src/media/fonts/FiraSans/firasansot-light-webfont.eot
deleted file mode 100755
index 02ba84d..0000000
Binary files a/src/media/fonts/FiraSans/firasansot-light-webfont.eot and /dev/null differ
diff --git a/src/media/fonts/FiraSans/firasansot-light-webfont.svg b/src/media/fonts/FiraSans/firasansot-light-webfont.svg
deleted file mode 100755
index a21f209..0000000
--- a/src/media/fonts/FiraSans/firasansot-light-webfont.svg
+++ /dev/null
@@ -1,953 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/src/media/fonts/FiraSans/firasansot-light-webfont.ttf b/src/media/fonts/FiraSans/firasansot-light-webfont.ttf
deleted file mode 100755
index 035e915..0000000
Binary files a/src/media/fonts/FiraSans/firasansot-light-webfont.ttf and /dev/null differ
diff --git a/src/media/fonts/FiraSans/firasansot-light-webfont.woff b/src/media/fonts/FiraSans/firasansot-light-webfont.woff
deleted file mode 100755
index b2e5149..0000000
Binary files a/src/media/fonts/FiraSans/firasansot-light-webfont.woff and /dev/null differ
diff --git a/src/offline/css/index.css b/src/offline/css/index.css
deleted file mode 100644
index 98f4891..0000000
--- a/src/offline/css/index.css
+++ /dev/null
@@ -1,69 +0,0 @@
-@font-face {
- src: local('FiraSansOTLight'),
- local('FiraSansLight'),
- local('Fira Sans OT Light'),
- local('Fira Sans Light'),
- url('../../media/fonts/FiraSans/firasansot-light-webfont.woff') format('woff'),
- url('../../media/fonts/FiraSans/firasansot-light-webfont.ttf') format('truetype'),
- url('../../media/fonts/FiraSans/firasansot-light-webfont.svg#fira_sans_otlight') format('svg');
- font-family: FiraSansWeb;
- font-weight: 300;
-}
-* {
- box-sizing: border-box;
-}
-html, body {
- height: 100%;
-}
-html {
- font-family: "Fira Sans OT", "Fira Sans", FiraSansWeb, sans-serif;
- font-style: italic;
- font-weight: 300;
- font-size: 10px;
-}
-body {
- margin: 0;
- padding: 6rem 0 0 36rem;
- background-color: #CCC;
-}
-.title, .button {
- font-family: "Fira Sans OT", "Fira Sans", FiraSansWeb, sans-serif;
- font-style: italic;
- font-weight: 300;
-}
-.title, .info {
- color: #4D4D4D;
-}
-.title {
- margin: 0 0 16rem;
- font-size: 46px;
-}
-.info {
- margin-left: 4.5rem;
- font-size: 28px;
-}
-.button {
- position: absolute;
- right: 40.5rem;
- bottom: 41rem;
- width: 23.7rem;
- height: 7rem;
- border: 0;
- border-radius: 5.5rem;
- padding: 2rem 4.4rem;
- opacity: .7;
- font-size: 2.8rem;
- line-height: 1rem;
- color: #FFF;
- background: none;
- background-color: #000;
- transition-property: all;
- transition-timing-function: cubic-bezier(.25, 0, 0, 1);
- transition-duration: .42s;
-}
-.button:focus {
- transform: scale(1.2);
-}
-.button::-moz-focus-inner {
- border: 0;
-}
diff --git a/src/offline/index.html b/src/offline/index.html
deleted file mode 100644
index 99bf6f1..0000000
--- a/src/offline/index.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
- Firefox Web Apps Offline
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/offline/js/index.js b/src/offline/js/index.js
deleted file mode 100644
index 9901bd4..0000000
--- a/src/offline/js/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-(function(window) {
- var $closeButton = document.getElementById('close-button');
-
- $closeButton.addEventListener('click', function() {
- window.close();
- });
-
- $closeButton.focus();
-})(window);
diff --git a/src/offline/js/l10n.js b/src/offline/js/l10n.js
deleted file mode 100644
index 0871f07..0000000
--- a/src/offline/js/l10n.js
+++ /dev/null
@@ -1,2205 +0,0 @@
-(function(window, undefined) {
- 'use strict';
-
- /* jshint validthis:true */
- function L10nError(message, id, loc) {
- this.name = 'L10nError';
- this.message = message;
- this.id = id;
- this.loc = loc;
- }
- L10nError.prototype = Object.create(Error.prototype);
- L10nError.prototype.constructor = L10nError;
-
-
- /* jshint browser:true */
-
- var io = {
-
- _load: function(type, url, callback, sync) {
- var xhr = new XMLHttpRequest();
- var needParse;
-
- if (xhr.overrideMimeType) {
- xhr.overrideMimeType(type);
- }
-
- xhr.open('GET', url, !sync);
-
- if (type === 'application/json') {
- // Gecko 11.0+ forbids the use of the responseType attribute when
- // performing sync requests (NS_ERROR_DOM_INVALID_ACCESS_ERR).
- // We'll need to JSON.parse manually.
- if (sync) {
- needParse = true;
- } else {
- xhr.responseType = 'json';
- }
- }
-
- xhr.addEventListener('load', function io_onload(e) {
- if (e.target.status === 200 || e.target.status === 0) {
- // Sinon.JS's FakeXHR doesn't have the response property
- var res = e.target.response || e.target.responseText;
- callback(null, needParse ? JSON.parse(res) : res);
- } else {
- callback(new L10nError('Not found: ' + url));
- }
- });
- xhr.addEventListener('error', callback);
- xhr.addEventListener('timeout', callback);
-
- // the app: protocol throws on 404, see https://bugzil.la/827243
- try {
- xhr.send(null);
- } catch (e) {
- if (e.name === 'NS_ERROR_FILE_NOT_FOUND') {
- // the app: protocol throws on 404, see https://bugzil.la/827243
- callback(new L10nError('Not found: ' + url));
- } else {
- throw e;
- }
- }
- },
-
- load: function(url, callback, sync) {
- return io._load('text/plain', url, callback, sync);
- },
-
- loadJSON: function(url, callback, sync) {
- return io._load('application/json', url, callback, sync);
- }
-
- };
-
- function EventEmitter() {}
-
- EventEmitter.prototype.emit = function ee_emit() {
- if (!this._listeners) {
- return;
- }
-
- var args = Array.prototype.slice.call(arguments);
- var type = args.shift();
- if (!this._listeners[type]) {
- return;
- }
-
- var typeListeners = this._listeners[type].slice();
- for (var i = 0; i < typeListeners.length; i++) {
- typeListeners[i].apply(this, args);
- }
- };
-
- EventEmitter.prototype.addEventListener = function ee_add(type, listener) {
- if (!this._listeners) {
- this._listeners = {};
- }
- if (!(type in this._listeners)) {
- this._listeners[type] = [];
- }
- this._listeners[type].push(listener);
- };
-
- EventEmitter.prototype.removeEventListener = function ee_rm(type, listener) {
- if (!this._listeners) {
- return;
- }
-
- var typeListeners = this._listeners[type];
- var pos = typeListeners.indexOf(listener);
- if (pos === -1) {
- return;
- }
-
- typeListeners.splice(pos, 1);
- };
-
-
- function getPluralRule(lang) {
- var locales2rules = {
- 'af': 3,
- 'ak': 4,
- 'am': 4,
- 'ar': 1,
- 'asa': 3,
- 'az': 0,
- 'be': 11,
- 'bem': 3,
- 'bez': 3,
- 'bg': 3,
- 'bh': 4,
- 'bm': 0,
- 'bn': 3,
- 'bo': 0,
- 'br': 20,
- 'brx': 3,
- 'bs': 11,
- 'ca': 3,
- 'cgg': 3,
- 'chr': 3,
- 'cs': 12,
- 'cy': 17,
- 'da': 3,
- 'de': 3,
- 'dv': 3,
- 'dz': 0,
- 'ee': 3,
- 'el': 3,
- 'en': 3,
- 'eo': 3,
- 'es': 3,
- 'et': 3,
- 'eu': 3,
- 'fa': 0,
- 'ff': 5,
- 'fi': 3,
- 'fil': 4,
- 'fo': 3,
- 'fr': 5,
- 'fur': 3,
- 'fy': 3,
- 'ga': 8,
- 'gd': 24,
- 'gl': 3,
- 'gsw': 3,
- 'gu': 3,
- 'guw': 4,
- 'gv': 23,
- 'ha': 3,
- 'haw': 3,
- 'he': 2,
- 'hi': 4,
- 'hr': 11,
- 'hu': 0,
- 'id': 0,
- 'ig': 0,
- 'ii': 0,
- 'is': 3,
- 'it': 3,
- 'iu': 7,
- 'ja': 0,
- 'jmc': 3,
- 'jv': 0,
- 'ka': 0,
- 'kab': 5,
- 'kaj': 3,
- 'kcg': 3,
- 'kde': 0,
- 'kea': 0,
- 'kk': 3,
- 'kl': 3,
- 'km': 0,
- 'kn': 0,
- 'ko': 0,
- 'ksb': 3,
- 'ksh': 21,
- 'ku': 3,
- 'kw': 7,
- 'lag': 18,
- 'lb': 3,
- 'lg': 3,
- 'ln': 4,
- 'lo': 0,
- 'lt': 10,
- 'lv': 6,
- 'mas': 3,
- 'mg': 4,
- 'mk': 16,
- 'ml': 3,
- 'mn': 3,
- 'mo': 9,
- 'mr': 3,
- 'ms': 0,
- 'mt': 15,
- 'my': 0,
- 'nah': 3,
- 'naq': 7,
- 'nb': 3,
- 'nd': 3,
- 'ne': 3,
- 'nl': 3,
- 'nn': 3,
- 'no': 3,
- 'nr': 3,
- 'nso': 4,
- 'ny': 3,
- 'nyn': 3,
- 'om': 3,
- 'or': 3,
- 'pa': 3,
- 'pap': 3,
- 'pl': 13,
- 'ps': 3,
- 'pt': 3,
- 'rm': 3,
- 'ro': 9,
- 'rof': 3,
- 'ru': 11,
- 'rwk': 3,
- 'sah': 0,
- 'saq': 3,
- 'se': 7,
- 'seh': 3,
- 'ses': 0,
- 'sg': 0,
- 'sh': 11,
- 'shi': 19,
- 'sk': 12,
- 'sl': 14,
- 'sma': 7,
- 'smi': 7,
- 'smj': 7,
- 'smn': 7,
- 'sms': 7,
- 'sn': 3,
- 'so': 3,
- 'sq': 3,
- 'sr': 11,
- 'ss': 3,
- 'ssy': 3,
- 'st': 3,
- 'sv': 3,
- 'sw': 3,
- 'syr': 3,
- 'ta': 3,
- 'te': 3,
- 'teo': 3,
- 'th': 0,
- 'ti': 4,
- 'tig': 3,
- 'tk': 3,
- 'tl': 4,
- 'tn': 3,
- 'to': 0,
- 'tr': 0,
- 'ts': 3,
- 'tzm': 22,
- 'uk': 11,
- 'ur': 3,
- 've': 3,
- 'vi': 0,
- 'vun': 3,
- 'wa': 4,
- 'wae': 3,
- 'wo': 0,
- 'xh': 3,
- 'xog': 3,
- 'yo': 0,
- 'zh': 0,
- 'zu': 3
- };
-
- // utility functions for plural rules methods
- function isIn(n, list) {
- return list.indexOf(n) !== -1;
- }
- function isBetween(n, start, end) {
- return typeof n === typeof start && start <= n && n <= end;
- }
-
- // list of all plural rules methods:
- // map an integer to the plural form name to use
- var pluralRules = {
- '0': function() {
- return 'other';
- },
- '1': function(n) {
- if ((isBetween((n % 100), 3, 10))) {
- return 'few';
- }
- if (n === 0) {
- return 'zero';
- }
- if ((isBetween((n % 100), 11, 99))) {
- return 'many';
- }
- if (n === 2) {
- return 'two';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '2': function(n) {
- if (n !== 0 && (n % 10) === 0) {
- return 'many';
- }
- if (n === 2) {
- return 'two';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '3': function(n) {
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '4': function(n) {
- if ((isBetween(n, 0, 1))) {
- return 'one';
- }
- return 'other';
- },
- '5': function(n) {
- if ((isBetween(n, 0, 2)) && n !== 2) {
- return 'one';
- }
- return 'other';
- },
- '6': function(n) {
- if (n === 0) {
- return 'zero';
- }
- if ((n % 10) === 1 && (n % 100) !== 11) {
- return 'one';
- }
- return 'other';
- },
- '7': function(n) {
- if (n === 2) {
- return 'two';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '8': function(n) {
- if ((isBetween(n, 3, 6))) {
- return 'few';
- }
- if ((isBetween(n, 7, 10))) {
- return 'many';
- }
- if (n === 2) {
- return 'two';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '9': function(n) {
- if (n === 0 || n !== 1 && (isBetween((n % 100), 1, 19))) {
- return 'few';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '10': function(n) {
- if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) {
- return 'few';
- }
- if ((n % 10) === 1 && !(isBetween((n % 100), 11, 19))) {
- return 'one';
- }
- return 'other';
- },
- '11': function(n) {
- if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
- return 'few';
- }
- if ((n % 10) === 0 ||
- (isBetween((n % 10), 5, 9)) ||
- (isBetween((n % 100), 11, 14))) {
- return 'many';
- }
- if ((n % 10) === 1 && (n % 100) !== 11) {
- return 'one';
- }
- return 'other';
- },
- '12': function(n) {
- if ((isBetween(n, 2, 4))) {
- return 'few';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '13': function(n) {
- if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
- return 'few';
- }
- if (n !== 1 && (isBetween((n % 10), 0, 1)) ||
- (isBetween((n % 10), 5, 9)) ||
- (isBetween((n % 100), 12, 14))) {
- return 'many';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '14': function(n) {
- if ((isBetween((n % 100), 3, 4))) {
- return 'few';
- }
- if ((n % 100) === 2) {
- return 'two';
- }
- if ((n % 100) === 1) {
- return 'one';
- }
- return 'other';
- },
- '15': function(n) {
- if (n === 0 || (isBetween((n % 100), 2, 10))) {
- return 'few';
- }
- if ((isBetween((n % 100), 11, 19))) {
- return 'many';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '16': function(n) {
- if ((n % 10) === 1 && n !== 11) {
- return 'one';
- }
- return 'other';
- },
- '17': function(n) {
- if (n === 3) {
- return 'few';
- }
- if (n === 0) {
- return 'zero';
- }
- if (n === 6) {
- return 'many';
- }
- if (n === 2) {
- return 'two';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '18': function(n) {
- if (n === 0) {
- return 'zero';
- }
- if ((isBetween(n, 0, 2)) && n !== 0 && n !== 2) {
- return 'one';
- }
- return 'other';
- },
- '19': function(n) {
- if ((isBetween(n, 2, 10))) {
- return 'few';
- }
- if ((isBetween(n, 0, 1))) {
- return 'one';
- }
- return 'other';
- },
- '20': function(n) {
- if ((isBetween((n % 10), 3, 4) || ((n % 10) === 9)) && !(
- isBetween((n % 100), 10, 19) ||
- isBetween((n % 100), 70, 79) ||
- isBetween((n % 100), 90, 99)
- )) {
- return 'few';
- }
- if ((n % 1000000) === 0 && n !== 0) {
- return 'many';
- }
- if ((n % 10) === 2 && !isIn((n % 100), [12, 72, 92])) {
- return 'two';
- }
- if ((n % 10) === 1 && !isIn((n % 100), [11, 71, 91])) {
- return 'one';
- }
- return 'other';
- },
- '21': function(n) {
- if (n === 0) {
- return 'zero';
- }
- if (n === 1) {
- return 'one';
- }
- return 'other';
- },
- '22': function(n) {
- if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) {
- return 'one';
- }
- return 'other';
- },
- '23': function(n) {
- if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) {
- return 'one';
- }
- return 'other';
- },
- '24': function(n) {
- if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) {
- return 'few';
- }
- if (isIn(n, [2, 12])) {
- return 'two';
- }
- if (isIn(n, [1, 11])) {
- return 'one';
- }
- return 'other';
- }
- };
-
- // return a function that gives the plural form name for a given integer
- var index = locales2rules[lang.replace(/-.*$/, '')];
- if (!(index in pluralRules)) {
- return function() { return 'other'; };
- }
- return pluralRules[index];
- }
-
-
-
-
- var MAX_PLACEABLES = 100;
-
- var PropertiesParser = {
- patterns: null,
- entryIds: null,
-
- init: function() {
- this.patterns = {
- comment: /^\s*#|^\s*$/,
- entity: /^([^=\s]+)\s*=\s*(.*)$/,
- multiline: /[^\\]\\$/,
- index: /\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,
- unicode: /\\u([0-9a-fA-F]{1,4})/g,
- entries: /[^\r\n]+/g,
- controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g,
- placeables: /\{\{\s*([^\s]*?)\s*\}\}/,
- };
- },
-
- parse: function(ctx, source) {
- if (!this.patterns) {
- this.init();
- }
-
- var ast = [];
- this.entryIds = Object.create(null);
-
- var entries = source.match(this.patterns.entries);
- if (!entries) {
- return ast;
- }
- for (var i = 0; i < entries.length; i++) {
- var line = entries[i];
-
- if (this.patterns.comment.test(line)) {
- continue;
- }
-
- while (this.patterns.multiline.test(line) && i < entries.length) {
- line = line.slice(0, -1) + entries[++i].trim();
- }
-
- var entityMatch = line.match(this.patterns.entity);
- if (entityMatch) {
- try {
- this.parseEntity(entityMatch[1], entityMatch[2], ast);
- } catch (e) {
- if (ctx) {
- ctx._emitter.emit('parseerror', e);
- } else {
- throw e;
- }
- }
- }
- }
- return ast;
- },
-
- parseEntity: function(id, value, ast) {
- var name, key;
-
- var pos = id.indexOf('[');
- if (pos !== -1) {
- name = id.substr(0, pos);
- key = id.substring(pos + 1, id.length - 1);
- } else {
- name = id;
- key = null;
- }
-
- var nameElements = name.split('.');
-
- if (nameElements.length > 2) {
- throw new L10nError('Error in ID: "' + name + '".' +
- ' Nested attributes are not supported.');
- }
-
- var attr;
- if (nameElements.length > 1) {
- name = nameElements[0];
- attr = nameElements[1];
-
- if (attr[0] === '$') {
- throw new L10nError('Attribute can\'t start with "$"', id);
- }
- } else {
- attr = null;
- }
-
- this.setEntityValue(name, attr, key, this.unescapeString(value), ast);
- },
-
- setEntityValue: function(id, attr, key, rawValue, ast) {
- var pos, v;
-
- var value = rawValue.indexOf('{{') > -1 ?
- this.parseString(rawValue) : rawValue;
-
- if (attr) {
- pos = this.entryIds[id];
- if (pos === undefined) {
- v = {$i: id};
- if (key) {
- v[attr] = {};
- v[attr][key] = value;
- } else {
- v[attr] = value;
- }
- ast.push(v);
- this.entryIds[id] = ast.length - 1;
- return;
- }
- if (key) {
- if (typeof(ast[pos][attr]) === 'string') {
- ast[pos][attr] = {
- $x: this.parseIndex(ast[pos][attr]),
- $v: {}
- };
- }
- ast[pos][attr].$v[key] = value;
- return;
- }
- ast[pos][attr] = value;
- return;
- }
-
- // Hash value
- if (key) {
- pos = this.entryIds[id];
- if (pos === undefined) {
- v = {};
- v[key] = value;
- ast.push({$i: id, $v: v});
- this.entryIds[id] = ast.length - 1;
- return;
- }
- if (typeof(ast[pos].$v) === 'string') {
- ast[pos].$x = this.parseIndex(ast[pos].$v);
- ast[pos].$v = {};
- }
- ast[pos].$v[key] = value;
- return;
- }
-
- // simple value
- ast.push({$i: id, $v: value});
- this.entryIds[id] = ast.length - 1;
- },
-
- parseString: function(str) {
- var chunks = str.split(this.patterns.placeables);
- var complexStr = [];
-
- var len = chunks.length;
- var placeablesCount = (len - 1) / 2;
-
- if (placeablesCount >= MAX_PLACEABLES) {
- throw new L10nError('Too many placeables (' + placeablesCount +
- ', max allowed is ' + MAX_PLACEABLES + ')');
- }
-
- for (var i = 0; i < chunks.length; i++) {
- if (chunks[i].length === 0) {
- continue;
- }
- if (i % 2 === 1) {
- complexStr.push({t: 'idOrVar', v: chunks[i]});
- } else {
- complexStr.push(chunks[i]);
- }
- }
- return complexStr;
- },
-
- unescapeString: function(str) {
- if (str.lastIndexOf('\\') !== -1) {
- str = str.replace(this.patterns.controlChars, '$1');
- }
- return str.replace(this.patterns.unicode, function(match, token) {
- return unescape('%u' + '0000'.slice(token.length) + token);
- });
- },
-
- parseIndex: function(str) {
- var match = str.match(this.patterns.index);
- if (!match) {
- throw new L10nError('Malformed index');
- }
- if (match[2]) {
- return [{t: 'idOrVar', v: match[1]}, match[2]];
- } else {
- return [{t: 'idOrVar', v: match[1]}];
- }
- }
- };
-
-
-
- var KNOWN_MACROS = ['plural'];
-
- var MAX_PLACEABLE_LENGTH = 2500;
- var rePlaceables = /\{\{\s*(.+?)\s*\}\}/g;
-
- // Matches characters outside of the Latin-1 character set
- var nonLatin1 = /[^\x01-\xFF]/;
-
- // Unicode bidi isolation characters
- var FSI = '\u2068';
- var PDI = '\u2069';
-
- function createEntry(node, env) {
- var keys = Object.keys(node);
-
- // the most common scenario: a simple string with no arguments
- if (typeof node.$v === 'string' && keys.length === 2) {
- return node.$v;
- }
-
- var attrs;
-
- /* jshint -W084 */
- for (var i = 0, key; key = keys[i]; i++) {
- // skip $i (id), $v (value), $x (index)
- if (key[0] === '$') {
- continue;
- }
-
- if (!attrs) {
- attrs = Object.create(null);
- }
- attrs[key] = createAttribute(node[key], env, node.$i + '.' + key);
- }
-
- return {
- id: node.$i,
- value: node.$v !== undefined ? node.$v : null,
- index: node.$x || null,
- attrs: attrs || null,
- env: env,
- // the dirty guard prevents cyclic or recursive references
- dirty: false
- };
- }
-
- function createAttribute(node, env, id) {
- if (typeof node === 'string') {
- return node;
- }
-
- return {
- id: id,
- value: node.$v || (node !== undefined ? node : null),
- index: node.$x || null,
- env: env,
- dirty: false
- };
- }
-
-
- function format(args, entity) {
- if (typeof entity === 'string') {
- return [{}, entity];
- }
-
- if (entity.dirty) {
- throw new L10nError('Cyclic reference detected: ' + entity.id);
- }
-
- entity.dirty = true;
-
- var rv;
-
- // if format fails, we want the exception to bubble up and stop the whole
- // resolving process; however, we still need to clean up the dirty flag
- try {
- rv = resolveValue({}, args, entity.env, entity.value, entity.index);
- } finally {
- entity.dirty = false;
- }
- return rv;
- }
-
- function resolveIdentifier(args, env, id) {
- if (KNOWN_MACROS.indexOf(id) > -1) {
- return [{}, env['__' + id]];
- }
-
- if (args && args.hasOwnProperty(id)) {
- if (typeof args[id] === 'string' || (typeof args[id] === 'number' &&
- !isNaN(args[id]))) {
- return [{}, args[id]];
- } else {
- throw new L10nError('Arg must be a string or a number: ' + id);
- }
- }
-
- // XXX: special case for Node.js where still:
- // '__proto__' in Object.create(null) => true
- if (id in env && id !== '__proto__') {
- return format(args, env[id]);
- }
-
- throw new L10nError('Unknown reference: ' + id);
- }
-
- function subPlaceable(locals, args, env, id) {
- var res;
-
- try {
- res = resolveIdentifier(args, env, id);
- } catch (err) {
- return [{ error: err }, '{{ ' + id + ' }}'];
- }
-
- var value = res[1];
-
- if (typeof value === 'number') {
- return res;
- }
-
- if (typeof value === 'string') {
- // prevent Billion Laughs attacks
- if (value.length >= MAX_PLACEABLE_LENGTH) {
- throw new L10nError('Too many characters in placeable (' +
- value.length + ', max allowed is ' +
- MAX_PLACEABLE_LENGTH + ')');
- }
-
- if (locals.contextIsNonLatin1 || value.match(nonLatin1)) {
- // When dealing with non-Latin-1 text
- // we wrap substitutions in bidi isolate characters
- // to avoid bidi issues.
- res[1] = FSI + value + PDI;
- }
-
- return res;
- }
-
- return [{}, '{{ ' + id + ' }}'];
- }
-
- function interpolate(locals, args, env, arr) {
- return arr.reduce(function(prev, cur) {
- if (typeof cur === 'string') {
- return [prev[0], prev[1] + cur];
- } else if (cur.t === 'idOrVar'){
- var placeable = subPlaceable(locals, args, env, cur.v);
- return [prev[0], prev[1] + placeable[1]];
- }
- }, [locals, '']);
- }
-
- function resolveSelector(args, env, expr, index) {
- var selectorName = index[0].v;
- var selector = resolveIdentifier(args, env, selectorName)[1];
-
- if (typeof selector !== 'function') {
- // selector is a simple reference to an entity or args
- return selector;
- }
-
- var argValue = index[1] ?
- resolveIdentifier(args, env, index[1])[1] : undefined;
-
- if (selector === env.__plural) {
- // special cases for zero, one, two if they are defined on the hash
- if (argValue === 0 && 'zero' in expr) {
- return 'zero';
- }
- if (argValue === 1 && 'one' in expr) {
- return 'one';
- }
- if (argValue === 2 && 'two' in expr) {
- return 'two';
- }
- }
-
- return selector(argValue);
- }
-
- function resolveValue(locals, args, env, expr, index) {
- if (!expr) {
- return [locals, expr];
- }
-
- if (typeof expr === 'string' ||
- typeof expr === 'boolean' ||
- typeof expr === 'number') {
- return [locals, expr];
- }
-
- if (Array.isArray(expr)) {
- locals.contextIsNonLatin1 = expr.some(function($_) {
- return typeof($_) === 'string' && $_.match(nonLatin1);
- });
- return interpolate(locals, args, env, expr);
- }
-
- // otherwise, it's a dict
- if (index) {
- // try to use the index in order to select the right dict member
- var selector = resolveSelector(args, env, expr, index);
- if (expr.hasOwnProperty(selector)) {
- return resolveValue(locals, args, env, expr[selector]);
- }
- }
-
- // if there was no index or no selector was found, try 'other'
- if ('other' in expr) {
- return resolveValue(locals, args, env, expr.other);
- }
-
- // XXX Specify entity id
- throw new L10nError('Unresolvable value');
- }
-
- var Resolver = {
- createEntry: createEntry,
- format: format,
- rePlaceables: rePlaceables
- };
-
-
-
- /* Utility functions */
-
- // Recursively walk an AST node searching for content leaves
- function walkContent(node, fn) {
- if (typeof node === 'string') {
- return fn(node);
- }
-
- if (node.t === 'idOrVar') {
- return node;
- }
-
- var rv = Array.isArray(node) ? [] : {};
- var keys = Object.keys(node);
-
- for (var i = 0, key; (key = keys[i]); i++) {
- // don't change identifier ($i) nor indices ($x)
- if (key === '$i' || key === '$x') {
- rv[key] = node[key];
- } else {
- rv[key] = walkContent(node[key], fn);
- }
- }
- return rv;
- }
-
-
- /* Pseudolocalizations
- *
- * PSEUDO is a dict of strategies to be used to modify the English
- * context in order to create pseudolocalizations. These can be used by
- * developers to test the localizability of their code without having to
- * actually speak a foreign language.
- *
- * Currently, the following pseudolocales are supported:
- *
- * fr-x-psaccent - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
- *
- * In Accented English all English letters are replaced by accented
- * Unicode counterparts which don't impair the readability of the content.
- * This allows developers to quickly test if any given string is being
- * correctly displayed in its 'translated' form. Additionally, simple
- * heuristics are used to make certain words longer to better simulate the
- * experience of international users.
- *
- * ar-x-psbidi - ɥsıʅƃuƎ ıpıԐ
- *
- * Bidi English is a fake RTL locale. All words are surrounded by
- * Unicode formatting marks forcing the RTL directionality of characters.
- * In addition, to make the reversed text easier to read, individual
- * letters are flipped.
- *
- * Note: The name above is hardcoded to be RTL in case code editors have
- * trouble with the RLO and PDF Unicode marks. In reality, it should be
- * surrounded by those marks as well.
- *
- * See https://bugzil.la/900182 for more information.
- *
- */
-
- var reAlphas = /[a-zA-Z]/g;
- var reVowels = /[aeiouAEIOU]/g;
-
- // ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ + [\\]^_` + ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ
- var ACCENTED_MAP = '\u0226\u0181\u0187\u1E12\u1E16\u0191\u0193\u0126\u012A' +
- '\u0134\u0136\u013F\u1E3E\u0220\u01FE\u01A4\u024A\u0158' +
- '\u015E\u0166\u016C\u1E7C\u1E86\u1E8A\u1E8E\u1E90' +
- '[\\]^_`' +
- '\u0227\u0180\u0188\u1E13\u1E17\u0192\u0260\u0127\u012B' +
- '\u0135\u0137\u0140\u1E3F\u019E\u01FF\u01A5\u024B\u0159' +
- '\u015F\u0167\u016D\u1E7D\u1E87\u1E8B\u1E8F\u1E91';
-
- // XXX Until https://bugzil.la/1007340 is fixed, ᗡℲ⅁⅂⅄ don't render correctly
- // on the devices. For now, use the following replacements: pɟפ˥ʎ
- // ∀ԐↃpƎɟפHIſӼ˥WNOԀÒᴚS⊥∩ɅMXʎZ + [\\]ᵥ_, + ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz
- var FLIPPED_MAP = '\u2200\u0510\u2183p\u018E\u025F\u05E4HI\u017F' +
- '\u04FC\u02E5WNO\u0500\xD2\u1D1AS\u22A5\u2229\u0245' +
- '\uFF2DX\u028EZ' +
- '[\\]\u1D65_,' +
- '\u0250q\u0254p\u01DD\u025F\u0183\u0265\u0131\u027E' +
- '\u029E\u0285\u026Fuodb\u0279s\u0287n\u028C\u028Dx\u028Ez';
-
- function makeLonger(val) {
- return val.replace(reVowels, function(match) {
- return match + match.toLowerCase();
- });
- }
-
- function replaceChars(map, val) {
- // Replace each Latin letter with a Unicode character from map
- return val.replace(reAlphas, function(match) {
- return map.charAt(match.charCodeAt(0) - 65);
- });
- }
-
- var reWords = /[^\W0-9_]+/g;
-
- function makeRTL(val) {
- // Surround each word with Unicode formatting codes, RLO and PDF:
- // U+202E: RIGHT-TO-LEFT OVERRIDE (RLO)
- // U+202C: POP DIRECTIONAL FORMATTING (PDF)
- // See http://www.w3.org/International/questions/qa-bidi-controls
- return val.replace(reWords, function(match) {
- return '\u202e' + match + '\u202c';
- });
- }
-
- // strftime tokens (%a, %Eb), template {vars}, HTML entities ()
- // and HTML tags.
- var reExcluded = /(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;
-
- function mapContent(fn, val) {
- if (!val) {
- return val;
- }
- var parts = val.split(reExcluded);
- var modified = parts.map(function(part) {
- if (reExcluded.test(part)) {
- return part;
- }
- return fn(part);
- });
- return modified.join('');
- }
-
- function Pseudo(id, name, charMap, modFn) {
- this.id = id;
- this.translate = mapContent.bind(null, function(val) {
- return replaceChars(charMap, modFn(val));
- });
- this.name = this.translate(name);
- }
-
- var PSEUDO = {
- 'fr-x-psaccent': new Pseudo('fr-x-psaccent', 'Runtime Accented',
- ACCENTED_MAP, makeLonger),
- 'ar-x-psbidi': new Pseudo('ar-x-psbidi', 'Runtime Bidi',
- FLIPPED_MAP, makeRTL)
- };
-
-
-
- function Locale(id, ctx) {
- this.id = id;
- this.ctx = ctx;
- this.isReady = false;
- this.entries = Object.create(null);
- this.entries.__plural = getPluralRule(this.isPseudo() ?
- this.ctx.defaultLocale : id);
- }
-
- Locale.prototype.isPseudo = function() {
- return this.ctx.qps.indexOf(this.id) !== -1;
- };
-
- var bindingsIO = {
- extra: function(id, ver, path, type, callback, errback) {
- if (type === 'properties') {
- type = 'text';
- }
- navigator.mozApps.getLocalizationResource(id, ver, path, type).
- then(callback.bind(null, null), errback);
- },
- app: function(id, ver, path, type, callback, errback, sync) {
- switch (type) {
- case 'properties':
- io.load(path, callback, sync);
- break;
- case 'json':
- io.loadJSON(path, callback, sync);
- break;
- }
- },
- };
-
- Locale.prototype.build = function L_build(callback) {
- var sync = !callback;
- var ctx = this.ctx;
- var self = this;
-
- var l10nLoads = ctx.resLinks.length;
-
- function onL10nLoaded(err) {
- if (err) {
- ctx._emitter.emit('fetcherror', err);
- }
- if (--l10nLoads <= 0) {
- self.isReady = true;
- if (callback) {
- callback();
- }
- }
- }
-
- if (l10nLoads === 0) {
- onL10nLoaded();
- return;
- }
-
- function onJSONLoaded(err, json) {
- if (!err && json) {
- self.addAST(json);
- }
- onL10nLoaded(err);
- }
-
- function onPropLoaded(err, source) {
- if (!err && source) {
- var ast = PropertiesParser.parse(ctx, source);
- self.addAST(ast);
- }
- onL10nLoaded(err);
- }
-
- var idToFetch = this.isPseudo() ? ctx.defaultLocale : this.id;
- var appVersion = null;
- var source = 'app';
- if (typeof(navigator) !== 'undefined') {
- source = navigator.mozL10n._config.localeSources[this.id] || 'app';
- appVersion = navigator.mozL10n._config.appVersion;
- }
-
- for (var i = 0; i < ctx.resLinks.length; i++) {
- var resLink = decodeURI(ctx.resLinks[i]);
- var path = resLink.replace('{locale}', idToFetch);
- var type = path.substr(path.lastIndexOf('.') + 1);
-
- var cb;
- switch (type) {
- case 'json':
- cb = onJSONLoaded;
- break;
- case 'properties':
- cb = onPropLoaded;
- break;
- }
- bindingsIO[source](this.id,
- appVersion, path, type, cb, onL10nLoaded, sync);
- }
- };
-
- function createPseudoEntry(node, entries) {
- return Resolver.createEntry(
- walkContent(node, PSEUDO[this.id].translate),
- entries);
- }
-
- Locale.prototype.addAST = function(ast) {
- /* jshint -W084 */
-
- var createEntry = this.isPseudo() ?
- createPseudoEntry.bind(this) : Resolver.createEntry;
-
- for (var i = 0; i < ast.length; i++) {
- this.entries[ast[i].$i] = createEntry(ast[i], this.entries);
- }
- };
-
-
-
-
- function Context(id) {
- this.id = id;
- this.isReady = false;
- this.isLoading = false;
-
- this.defaultLocale = 'en-US';
- this.availableLocales = [];
- this.supportedLocales = [];
- this.qps = [];
-
- this.resLinks = [];
- this.locales = {};
-
- this._emitter = new EventEmitter();
- this._ready = new Promise(this.once.bind(this));
- }
-
-
- // Getting translations
-
- function reportMissing(id, err) {
- this._emitter.emit('notfounderror', err);
- return id;
- }
-
- function getWithFallback(id) {
- /* jshint -W084 */
- var cur = 0;
- var loc;
- var locale;
- while (loc = this.supportedLocales[cur]) {
- locale = this.getLocale(loc);
- if (!locale.isReady) {
- // build without callback, synchronously
- locale.build(null);
- }
- var entry = locale.entries[id];
- if (entry === undefined) {
- cur++;
- reportMissing.call(this, id, new L10nError(
- '"' + id + '"' + ' not found in ' + loc + ' in ' + this.id,
- id, loc));
- continue;
- }
- return entry;
- }
-
- throw new L10nError(
- '"' + id + '"' + ' missing from all supported locales in ' + this.id, id);
- }
-
- function formatTuple(args, entity) {
- try {
- return Resolver.format(args, entity);
- } catch (err) {
- this._emitter.emit('resolveerror', err);
- var locals = {
- error: err
- };
- return [locals, entity.id];
- }
- }
-
- function formatValue(args, entity) {
- if (typeof entity === 'string') {
- return entity;
- }
-
- // take the string value only
- return formatTuple.call(this, args, entity)[1];
- }
-
- function formatEntity(args, entity) {
- var entityTuple = formatTuple.call(this, args, entity);
- var value = entityTuple[1];
-
- var formatted = {
- value: value,
- attrs: null,
- };
-
- if (entity.attrs) {
- formatted.attrs = Object.create(null);
- }
-
- for (var key in entity.attrs) {
- /* jshint -W089 */
- var attrTuple = formatTuple.call(this, args, entity.attrs[key]);
- formatted.attrs[key] = attrTuple[1];
- }
-
- return formatted;
- }
-
- function formatAsync(fn, id, args) {
- return this._ready.then(
- getWithFallback.bind(this, id)).then(
- fn.bind(this, args),
- reportMissing.bind(this, id));
- }
-
- Context.prototype.formatValue = function(id, args) {
- return formatAsync.call(this, formatValue, id, args);
- };
-
- Context.prototype.formatEntity = function(id, args) {
- return formatAsync.call(this, formatEntity, id, args);
- };
-
- function legacyGet(fn, id, args) {
- if (!this.isReady) {
- throw new L10nError('Context not ready');
- }
-
- var entry;
- try {
- entry = getWithFallback.call(this, id);
- } catch (err) {
- // Don't handle notfounderrors in individual locales in any special way
- if (err.loc) {
- throw err;
- }
- // For general notfounderrors, report them and return legacy fallback
- reportMissing.call(this, id, err);
- // XXX legacy compat; some Gaia code checks if returned value is falsy or
- // an empty string to know if a translation is available; this is bad and
- // will be fixed eventually in https://bugzil.la/1020138
- return '';
- }
-
- // If translation is broken use regular fallback-on-id approach
- return fn.call(this, args, entry);
- }
-
- Context.prototype.get = function(id, args) {
- return legacyGet.call(this, formatValue, id, args);
- };
-
- Context.prototype.getEntity = function(id, args) {
- return legacyGet.call(this, formatEntity, id, args);
- };
-
- Context.prototype.getLocale = function getLocale(code) {
- /* jshint -W093 */
-
- var locales = this.locales;
- if (locales[code]) {
- return locales[code];
- }
-
- return locales[code] = new Locale(code, this);
- };
-
-
- // Getting ready
-
- function negotiate(available, requested, defaultLocale) {
- var supportedLocale;
- // Find the first locale in the requested list that is supported.
- for (var i = 0; i < requested.length; i++) {
- var locale = requested[i];
- if (available.indexOf(locale) !== -1) {
- supportedLocale = locale;
- break;
- }
- }
- if (!supportedLocale ||
- supportedLocale === defaultLocale) {
- return [defaultLocale];
- }
-
- return [supportedLocale, defaultLocale];
- }
-
- function freeze(supported) {
- var locale = this.getLocale(supported[0]);
- if (locale.isReady) {
- setReady.call(this, supported);
- } else {
- locale.build(setReady.bind(this, supported));
- }
- }
-
- function setReady(supported) {
- this.supportedLocales = supported;
- this.isReady = true;
- this._emitter.emit('ready');
- }
-
- Context.prototype.registerLocales = function(defLocale, available) {
-
- if (defLocale) {
- this.defaultLocale = defLocale;
- }
- /* jshint boss:true */
- this.availableLocales = [this.defaultLocale];
- this.qps = Object.keys(PSEUDO);
-
- if (available) {
- for (var i = 0, loc; loc = available[i]; i++) {
- if (this.availableLocales.indexOf(loc) === -1) {
- this.availableLocales.push(loc);
- var pos = this.qps.indexOf(loc);
- if (pos !== -1) {
- // remove from this context's runtime pseudolocales
- this.qps.splice(pos, 1);
- }
- }
- }
- }
- };
-
- Context.prototype.requestLocales = function requestLocales() {
- if (this.isLoading && !this.isReady) {
- throw new L10nError('Context not ready');
- }
-
- this.isLoading = true;
- var requested = Array.prototype.slice.call(arguments);
- if (requested.length === 0) {
- throw new L10nError('No locales requested');
- }
-
- var supported = negotiate(
- this.availableLocales.concat(this.qps),
- requested,
- this.defaultLocale);
-
- // freeze only if the first language in the fallback chain is new
- if (this.supportedLocales[0] !== supported[0]) {
- freeze.call(this, supported);
- }
- };
-
-
- // Events
-
- Context.prototype.addEventListener = function(type, listener) {
- this._emitter.addEventListener(type, listener);
- };
-
- Context.prototype.removeEventListener = function(type, listener) {
- this._emitter.removeEventListener(type, listener);
- };
-
- Context.prototype.ready = function(callback) {
- if (this.isReady) {
- setTimeout(callback);
- }
- this.addEventListener('ready', callback);
- };
-
- Context.prototype.once = function(callback) {
- /* jshint -W068 */
- if (this.isReady) {
- setTimeout(callback);
- return;
- }
-
- var callAndRemove = (function() {
- this.removeEventListener('ready', callAndRemove);
- callback();
- }).bind(this);
- this.addEventListener('ready', callAndRemove);
- };
-
-
-
- var allowed = {
- elements: [
- 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
- 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u',
- 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr'
- ],
- attributes: {
- global: [ 'title', 'aria-label', 'aria-valuetext', 'aria-moz-hint' ],
- a: [ 'download' ],
- area: [ 'download', 'alt' ],
- // value is special-cased in isAttrAllowed
- input: [ 'alt', 'placeholder' ],
- menuitem: [ 'label' ],
- menu: [ 'label' ],
- optgroup: [ 'label' ],
- option: [ 'label' ],
- track: [ 'label' ],
- img: [ 'alt' ],
- textarea: [ 'placeholder' ],
- th: [ 'abbr']
- }
- };
-
-
-
- var rtlList = ['ar', 'he', 'fa', 'ps', 'ar-x-psbidi', 'ur'];
- var nodeObserver = null;
- var pendingElements = null;
-
- var moConfig = {
- attributes: true,
- characterData: false,
- childList: true,
- subtree: true,
- attributeFilter: ['data-l10n-id', 'data-l10n-args']
- };
-
- // Public API
-
- navigator.mozL10n = {
- ctx: null,
- get: function get(id, ctxdata) {
- return navigator.mozL10n.ctx.get(id, ctxdata);
- },
- formatValue: function(id, ctxdata) {
- return navigator.mozL10n.ctx.formatValue(id, ctxdata);
- },
- formatEntity: function(id, ctxdata) {
- return navigator.mozL10n.ctx.formatEntity(id, ctxdata);
- },
- translateFragment: function (fragment) {
- return translateFragment.call(navigator.mozL10n, fragment);
- },
- setAttributes: setL10nAttributes,
- getAttributes: getL10nAttributes,
- ready: function ready(callback) {
- return navigator.mozL10n.ctx.ready(callback);
- },
- once: function once(callback) {
- return navigator.mozL10n.ctx.once(callback);
- },
- get readyState() {
- return navigator.mozL10n.ctx.isReady ? 'complete' : 'loading';
- },
- language: {
- set code(lang) {
- navigator.mozL10n.ctx.requestLocales(lang);
- },
- get code() {
- return navigator.mozL10n.ctx.supportedLocales[0];
- },
- get direction() {
- return getDirection(navigator.mozL10n.ctx.supportedLocales[0]);
- }
- },
- qps: PSEUDO,
- _config: {
- appVersion: null,
- localeSources: Object.create(null),
- isPretranslated: false,
- },
- _getInternalAPI: function() {
- return {
- Error: L10nError,
- Context: Context,
- Locale: Locale,
- Resolver: Resolver,
- getPluralRule: getPluralRule,
- rePlaceables: rePlaceables,
- translateDocument: translateDocument,
- onMetaInjected: onMetaInjected,
- PropertiesParser: PropertiesParser,
- walkContent: walkContent,
- buildLocaleList: buildLocaleList
- };
- }
- };
-
- function getDirection(lang) {
- return (rtlList.indexOf(lang) >= 0) ? 'rtl' : 'ltr';
- }
-
- var readyStates = {
- loading: 0,
- interactive: 1,
- complete: 2
- };
-
- function whenInteractive(callback) {
- if (readyStates[document.readyState] >= readyStates.interactive) {
- callback();
- return;
- }
-
- document.addEventListener('readystatechange', function l10n_onrsc() {
- if (readyStates[document.readyState] >= readyStates.interactive) {
- document.removeEventListener('readystatechange', l10n_onrsc);
- callback();
- }
- });
- }
-
- function initObserver() {
- nodeObserver = new MutationObserver(onMutations.bind(navigator.mozL10n));
- nodeObserver.observe(document, moConfig);
- }
-
- function init(pretranslate) {
- if (!pretranslate) {
- // initialize MO early to collect nodes injected between now and when
- // resources are loaded because we're not going to translate the whole
- // document once l10n resources are ready
- initObserver();
- }
- initResources.call(navigator.mozL10n);
- }
-
- function initResources() {
- /* jshint boss:true */
-
- var meta = {};
- var nodes = document.head
- .querySelectorAll('link[rel="localization"],' +
- 'meta[name="availableLanguages"],' +
- 'meta[name="defaultLanguage"],' +
- 'meta[name="appVersion"],' +
- 'script[type="application/l10n"]');
- for (var i = 0, node; node = nodes[i]; i++) {
- var type = node.getAttribute('rel') || node.nodeName.toLowerCase();
- switch (type) {
- case 'localization':
- this.ctx.resLinks.push(node.getAttribute('href'));
- break;
- case 'meta':
- onMetaInjected.call(this, node, meta);
- break;
- case 'script':
- onScriptInjected.call(this, node);
- break;
- }
- }
-
- var additionalLanguagesPromise;
-
- if (navigator.mozApps && navigator.mozApps.getAdditionalLanguages) {
- // if the environment supports langpacks, register extra languages…
- additionalLanguagesPromise =
- navigator.mozApps.getAdditionalLanguages().catch(function(e) {
- console.error('Error while loading getAdditionalLanguages', e);
- });
-
- // …and listen to langpacks being added and removed
- document.addEventListener('additionallanguageschange', function(evt) {
- registerLocales.call(this, meta, evt.detail);
- this.ctx.requestLocales.apply(
- this.ctx, navigator.languages || [navigator.language]);
- }.bind(this));
- } else {
- additionalLanguagesPromise = Promise.resolve();
- }
-
- additionalLanguagesPromise.then(function(extraLangs) {
- registerLocales.call(this, meta, extraLangs);
- initLocale.call(this);
- }.bind(this));
- }
-
- function registerLocales(meta, extraLangs) {
- var locales = buildLocaleList.call(this, meta, extraLangs);
- navigator.mozL10n._config.localeSources = locales[1];
- this.ctx.registerLocales(locales[0], Object.keys(locales[1]));
- }
-
- function getMatchingLangpack(appVersion, langpacks) {
- for (var i = 0, langpack; (langpack = langpacks[i]); i++) {
- if (langpack.target === appVersion) {
- return langpack;
- }
- }
- return null;
- }
-
- function buildLocaleList(meta, extraLangs) {
- var loc, lp;
- var localeSources = Object.create(null);
- var defaultLocale = meta.defaultLanguage || this.ctx.defaultLocale;
-
- if (meta.availableLanguages) {
- for (loc in meta.availableLanguages) {
- localeSources[loc] = 'app';
- }
- }
-
- if (extraLangs) {
- for (loc in extraLangs) {
- lp = getMatchingLangpack(this._config.appVersion, extraLangs[loc]);
-
- if (!lp) {
- continue;
- }
- if (!(loc in localeSources) ||
- !meta.availableLanguages[loc] ||
- parseInt(lp.revision) > meta.availableLanguages[loc]) {
- localeSources[loc] = 'extra';
- }
- }
- }
-
- if (!(defaultLocale in localeSources)) {
- localeSources[defaultLocale] = 'app';
- }
- return [defaultLocale, localeSources];
- }
-
- function splitAvailableLanguagesString(str) {
- var langs = {};
-
- str.split(',').forEach(function(lang) {
- // code:revision
- lang = lang.trim().split(':');
- // if revision is missing, use NaN
- langs[lang[0]] = parseInt(lang[1]);
- });
- return langs;
- }
-
- function onMetaInjected(node, meta) {
- switch (node.getAttribute('name')) {
- case 'availableLanguages':
- meta.availableLanguages =
- splitAvailableLanguagesString(node.getAttribute('content'));
- break;
- case 'defaultLanguage':
- meta.defaultLanguage = node.getAttribute('content');
- break;
- case 'appVersion':
- navigator.mozL10n._config.appVersion = node.getAttribute('content');
- break;
- }
- }
-
- function onScriptInjected(node) {
- var lang = node.getAttribute('lang');
- var locale = this.ctx.getLocale(lang);
- locale.addAST(JSON.parse(node.textContent));
- }
-
- function initLocale() {
- this.ctx.requestLocales.apply(
- this.ctx, navigator.languages || [navigator.language]);
- window.addEventListener('languagechange', function l10n_langchange() {
- this.ctx.requestLocales.apply(
- this.ctx, navigator.languages || [navigator.language]);
- }.bind(this));
- }
-
- function localizeMutations(mutations) {
- var mutation;
- var targets = new Set();
-
- for (var i = 0; i < mutations.length; i++) {
- mutation = mutations[i];
- if (mutation.type === 'childList') {
- var addedNode;
-
- for (var j = 0; j < mutation.addedNodes.length; j++) {
- addedNode = mutation.addedNodes[j];
- if (addedNode.nodeType !== Node.ELEMENT_NODE) {
- continue;
- }
- targets.add(addedNode);
- }
- }
-
- if (mutation.type === 'attributes') {
- targets.add(mutation.target);
- }
- }
-
- targets.forEach(function(target) {
- if (target.childElementCount) {
- translateFragment.call(this, target);
- } else if (target.hasAttribute('data-l10n-id')) {
- translateElement.call(this, target);
- }
- }, this);
- }
-
- function onMutations(mutations, self) {
- self.disconnect();
- localizeMutations.call(this, mutations);
- self.observe(document, moConfig);
- }
-
- function onReady() {
- if (!navigator.mozL10n._config.isPretranslated) {
- translateDocument.call(this);
- }
- navigator.mozL10n._config.isPretranslated = false;
-
- if (pendingElements) {
- /* jshint boss:true */
- for (var i = 0, element; element = pendingElements[i]; i++) {
- translateElement.call(this, element);
- }
- pendingElements = null;
- }
-
- if (!nodeObserver) {
- initObserver();
- }
- fireLocalizedEvent.call(this);
- }
-
- function fireLocalizedEvent() {
- var event = new CustomEvent('localized', {
- 'bubbles': false,
- 'cancelable': false,
- 'detail': {
- 'language': this.ctx.supportedLocales[0]
- }
- });
- window.dispatchEvent(event);
- }
-
-
- // match the opening angle bracket (<) in HTML tags, and HTML entities like
- // &, &, &.
- var reOverlay = /<|?\w+;/;
- var reHtml = /[&<>]/g;
- var htmlEntities = {
- '&': '&',
- '<': '<',
- '>': '>',
- };
-
- function translateDocument() {
- document.documentElement.lang = this.language.code;
- document.documentElement.dir = this.language.direction;
- translateFragment.call(this, document.documentElement);
- }
-
- function translateFragment(element) {
- if (typeof element.hasAttribute === 'function' &&
- element.hasAttribute('data-l10n-id')) {
- translateElement.call(this, element);
- }
-
- var nodes = getTranslatableChildren(element);
- for (var i = 0; i < nodes.length; i++ ) {
- translateElement.call(this, nodes[i]);
- }
- }
-
- function setL10nAttributes(element, id, args) {
- element.setAttribute('data-l10n-id', id);
- if (args) {
- element.setAttribute('data-l10n-args', JSON.stringify(args));
- }
- }
-
- function getL10nAttributes(element) {
- return {
- id: element.getAttribute('data-l10n-id'),
- args: JSON.parse(element.getAttribute('data-l10n-args'))
- };
- }
-
- function getTranslatableChildren(element) {
- return element ? element.querySelectorAll('*[data-l10n-id]') : [];
- }
-
- function camelCaseToDashed(string) {
- // XXX workaround for https://bugzil.la/1141934
- if (string === 'ariaValueText') {
- return 'aria-valuetext';
- }
-
- return string
- .replace(/[A-Z]/g, function (match) {
- return '-' + match.toLowerCase();
- })
- .replace(/^-/, '');
- }
-
- function escapeL10nArgs(match) {
- return htmlEntities[match];
- }
-
- function translateElement(element) {
- if (!this.ctx.isReady) {
- if (!pendingElements) {
- pendingElements = [];
- }
- pendingElements.push(element);
- return;
- }
-
- var l10nId = element.getAttribute('data-l10n-id');
-
- if (!l10nId) {
- return false;
- }
-
- var l10nArgs = element.getAttribute('data-l10n-args');
-
- var entity = this.ctx.getEntity(
- l10nId,
- l10nArgs ?
- JSON.parse(l10nArgs.replace(reHtml, escapeL10nArgs)) :
- undefined
- );
-
- var value = entity.value;
-
- if (typeof value === 'string') {
- if (!reOverlay.test(value)) {
- element.textContent = value;
- } else {
- // start with an inert template element and move its children into
- // `element` but such that `element`'s own children are not replaced
- var translation = element.ownerDocument.createElement('template');
- translation.innerHTML = value;
- // overlay the node with the DocumentFragment
- overlayElement(element, translation.content);
- }
- }
-
- for (var key in entity.attrs) {
- var attrName = camelCaseToDashed(key);
- if (isAttrAllowed({ name: attrName }, element)) {
- element.setAttribute(attrName, entity.attrs[key]);
- }
- }
- }
-
- // The goal of overlayElement is to move the children of `translationElement`
- // into `sourceElement` such that `sourceElement`'s own children are not
- // replaced, but onle have their text nodes and their attributes modified.
- //
- // We want to make it possible for localizers to apply text-level semantics to
- // the translations and make use of HTML entities. At the same time, we
- // don't trust translations so we need to filter unsafe elements and
- // attribtues out and we don't want to break the Web by replacing elements to
- // which third-party code might have created references (e.g. two-way
- // bindings in MVC frameworks).
- function overlayElement(sourceElement, translationElement) {
- var result = translationElement.ownerDocument.createDocumentFragment();
- var k, attr;
-
- // take one node from translationElement at a time and check it against
- // the allowed list or try to match it with a corresponding element
- // in the source
- var childElement;
- while ((childElement = translationElement.childNodes[0])) {
- translationElement.removeChild(childElement);
-
- if (childElement.nodeType === Node.TEXT_NODE) {
- result.appendChild(childElement);
- continue;
- }
-
- var index = getIndexOfType(childElement);
- var sourceChild = getNthElementOfType(sourceElement, childElement, index);
- if (sourceChild) {
- // there is a corresponding element in the source, let's use it
- overlayElement(sourceChild, childElement);
- result.appendChild(sourceChild);
- continue;
- }
-
- if (isElementAllowed(childElement)) {
- var sanitizedChild = childElement.ownerDocument.createElement(
- childElement.nodeName);
- overlayElement(sanitizedChild, childElement);
- result.appendChild(sanitizedChild);
- continue;
- }
-
- // otherwise just take this child's textContent
- result.appendChild(
- document.createTextNode(childElement.textContent));
- }
-
- // clear `sourceElement` and append `result` which by this time contains
- // `sourceElement`'s original children, overlayed with translation
- sourceElement.textContent = '';
- sourceElement.appendChild(result);
-
- // if we're overlaying a nested element, translate the allowed
- // attributes; top-level attributes are handled in `translateElement`
- // XXX attributes previously set here for another language should be
- // cleared if a new language doesn't use them; https://bugzil.la/922577
- if (translationElement.attributes) {
- for (k = 0, attr; (attr = translationElement.attributes[k]); k++) {
- if (isAttrAllowed(attr, sourceElement)) {
- sourceElement.setAttribute(attr.name, attr.value);
- }
- }
- }
- }
-
- // XXX the allowed list should be amendable; https://bugzil.la/922573
- function isElementAllowed(element) {
- return allowed.elements.indexOf(element.tagName.toLowerCase()) !== -1;
- }
-
- function isAttrAllowed(attr, element) {
- var attrName = attr.name.toLowerCase();
- var tagName = element.tagName.toLowerCase();
- // is it a globally safe attribute?
- if (allowed.attributes.global.indexOf(attrName) !== -1) {
- return true;
- }
- // are there no allowed attributes for this element?
- if (!allowed.attributes[tagName]) {
- return false;
- }
- // is it allowed on this element?
- // XXX the allowed list should be amendable; https://bugzil.la/922573
- if (allowed.attributes[tagName].indexOf(attrName) !== -1) {
- return true;
- }
- // special case for value on inputs with type button, reset, submit
- if (tagName === 'input' && attrName === 'value') {
- var type = element.type.toLowerCase();
- if (type === 'submit' || type === 'button' || type === 'reset') {
- return true;
- }
- }
- return false;
- }
-
- // Get n-th immediate child of context that is of the same type as element.
- // XXX Use querySelector(':scope > ELEMENT:nth-of-type(index)'), when:
- // 1) :scope is widely supported in more browsers and 2) it works with
- // DocumentFragments.
- function getNthElementOfType(context, element, index) {
- /* jshint boss:true */
- var nthOfType = 0;
- for (var i = 0, child; child = context.children[i]; i++) {
- if (child.nodeType === Node.ELEMENT_NODE &&
- child.tagName === element.tagName) {
- if (nthOfType === index) {
- return child;
- }
- nthOfType++;
- }
- }
- return null;
- }
-
- // Get the index of the element among siblings of the same type.
- function getIndexOfType(element) {
- var index = 0;
- var child;
- while ((child = element.previousElementSibling)) {
- if (child.tagName === element.tagName) {
- index++;
- }
- }
- return index;
- }
-
-
- var DEBUG = false;
-
- navigator.mozL10n.ctx = new Context(window.document ? document.URL : null);
- navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n));
-
- navigator.mozL10n.ctx.addEventListener('notfounderror',
- function reportMissingEntity(e) {
- if (DEBUG || e.loc === 'en-US') {
- console.warn(e.toString());
- }
- });
-
- if (DEBUG) {
- navigator.mozL10n.ctx.addEventListener('fetcherror',
- console.error.bind(console));
- navigator.mozL10n.ctx.addEventListener('parseerror',
- console.error.bind(console));
- navigator.mozL10n.ctx.addEventListener('resolveerror',
- console.error.bind(console));
- }
-
- if (window.document) {
- navigator.mozL10n._config.isPretranslated =
- document.documentElement.lang === navigator.language;
-
- var forcePretranslate = !navigator.mozL10n._config.isPretranslated;
- whenInteractive(init.bind(navigator.mozL10n, forcePretranslate));
- }
-
- document.l10n = {
- setAttributes: navigator.mozL10n.setAttributes,
- getAttributes: navigator.mozL10n.getAttributes,
- formatValue: function(id, args) {
- return navigator.mozL10n.formatValue(id, args);
- },
- translateFragment: function (frag) {
- return Promise.resolve(navigator.mozL10n.translateFragment(frag));
- },
- ready: new Promise(function(resolve) {
- navigator.mozL10n.once(resolve);
- }),
- formatValues: function() {
- var keys = arguments;
- var resp = keys.map(function(key) {
- if (Array.isArray(key)) {
- return navigator.mozL10n.formatValue(key[0], key[1]);
- }
- return navigator.mozL10n.formatValue(key);
- });
-
- return Promise.all(resp);
- },
- requestLanguages: function(langs) {
- // XXX real l20n returns a promise
- navigator.mozL10n.ctx.requestLocales.apply(
- navigator.mozL10n.ctx, langs);
- },
- pseudo: {
- 'fr-x-psaccent': {
- getName: function() {
- return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].name);
- },
- processString: function(s) {
- return Promise.resolve(
- navigator.mozL10n.qps['fr-x-psaccent'].translate(s));
- }
- },
- 'ar-x-psbidi': {
- getName: function() {
- return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].name);
- },
- processString: function(s) {
- return Promise.resolve(
- navigator.mozL10n.qps['ar-x-psbidi'].translate(s));
- }
- }
- },
- };
-
- navigator.mozL10n.ready(function() {
- document.documentElement.setAttribute(
- 'langs', navigator.mozL10n.ctx.supportedLocales.join(' '));
- });
-
- navigator.mozL10n.once(function() {
- window.addEventListener('localized', function() {
- document.dispatchEvent(new CustomEvent('DOMRetranslated', {
- bubbles: false,
- cancelable: false
- }));
- });
- });
-
-})(this);
diff --git a/src/offline/locales/marketplace-offline.en-US.properties b/src/offline/locales/marketplace-offline.en-US.properties
deleted file mode 100644
index 6df506c..0000000
--- a/src/offline/locales/marketplace-offline.en-US.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-web-apps=Web Apps
-you-are-offline-right-now-please-connect-to-internet=You are offline right now, please connect to internet.
-exit=Exit