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