diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/static/js/html10n.js | 825 | ||||
-rw-r--r-- | src/static/js/l10n.js | 1028 | ||||
-rw-r--r-- | src/templates/index.html | 14 | ||||
-rw-r--r-- | src/templates/pad.html | 10 | ||||
-rw-r--r-- | src/templates/timeslider.html | 13 |
5 files changed, 835 insertions, 1055 deletions
diff --git a/src/static/js/html10n.js b/src/static/js/html10n.js new file mode 100644 index 00000000..c36dcc9f --- /dev/null +++ b/src/static/js/html10n.js @@ -0,0 +1,825 @@ +/** + * Copyright (c) 2012 Marcel Klehr + * Copyright (c) 2011-2012 Fabien Cazenave, Mozilla + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +window.html10n = (function(window, document, undefined) { + var consoleLog = console? console.log : function() {} + , consoleWarn = console? console.warn : function() {} + + /** + * MicroEvent - to make any js object an event emitter (server or browser) + */ + + var MicroEvent = function(){} + MicroEvent.prototype = { + bind : function(event, fct){ + this._events = this._events || {}; + this._events[event] = this._events[event] || []; + this._events[event].push(fct); + }, + unbind : function(event, fct){ + this._events = this._events || {}; + if( event in this._events === false ) return; + this._events[event].splice(this._events[event].indexOf(fct), 1); + }, + trigger : function(event /* , args... */){ + this._events = this._events || {}; + if( event in this._events === false ) return; + for(var i = 0; i < this._events[event].length; i++){ + this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)) + } + } + }; + /** + * mixin will delegate all MicroEvent.js function in the destination object + * @param {Object} the object which will support MicroEvent + */ + MicroEvent.mixin = function(destObject){ + var props = ['bind', 'unbind', 'trigger']; + if(!destObject.prototype) destObject.prototype = {}; + for(var i = 0; i < props.length; i ++){ + destObject.prototype[props[i]] = MicroEvent.prototype[props[i]]; + } + } + + /** + * Loader + * The loader is responsible for loading + * and caching all necessary resources + */ + function Loader(resources) { + this.resources = resources + this.cache = {} // file => contents + this.langs = {} // lang => strings + } + + Loader.prototype.load = function(lang, cb) { + if(this.langs[lang]) return cb() + + if (this.resources.length > 0) { + var reqs = 0; + for (var i=0, n=this.resources.length; i < n; i++) { + this.fetch(this.resources[i], lang, function(e) { + reqs++; + if(e) setTimeout(function(){ throw e }, 0) + + if (reqs < n) return;// Call back once all reqs are completed + cb() + }) + } + } + } + + Loader.prototype.fetch = function(href, lang, cb) { + var that = this + + if (this.cache[href]) { + this.parse(this.cache[href]) + cb() + return; + } + + var xhr = new XMLHttpRequest() + xhr.open('GET', href, /*async: */true) + if (xhr.overrideMimeType) { + xhr.overrideMimeType('application/json; charset=utf-8'); + } + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200 || xhr.status === 0) { + var data = JSON.parse(xhr.responseText) + that.cache[href] = data + // Pass on the contents for parsing + this.parse(lang, data, cb) + } else { + cb(new Error('Failed to load '+href)) + } + } + }; + xhr.send(null); + } + + Loader.prototype.parse = function(lang, data, cb) { + if ('object' != typeof data) { + cb(new Error('A file couldn\'t be parsed as json.')) + return + } + + if (!data[lang]) { + cb(new Error('Couldn\'t find translations for '+lang)) + return + } + + if ('string' == typeof data[lang]) { + // Import rule + this.fetch(data[lang], lang, cb) + return + } + + if ('object' != typeof data[lang]) { + cb(new Error('Translations should be specified as JSON objects!')) + return + } + + this.langs[lang] = data[lang] + // TODO: Also store accompanying langs + cb() + } + + + /** + * The html10n object + */ + var html10n = + { language: null + } + MicroEvent.mixin(html10n) + + html10n.macros = {} + + /** + * Get rules for plural forms (shared with JetPack), see: + * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p + * + * @param {string} lang + * locale (language) used. + * + * @return {Function} + * returns a function that gives the plural form name for a given integer: + * var fun = getPluralRules('en'); + * fun(1) -> 'one' + * fun(0) -> 'other' + * fun(1000) -> 'other'. + */ + function getPluralRules(lang) { + var locales2rules = { + 'af': 3, + 'ak': 4, + 'am': 4, + 'ar': 1, + 'asa': 3, + 'az': 0, + 'be': 11, + 'bem': 3, + 'bez': 3, + 'bg': 3, + 'bh': 4, + 'bm': 0, + 'bn': 3, + 'bo': 0, + 'br': 20, + 'brx': 3, + 'bs': 11, + 'ca': 3, + 'cgg': 3, + 'chr': 3, + 'cs': 12, + 'cy': 17, + 'da': 3, + 'de': 3, + 'dv': 3, + 'dz': 0, + 'ee': 3, + 'el': 3, + 'en': 3, + 'eo': 3, + 'es': 3, + 'et': 3, + 'eu': 3, + 'fa': 0, + 'ff': 5, + 'fi': 3, + 'fil': 4, + 'fo': 3, + 'fr': 5, + 'fur': 3, + 'fy': 3, + 'ga': 8, + 'gd': 24, + 'gl': 3, + 'gsw': 3, + 'gu': 3, + 'guw': 4, + 'gv': 23, + 'ha': 3, + 'haw': 3, + 'he': 2, + 'hi': 4, + 'hr': 11, + 'hu': 0, + 'id': 0, + 'ig': 0, + 'ii': 0, + 'is': 3, + 'it': 3, + 'iu': 7, + 'ja': 0, + 'jmc': 3, + 'jv': 0, + 'ka': 0, + 'kab': 5, + 'kaj': 3, + 'kcg': 3, + 'kde': 0, + 'kea': 0, + 'kk': 3, + 'kl': 3, + 'km': 0, + 'kn': 0, + 'ko': 0, + 'ksb': 3, + 'ksh': 21, + 'ku': 3, + 'kw': 7, + 'lag': 18, + 'lb': 3, + 'lg': 3, + 'ln': 4, + 'lo': 0, + 'lt': 10, + 'lv': 6, + 'mas': 3, + 'mg': 4, + 'mk': 16, + 'ml': 3, + 'mn': 3, + 'mo': 9, + 'mr': 3, + 'ms': 0, + 'mt': 15, + 'my': 0, + 'nah': 3, + 'naq': 7, + 'nb': 3, + 'nd': 3, + 'ne': 3, + 'nl': 3, + 'nn': 3, + 'no': 3, + 'nr': 3, + 'nso': 4, + 'ny': 3, + 'nyn': 3, + 'om': 3, + 'or': 3, + 'pa': 3, + 'pap': 3, + 'pl': 13, + 'ps': 3, + 'pt': 3, + 'rm': 3, + 'ro': 9, + 'rof': 3, + 'ru': 11, + 'rwk': 3, + 'sah': 0, + 'saq': 3, + 'se': 7, + 'seh': 3, + 'ses': 0, + 'sg': 0, + 'sh': 11, + 'shi': 19, + 'sk': 12, + 'sl': 14, + 'sma': 7, + 'smi': 7, + 'smj': 7, + 'smn': 7, + 'sms': 7, + 'sn': 3, + 'so': 3, + 'sq': 3, + 'sr': 11, + 'ss': 3, + 'ssy': 3, + 'st': 3, + 'sv': 3, + 'sw': 3, + 'syr': 3, + 'ta': 3, + 'te': 3, + 'teo': 3, + 'th': 0, + 'ti': 4, + 'tig': 3, + 'tk': 3, + 'tl': 4, + 'tn': 3, + 'to': 0, + 'tr': 0, + 'ts': 3, + 'tzm': 22, + 'uk': 11, + 'ur': 3, + 've': 3, + 'vi': 0, + 'vun': 3, + 'wa': 4, + 'wae': 3, + 'wo': 0, + 'xh': 3, + 'xog': 3, + 'yo': 0, + 'zh': 0, + 'zu': 3 + }; + + // utility functions for plural rules methods + function isIn(n, list) { + return list.indexOf(n) !== -1; + } + function isBetween(n, start, end) { + return start <= n && n <= end; + } + + // list of all plural rules methods: + // map an integer to the plural form name to use + var pluralRules = { + '0': function(n) { + return 'other'; + }, + '1': function(n) { + if ((isBetween((n % 100), 3, 10))) + return 'few'; + if (n === 0) + return 'zero'; + if ((isBetween((n % 100), 11, 99))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '2': function(n) { + if (n !== 0 && (n % 10) === 0) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '3': function(n) { + if (n == 1) + return 'one'; + return 'other'; + }, + '4': function(n) { + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '5': function(n) { + if ((isBetween(n, 0, 2)) && n != 2) + return 'one'; + return 'other'; + }, + '6': function(n) { + if (n === 0) + return 'zero'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '7': function(n) { + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '8': function(n) { + if ((isBetween(n, 3, 6))) + return 'few'; + if ((isBetween(n, 7, 10))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '9': function(n) { + if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '10': function(n) { + if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) + return 'few'; + if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) + return 'one'; + return 'other'; + }, + '11': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if ((n % 10) === 0 || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 11, 14))) + return 'many'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '12': function(n) { + if ((isBetween(n, 2, 4))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '13': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if (n != 1 && (isBetween((n % 10), 0, 1)) || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 12, 14))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '14': function(n) { + if ((isBetween((n % 100), 3, 4))) + return 'few'; + if ((n % 100) == 2) + return 'two'; + if ((n % 100) == 1) + return 'one'; + return 'other'; + }, + '15': function(n) { + if (n === 0 || (isBetween((n % 100), 2, 10))) + return 'few'; + if ((isBetween((n % 100), 11, 19))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '16': function(n) { + if ((n % 10) == 1 && n != 11) + return 'one'; + return 'other'; + }, + '17': function(n) { + if (n == 3) + return 'few'; + if (n === 0) + return 'zero'; + if (n == 6) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '18': function(n) { + if (n === 0) + return 'zero'; + if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) + return 'one'; + return 'other'; + }, + '19': function(n) { + if ((isBetween(n, 2, 10))) + return 'few'; + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '20': function(n) { + if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( + isBetween((n % 100), 10, 19) || + isBetween((n % 100), 70, 79) || + isBetween((n % 100), 90, 99) + )) + return 'few'; + if ((n % 1000000) === 0 && n !== 0) + return 'many'; + if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) + return 'two'; + if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) + return 'one'; + return 'other'; + }, + '21': function(n) { + if (n === 0) + return 'zero'; + if (n == 1) + return 'one'; + return 'other'; + }, + '22': function(n) { + if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) + return 'one'; + return 'other'; + }, + '23': function(n) { + if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) + return 'one'; + return 'other'; + }, + '24': function(n) { + if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) + return 'few'; + if (isIn(n, [2, 12])) + return 'two'; + if (isIn(n, [1, 11])) + return 'one'; + return 'other'; + } + }; + + // return a function that gives the plural form name for a given integer + var index = locales2rules[lang.replace(/-.*$/, '')]; + if (!(index in pluralRules)) { + consoleWarn('plural form unknown for [' + lang + ']'); + return function() { return 'other'; }; + } + return pluralRules[index]; + } + + /** + * pre-defined 'plural' macro + */ + html10n.macros.plural = function(translations, key, str, param) { + var n = parseFloat(param); + if (isNaN(n)) + return str; + + // initialize _pluralRules + if (!this._pluralRules) + this._pluralRules = getPluralRules(html10n.language); + var index = '[' + this._pluralRules(n) + ']'; + + // try to find a [zero|one|two] key if it's defined + if (n === 0 && (key + '[zero]') in translations) { + str = translations[key + '[zero]']; + } else if (n == 1 && (key + '[one]') in translations) { + str = translations[key + '[one]']; + } else if (n == 2 && (key + '[two]') in translations) { + str = translations[key + '[two]']; + } else if ((key + index) in translations) { + str = translations[key + index][prop]; + } + + return str; + }; + + /** + * Localize a document + * @param langs An array of lang codes defining fallbacks + */ + html10n.localize = function(langs) { + // if only one string => create an array + if ('string' == typeof langs) langs = [langs] + + this.build(langs, function(er, translations) { + html10n.translations = translations + html10n.translateElement(translations) + }) + } + + /** + * Triggers the translation process + * for an element + * @param translations A hash of all translation strings + * @param element A DOM element, if omitted, the document element will be used + */ + html10n.translateElement = function(translations, element) { + element = element || document.documentElement + + var children = element? getTranslatableChildren(element) : document.childNodes; + for (var i=0, n=children.length; i < n; i++) { + translateNode(translations, children[i]) + } + + // translate element itself if necessary + translateNode(translations, element) + } + + function asyncForEach(list, iterator, cb) { + var i = 0 + , n = list.length + iterator(list[i], i, function each(err) { + consoleLog(err) + i++ + if (i < n) return iterator(list[i],i, each); + cb() + }) + } + + function getTranslatableChildren(element) { + if(!document.querySelectorAll) { + if (!element) return [] + var nodes = element.getElementsByTagName('*') + , l10nElements = [] + for (var i=0, n=nodes.length; i < n; i++) { + if (nodes[i].getAttribute('data-l10n-id')) + l10nElements.push(nodes[i]); + } + return l10nElements + } + return element.querySelectorAll('*[data-l10n-id]') + } + + html10n.get = function(id, args) { + var translations = html10n.translations + if(!translations[id]) return consoleWarn('Could not find string '+id) + + // apply args + str = substArguments(translations[id], args) + + // apply macros + return substMacros(translations, id, str, args) + + // replace {{arguments}} with their values or the + // associated translation string (based on its key) + function substArguments(str, args) { + var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/ + , match + + while (match = reArgs.exec(str)) { + if (!match || match.length < 2) + return str // argument key not found + + var arg = match[1] + , sub = '' + if (arg in args) { + sub = args[arg] + } else if (arg in translations) { + sub = translations[arg] + } else { + consoleWarn('Could not find argument {{' + arg + '}}') + return str + } + + str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length) + } + + return str + } + + // replace {[macros]} with their values + function substMacros(key, str, args) { + var regex = /\{\[\s*([a-zA-Z]+):([a-zA-Z]+)\s*\]\}/ + , match = regex.exec(str); + if (!match || !match.length) + return str; + + // a macro has been found + // Note: at the moment, only one parameter is supported + var macroName = reMatch[1] + , paramName = reMatch[2] + + if (!(macroName in gMacros)) return str + + var param + if (args && paramName in args) { + param = args[paramName] + } else if (paramName in translations) { + param = translations[paramName] + } + + // there's no macro parser yet: it has to be defined in gMacros + var macro = html10n.macros[macroName] + str = macro(translations, key, str, param) + return str + } + } + + /** + * Applies translations to a DOM node (recursive) + */ + html10n.translateNode = function(translations, node) { + var str = {} + + // get id + str.id = node.getAttribute('data-l10n-id') + if (!str.id) return + if(!translations[str.id]) return + + // get args + if(window.JSON) { + str.args = JSON.parse(node.getAttribute('data-l10n-args')) + }else{ + try{ + str.args = eval(node.getAttribute('data-l10n-args')) + }catch(e) { + consoleWarn('Couldn\'t parse args for '+str.id) + } + } + + str.str = html10n.get(str.id, str.args) + + var prop + , index = str.id.lastIndexOf('.') + , attrList = // allowed attributes + { "title": 1 + , "innerHTML": 1 + , "alt": 1 + , "textContent": 1 + } + // get attribute name to apply str to + if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified + prop = str.id.substr(index + 1) + } else { // no attribute: assuming text content by default + prop = document.body.textContent ? 'textContent' : 'innerText' + } + + // Apply translation + if (node.children.length === 0) { + node[prop] = str.str + } else { + var children = element.childNodes, + found = false + for (var i=0, n=children.length; i < n; i++) { + if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) { + if (!found) { + children[i].nodeValue = str.str + found = true + } else { + children[i].nodeValue = '' + } + } + } + if (!found) { + consoleWarn('Unexpected error: could not translate element content') + } + } + } + + /** + * Builds a translation object from a list of langs (loads the necessary translations) + * @param langs Array - a list of langs sorted by priority (default langs should go last) + */ + html10n.build = function(langs, cb) { + var build = {} + + asyncForEach(langs, function (lang, i, next) { + html10n.loader.load(lang, next) + }, function() { + + langs.reverse() + for (var i=0, n=langs.length; i < n; i++) { + // apply all strings of the current lang in the list + // to our build object + for (var string in this.loader.langs[lang]) { + build[string] = this.loader.langs[lang][string] + } + } + cb(null, build) + }) + } + + /** + * Index all <link>s + */ + html10n.index = function () { + // Find all <link>s + var links = document.getElementsByTagName('link') + for (var i=0, n=links.length; i < n; i++) { + if (links[i].type != 'application/l10n+json') + continue; + resources.push(links[i].href) + } + this.loader = new Loader(resources) + this.trigger('indexed') + } + + if (document.addEventListener) // modern browsers and IE9+ + document.addEventListener('DOMContentLoaded', html10n.index, false) + else if (window.attachEvent) + document.attachEvent('onload', html10n.index, false) + + // gettext-like shortcut + if (window._ === undefined) + var _ = html10n.get; + +})(window, document)
\ No newline at end of file diff --git a/src/static/js/l10n.js b/src/static/js/l10n.js deleted file mode 100644 index ef8218d3..00000000 --- a/src/static/js/l10n.js +++ /dev/null @@ -1,1028 +0,0 @@ -/** Copyright (c) 2011-2012 Fabien Cazenave, Mozilla. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -/*jshint browser: true, devel: true, es5: true, globalstrict: true */ -'use strict'; - -document.webL10n = (function(window, document, undefined) { - var gL10nData = {}; - var gTextData = ''; - var gTextProp = 'textContent'; - var gLanguage = ''; - var gMacros = {}; - var gReadyState = 'loading'; - - // read-only setting -- we recommend to load l10n resources synchronously - var gAsyncResourceLoading = true; - - // debug helpers - var gDEBUG = false; - function consoleLog(message) { - if (gDEBUG) - console.log('[l10n] ' + message); - }; - function consoleWarn(message) { - if (gDEBUG) - console.warn('[l10n] ' + message); - }; - - /** - * DOM helpers for the so-called "HTML API". - * - * These functions are written for modern browsers. For old versions of IE, - * they're overridden in the 'startup' section at the end of this file. - */ - - function getL10nResourceLinks() { - return document.querySelectorAll('link[type="application/l10n"]'); - } - - function getTranslatableChildren(element) { - return element ? element.querySelectorAll('*[data-l10n-id]') : []; - } - - function getL10nAttributes(element) { - if (!element) - return {}; - - var l10nId = element.getAttribute('data-l10n-id'); - var l10nArgs = element.getAttribute('data-l10n-args'); - var args = {}; - if (l10nArgs) { - try { - args = JSON.parse(l10nArgs); - } catch (e) { - consoleWarn('could not parse arguments for #' + l10nId); - } - } - return { id: l10nId, args: args }; - } - - function fireL10nReadyEvent(lang) { - var evtObject = document.createEvent('Event'); - evtObject.initEvent('localized', false, false); - evtObject.language = lang; - window.dispatchEvent(evtObject); - } - - - /** - * l10n resource parser: - * - reads (async XHR) the l10n resource matching `lang'; - * - imports linked resources (synchronously) when specified; - * - parses the text data (fills `gL10nData' and `gTextData'); - * - triggers success/failure callbacks when done. - * - * @param {string} href - * URL of the l10n resource to parse. - * - * @param {string} lang - * locale (language) to parse. - * - * @param {Function} successCallback - * triggered when the l10n resource has been successully parsed. - * - * @param {Function} failureCallback - * triggered when the an error has occured. - * - * @return {void} - * uses the following global variables: gL10nData, gTextData, gTextProp. - */ - - function parseResource(href, lang, successCallback, failureCallback) { - var baseURL = href.replace(/\/[^\/]*$/, '/'); - - // handle escaped characters (backslashes) in a string - function evalString(text) { - if (text.lastIndexOf('\\') < 0) - return text; - return text.replace(/\\\\/g, '\\') - .replace(/\\n/g, '\n') - .replace(/\\r/g, '\r') - .replace(/\\t/g, '\t') - .replace(/\\b/g, '\b') - .replace(/\\f/g, '\f') - .replace(/\\{/g, '{') - .replace(/\\}/g, '}') - .replace(/\\"/g, '"') - .replace(/\\'/g, "'"); - } - - // parse *.properties text data into an l10n dictionary - function parseProperties(text) { - var dictionary = {}; - - // token expressions - var reBlank = /^\s*|\s*$/; - var reComment = /^\s*;|^\s*$/;// Use ; for comments! - var reSection = /^\s*\[(.*)\]\s*$/; - var reImport = /^\s*@import\s+url\((.*)\)\s*$/i; - var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\' - - // parse the *.properties file into an associative array - function parseRawLines(rawText, extendedSyntax) { - var entries = rawText.replace(reBlank, '').split(/[\r\n]+/); - var currentLang = '*'; - var genericLang = lang.replace(/-[a-z]+$/i, ''); - var skipLang = false; - var match = ''; - - for (var i = 0; i < entries.length; i++) { - var line = entries[i]; - - // comment or blank line? - if (reComment.test(line)) - continue; - - // the extended syntax supports [lang] sections and @import rules - if (extendedSyntax) { - if (reSection.test(line)) { // section start? - match = reSection.exec(line); - currentLang = match[1]; - skipLang = (currentLang !== '*') && - (currentLang !== lang) && (currentLang !== genericLang); - continue; - } else if (skipLang) { - continue; - } - if (reImport.test(line)) { // @import rule? - match = reImport.exec(line); - loadImport(baseURL + match[1]); // load the resource synchronously - } - } - - // key-value pair - consoleLog(tmp) - var tmp = line.match(reSplit); - if (tmp && tmp.length == 3) - dictionary[tmp[1]] = evalString(tmp[2]); - } - } - - // import another *.properties file - function loadImport(url) { - loadResource(url, function(content) { - parseRawLines(content, false); // don't allow recursive imports - }, false, false); // load synchronously - } - - // fill the dictionary - parseRawLines(text, true); - return dictionary; - } - - // load the specified resource file - function loadResource(url, onSuccess, onFailure, asynchronous) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, asynchronous); - if (xhr.overrideMimeType) { - xhr.overrideMimeType('text/plain; charset=utf-8'); - } - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - if (xhr.status == 200 || xhr.status === 0) { - if (onSuccess) - onSuccess(xhr.responseText); - } else { - if (onFailure) - onFailure(); - } - } - }; - xhr.send(null); - } - - // load and parse l10n data (warning: global variables are used here) - loadResource(href, function(response) { - gTextData += response; // mostly for debug - - // parse *.properties text data into an l10n dictionary - var data = parseProperties(response); - - // allowed attributes - var attrList = - { "title": 1 - , "innerHTML": 1 - , "alt": 1 - , "textContent": 1 - } - - // find attribute descriptions, if any - for (var key in data) { - var id, prop, index = key.lastIndexOf('.'); - if (index > 0 && key.substr(index + 1) in attrList) { // an attribute has been specified - id = key.substring(0, index); - prop = key.substr(index + 1); - } else { // no attribute: assuming text content by default - id = key; - prop = gTextProp; - } - if (!gL10nData[id]) { - gL10nData[id] = {}; - } - gL10nData[id][prop] = data[key]; - } - - // trigger callback - if (successCallback) - successCallback(); - }, failureCallback, gAsyncResourceLoading); - }; - - // load and parse all resources for the specified locale - function loadLocale(lang, callback) { - clear(); - gLanguage = lang; - - // check all <link type="application/l10n" href="..." /> nodes - // and load the resource files - var langLinks = getL10nResourceLinks(); - var langCount = langLinks.length; - if (langCount == 0) { - consoleLog('no resource to load, early way out'); - fireL10nReadyEvent(lang); - gReadyState = 'complete'; - return; - } - - // start the callback when all resources are loaded - var onResourceLoaded = null; - var gResourceCount = 0; - onResourceLoaded = function() { - gResourceCount++; - if (gResourceCount >= langCount) { - if (callback) // execute the [optional] callback - callback(); - fireL10nReadyEvent(lang); - gReadyState = 'complete'; - } - }; - - // load all resource files - function l10nResourceLink(link) { - var href = link.href; - var type = link.type; - this.load = function(lang, callback) { - var applied = lang; - parseResource(href, lang, callback, function() { - consoleWarn(href + ' not found.'); - applied = ''; - }); - return applied; // return lang if found, an empty string if not found - }; - } - - for (var i = 0; i < langCount; i++) { - var resource = new l10nResourceLink(langLinks[i]); - var rv = resource.load(lang, onResourceLoaded); - if (rv != lang) { // lang not found, used default resource instead - consoleWarn('"' + lang + '" resource not found'); - gLanguage = ''; - } - } - } - - // clear all l10n data - function clear() { - gL10nData = {}; - gTextData = ''; - gLanguage = ''; - // TODO: clear all non predefined macros. - // There's no such macro /yet/ but we're planning to have some... - } - - - /** - * Get rules for plural forms (shared with JetPack), see: - * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p - * - * @param {string} lang - * locale (language) used. - * - * @return {Function} - * returns a function that gives the plural form name for a given integer: - * var fun = getPluralRules('en'); - * fun(1) -> 'one' - * fun(0) -> 'other' - * fun(1000) -> 'other'. - */ - - function getPluralRules(lang) { - var locales2rules = { - 'af': 3, - 'ak': 4, - 'am': 4, - 'ar': 1, - 'asa': 3, - 'az': 0, - 'be': 11, - 'bem': 3, - 'bez': 3, - 'bg': 3, - 'bh': 4, - 'bm': 0, - 'bn': 3, - 'bo': 0, - 'br': 20, - 'brx': 3, - 'bs': 11, - 'ca': 3, - 'cgg': 3, - 'chr': 3, - 'cs': 12, - 'cy': 17, - 'da': 3, - 'de': 3, - 'dv': 3, - 'dz': 0, - 'ee': 3, - 'el': 3, - 'en': 3, - 'eo': 3, - 'es': 3, - 'et': 3, - 'eu': 3, - 'fa': 0, - 'ff': 5, - 'fi': 3, - 'fil': 4, - 'fo': 3, - 'fr': 5, - 'fur': 3, - 'fy': 3, - 'ga': 8, - 'gd': 24, - 'gl': 3, - 'gsw': 3, - 'gu': 3, - 'guw': 4, - 'gv': 23, - 'ha': 3, - 'haw': 3, - 'he': 2, - 'hi': 4, - 'hr': 11, - 'hu': 0, - 'id': 0, - 'ig': 0, - 'ii': 0, - 'is': 3, - 'it': 3, - 'iu': 7, - 'ja': 0, - 'jmc': 3, - 'jv': 0, - 'ka': 0, - 'kab': 5, - 'kaj': 3, - 'kcg': 3, - 'kde': 0, - 'kea': 0, - 'kk': 3, - 'kl': 3, - 'km': 0, - 'kn': 0, - 'ko': 0, - 'ksb': 3, - 'ksh': 21, - 'ku': 3, - 'kw': 7, - 'lag': 18, - 'lb': 3, - 'lg': 3, - 'ln': 4, - 'lo': 0, - 'lt': 10, - 'lv': 6, - 'mas': 3, - 'mg': 4, - 'mk': 16, - 'ml': 3, - 'mn': 3, - 'mo': 9, - 'mr': 3, - 'ms': 0, - 'mt': 15, - 'my': 0, - 'nah': 3, - 'naq': 7, - 'nb': 3, - 'nd': 3, - 'ne': 3, - 'nl': 3, - 'nn': 3, - 'no': 3, - 'nr': 3, - 'nso': 4, - 'ny': 3, - 'nyn': 3, - 'om': 3, - 'or': 3, - 'pa': 3, - 'pap': 3, - 'pl': 13, - 'ps': 3, - 'pt': 3, - 'rm': 3, - 'ro': 9, - 'rof': 3, - 'ru': 11, - 'rwk': 3, - 'sah': 0, - 'saq': 3, - 'se': 7, - 'seh': 3, - 'ses': 0, - 'sg': 0, - 'sh': 11, - 'shi': 19, - 'sk': 12, - 'sl': 14, - 'sma': 7, - 'smi': 7, - 'smj': 7, - 'smn': 7, - 'sms': 7, - 'sn': 3, - 'so': 3, - 'sq': 3, - 'sr': 11, - 'ss': 3, - 'ssy': 3, - 'st': 3, - 'sv': 3, - 'sw': 3, - 'syr': 3, - 'ta': 3, - 'te': 3, - 'teo': 3, - 'th': 0, - 'ti': 4, - 'tig': 3, - 'tk': 3, - 'tl': 4, - 'tn': 3, - 'to': 0, - 'tr': 0, - 'ts': 3, - 'tzm': 22, - 'uk': 11, - 'ur': 3, - 've': 3, - 'vi': 0, - 'vun': 3, - 'wa': 4, - 'wae': 3, - 'wo': 0, - 'xh': 3, - 'xog': 3, - 'yo': 0, - 'zh': 0, - 'zu': 3 - }; - - // utility functions for plural rules methods - function isIn(n, list) { - return list.indexOf(n) !== -1; - } - function isBetween(n, start, end) { - return start <= n && n <= end; - } - - // list of all plural rules methods: - // map an integer to the plural form name to use - var pluralRules = { - '0': function(n) { - return 'other'; - }, - '1': function(n) { - if ((isBetween((n % 100), 3, 10))) - return 'few'; - if (n === 0) - return 'zero'; - if ((isBetween((n % 100), 11, 99))) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '2': function(n) { - if (n !== 0 && (n % 10) === 0) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '3': function(n) { - if (n == 1) - return 'one'; - return 'other'; - }, - '4': function(n) { - if ((isBetween(n, 0, 1))) - return 'one'; - return 'other'; - }, - '5': function(n) { - if ((isBetween(n, 0, 2)) && n != 2) - return 'one'; - return 'other'; - }, - '6': function(n) { - if (n === 0) - return 'zero'; - if ((n % 10) == 1 && (n % 100) != 11) - return 'one'; - return 'other'; - }, - '7': function(n) { - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '8': function(n) { - if ((isBetween(n, 3, 6))) - return 'few'; - if ((isBetween(n, 7, 10))) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '9': function(n) { - if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) - return 'few'; - if (n == 1) - return 'one'; - return 'other'; - }, - '10': function(n) { - if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) - return 'few'; - if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) - return 'one'; - return 'other'; - }, - '11': function(n) { - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) - return 'few'; - if ((n % 10) === 0 || - (isBetween((n % 10), 5, 9)) || - (isBetween((n % 100), 11, 14))) - return 'many'; - if ((n % 10) == 1 && (n % 100) != 11) - return 'one'; - return 'other'; - }, - '12': function(n) { - if ((isBetween(n, 2, 4))) - return 'few'; - if (n == 1) - return 'one'; - return 'other'; - }, - '13': function(n) { - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) - return 'few'; - if (n != 1 && (isBetween((n % 10), 0, 1)) || - (isBetween((n % 10), 5, 9)) || - (isBetween((n % 100), 12, 14))) - return 'many'; - if (n == 1) - return 'one'; - return 'other'; - }, - '14': function(n) { - if ((isBetween((n % 100), 3, 4))) - return 'few'; - if ((n % 100) == 2) - return 'two'; - if ((n % 100) == 1) - return 'one'; - return 'other'; - }, - '15': function(n) { - if (n === 0 || (isBetween((n % 100), 2, 10))) - return 'few'; - if ((isBetween((n % 100), 11, 19))) - return 'many'; - if (n == 1) - return 'one'; - return 'other'; - }, - '16': function(n) { - if ((n % 10) == 1 && n != 11) - return 'one'; - return 'other'; - }, - '17': function(n) { - if (n == 3) - return 'few'; - if (n === 0) - return 'zero'; - if (n == 6) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '18': function(n) { - if (n === 0) - return 'zero'; - if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) - return 'one'; - return 'other'; - }, - '19': function(n) { - if ((isBetween(n, 2, 10))) - return 'few'; - if ((isBetween(n, 0, 1))) - return 'one'; - return 'other'; - }, - '20': function(n) { - if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( - isBetween((n % 100), 10, 19) || - isBetween((n % 100), 70, 79) || - isBetween((n % 100), 90, 99) - )) - return 'few'; - if ((n % 1000000) === 0 && n !== 0) - return 'many'; - if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) - return 'two'; - if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) - return 'one'; - return 'other'; - }, - '21': function(n) { - if (n === 0) - return 'zero'; - if (n == 1) - return 'one'; - return 'other'; - }, - '22': function(n) { - if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) - return 'one'; - return 'other'; - }, - '23': function(n) { - if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) - return 'one'; - return 'other'; - }, - '24': function(n) { - if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) - return 'few'; - if (isIn(n, [2, 12])) - return 'two'; - if (isIn(n, [1, 11])) - return 'one'; - return 'other'; - } - }; - - // return a function that gives the plural form name for a given integer - var index = locales2rules[lang.replace(/-.*$/, '')]; - if (!(index in pluralRules)) { - consoleWarn('plural form unknown for [' + lang + ']'); - return function() { return 'other'; }; - } - return pluralRules[index]; - } - - // pre-defined 'plural' macro - gMacros.plural = function(str, param, key, prop) { - var n = parseFloat(param); - if (isNaN(n)) - return str; - - // TODO: support other properties (l20n still doesn't...) - if (prop != gTextProp) - return str; - - // initialize _pluralRules - if (!gMacros._pluralRules) - gMacros._pluralRules = getPluralRules(gLanguage); - var index = '[' + gMacros._pluralRules(n) + ']'; - - // try to find a [zero|one|two] key if it's defined - if (n === 0 && (key + '[zero]') in gL10nData) { - str = gL10nData[key + '[zero]'][prop]; - } else if (n == 1 && (key + '[one]') in gL10nData) { - str = gL10nData[key + '[one]'][prop]; - } else if (n == 2 && (key + '[two]') in gL10nData) { - str = gL10nData[key + '[two]'][prop]; - } else if ((key + index) in gL10nData) { - str = gL10nData[key + index][prop]; - } - - return str; - }; - - - /** - * l10n dictionary functions - */ - - // fetch an l10n object, warn if not found, apply `args' if possible - function getL10nData(key, args) { - var data = gL10nData[key]; - if (!data) { - consoleWarn('#' + key + ' missing for [' + gLanguage + ']'); - } - - /** This is where l10n expressions should be processed. - * The plan is to support C-style expressions from the l20n project; - * until then, only two kinds of simple expressions are supported: - * {[ index ]} and {{ arguments }}. - */ - var rv = {}; - for (var prop in data) { - var str = data[prop]; - str = substIndexes(str, args, key, prop); - str = substArguments(str, args); - rv[prop] = str; - } - return rv; - } - - // replace {[macros]} with their values - function substIndexes(str, args, key, prop) { - var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/; - var reMatch = reIndex.exec(str); - if (!reMatch || !reMatch.length) - return str; - - // an index/macro has been found - // Note: at the moment, only one parameter is supported - var macroName = reMatch[1]; - var paramName = reMatch[2]; - var param; - if (args && paramName in args) { - param = args[paramName]; - } else if (paramName in gL10nData) { - param = gL10nData[paramName]; - } - - // there's no macro parser yet: it has to be defined in gMacros - if (macroName in gMacros) { - var macro = gMacros[macroName]; - str = macro(str, param, key, prop); - } - return str; - } - - // replace {{arguments}} with their values - function substArguments(str, args) { - var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/; - var match = reArgs.exec(str); - while (match) { - if (!match || match.length < 2) - return str; // argument key not found - - var arg = match[1]; - var sub = ''; - if (arg in args) { - sub = args[arg]; - } else if (arg in gL10nData) { - sub = gL10nData[arg][gTextProp]; - } else { - consoleWarn('could not find argument {{' + arg + '}}'); - return str; - } - - str = str.substring(0, match.index) + sub + - str.substr(match.index + match[0].length); - match = reArgs.exec(str); - } - return str; - } - - // translate an HTML element - function translateElement(element) { - var l10n = getL10nAttributes(element); - if (!l10n.id) - return; - - // get the related l10n object - var data = getL10nData(l10n.id, l10n.args); - if (!data) { - consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']'); - return; - } - - // translate element (TODO: security checks?) - // for the node content, replace the content of the first child textNode - // and clear other child textNodes - if (data[gTextProp]) { // XXX - if (element.children.length === 0) { - element[gTextProp] = data[gTextProp]; - } else { - var children = element.childNodes, - found = false; - for (var i = 0, l = children.length; i < l; i++) { - if (children[i].nodeType === 3 && - /\S/.test(children[i].textContent)) { // XXX - // using nodeValue seems cross-browser - if (found) { - children[i].nodeValue = ''; - } else { - children[i].nodeValue = data[gTextProp]; - found = true; - } - } - } - if (!found) { - consoleWarn('unexpected error, could not translate element content'); - } - } - delete data[gTextProp]; - } - - for (var k in data) { - element[k] = data[k]; - } - } - - // translate an HTML subtree - function translateFragment(element) { - element = element || document.documentElement; - - // check all translatable children (= w/ a `data-l10n-id' attribute) - var children = getTranslatableChildren(element); - var elementCount = children.length; - for (var i = 0; i < elementCount; i++) { - translateElement(children[i]); - } - - // translate element itself if necessary - translateElement(element); - } - - - /** - * Startup & Public API - * - * Warning: this part of the code contains browser-specific chunks -- - * that's where obsolete browsers, namely IE8 and earlier, are handled. - * - * Unlike the rest of the lib, this section is not shared with FirefoxOS/Gaia. - */ - - // browser-specific startup - if (document.addEventListener) { // modern browsers and IE9+ - document.addEventListener('DOMContentLoaded', function() { - var lang = document.documentElement.lang || navigator.language || navigator.userLanguage || 'en'; - loadLocale(lang, translateFragment); - }, false); - } else if (window.attachEvent) { // IE8 and before (= oldIE) - // TODO: check if jQuery is loaded (CSS selector + JSON + events) - - // dummy `console.log' and `console.warn' functions - if (!window.console) { - consoleLog = function(message) {}; // just ignore console.log calls - consoleWarn = function(message) { - if (gDEBUG) - alert('[l10n] ' + message); // vintage debugging, baby! - }; - } - - // worst hack ever for IE6 and IE7 - if (!window.JSON) { - consoleWarn('[l10n] no JSON support'); - - getL10nAttributes = function(element) { - if (!element) - return {}; - var l10nId = element.getAttribute('data-l10n-id'), - l10nArgs = element.getAttribute('data-l10n-args'), - args = {}; - if (l10nArgs) try { - args = eval(l10nArgs); // XXX yeah, I know... - } catch (e) { - consoleWarn('[l10n] could not parse arguments for #' + l10nId); - } - return { id: l10nId, args: args }; - }; - } - - // override `getTranslatableChildren' and `getL10nResourceLinks' - if (!document.querySelectorAll) { - consoleWarn('[l10n] no "querySelectorAll" support'); - - getTranslatableChildren = function(element) { - if (!element) - return []; - var nodes = element.getElementsByTagName('*'), - l10nElements = [], - n = nodes.length; - for (var i = 0; i < n; i++) { - if (nodes[i].getAttribute('data-l10n-id')) - l10nElements.push(nodes[i]); - } - return l10nElements; - }; - - getL10nResourceLinks = function() { - var links = document.getElementsByTagName('link'), - l10nLinks = [], - n = links.length; - for (var i = 0; i < n; i++) { - if (links[i].type == 'application/l10n') - l10nLinks.push(links[i]); - } - return l10nLinks; - }; - } - - // fire non-standard `localized' DOM events - if (document.createEventObject && !document.createEvent) { - fireL10nReadyEvent = function(lang) { - // hack to simulate a custom event in IE: - // to catch this event, add an event handler to `onpropertychange' - document.documentElement.localized = 1; - }; - } - - // startup for IE<9 - window.attachEvent('onload', function() { - gTextProp = document.body.textContent ? 'textContent' : 'innerText'; - var lang = document.documentElement.lang || navigator.language || navigator.userLanguage || 'en'; - loadLocale(lang, translateFragment); - }); - } - - // cross-browser API (sorry, oldIE doesn't support getters & setters) - return { - // get a localized string - get: function(key, args, fallback) { - var data = getL10nData(key, args) || fallback; - if (data) { // XXX double-check this - return 'textContent' in data ? data.textContent : ''; - } - return '{{' + key + '}}'; - }, - - // debug - getData: function() { return gL10nData; }, - getText: function() { return gTextData; }, - - // get|set the document language - getLanguage: function() { return gLanguage; }, - setLanguage: function(lang) { loadLocale(lang, translateFragment); }, - - // get the direction (ltr|rtl) of the current language - getDirection: function() { - // http://www.w3.org/International/questions/qa-scripts - // Arabic, Hebrew, Farsi, Pashto, Urdu - var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; - return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; - }, - - // translate an element or document fragment - translate: translateFragment, - - // this can be used to prevent race conditions - getReadyState: function() { return gReadyState; } - }; - -}) (window, document); - -// gettext-like shortcut for navigator.webL10n.get -if (window._ === undefined) - var _ = document.webL10n.get; - -// CommonJS -try { - exports = document.webL10n; -}catch(e){}
\ No newline at end of file diff --git a/src/templates/index.html b/src/templates/index.html index 961c6012..222b9f29 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -31,17 +31,11 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> - <link rel="resource" type="application/l10n" href="locales.ini" /> <link rel="shortcut icon" href="<%=settings.favicon%>"> - - <script type="text/javascript"> - (function(document) { - // Set language for l10n - var language = document.cookie.match(/language=(\w{2})/); - if(language) document.documentElement.lang = language[1]; - })(document) - </script> - <script type="text/javascript" src="static/js/l10n.js"></script> + + <link rel="localizations" type="application/l10n+json" href="../locales.ini" /> + <script type="text/javascript" src="../static/js/html10n.js"></script> + <script type="text/javascript" src="../static/js/l10n.js"></script> <style> html, body { diff --git a/src/templates/pad.html b/src/templates/pad.html index 804c2e80..caeb4553 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -34,14 +34,8 @@ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> <link rel="shortcut icon" href="<%=settings.faviconPad%>"> - <link rel="resource" type="application/l10n" href="../locales.ini" /> - <script type="text/javascript"> - (function() { - // Set language for l10n - var language = document.cookie.match(/language=(\w{2})/); - if(language) document.documentElement.lang = language[1]; - })(); - </script> + <link rel="localizations" type="application/l10n+json" href="../locales.ini" /> + <script type="text/javascript" src="../static/js/html10n.js"></script> <script type="text/javascript" src="../static/js/l10n.js"></script> <% e.begin_block("styles"); %> diff --git a/src/templates/timeslider.html b/src/templates/timeslider.html index a3e94118..6f64ae82 100644 --- a/src/templates/timeslider.html +++ b/src/templates/timeslider.html @@ -32,16 +32,11 @@ <meta charset="utf-8"> <meta name="robots" content="noindex, nofollow"> <link rel="shortcut icon" href="<%=settings.faviconTimeslider%>"> - <link rel="resource" type="application/l10n" href="../../locales.ini" /> - - <script type="text/javascript"> - (function() { - // Set language for l10n - var language = document.cookie.match(/language=(\w{2})/); - if(language) document.documentElement.lang = language[1]; - })(); - </script> + + <link rel="localizations" type="application/l10n+json" href="../../locales.ini" /> + <script type="text/javascript" src="../../static/js/html10n.js"></script> <script type="text/javascript" src="../../static/js/l10n.js"></script> + <link rel="stylesheet" href="../../static/css/pad.css"> <link rel="stylesheet" href="../../static/css/timeslider.css"> <link rel="stylesheet" href="../../static/custom/timeslider.css"> |