|
|
|
@ -1,30 +1,25 @@
@@ -1,30 +1,25 @@
|
|
|
|
|
/** 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. |
|
|
|
|
*/ |
|
|
|
|
/* |
|
|
|
|
Additional modifications for PDF.js project: |
|
|
|
|
- Disables language initialization on page loading; |
|
|
|
|
- Adds fallback argument to the getL10nData; |
|
|
|
|
- Removes consoleLog and simplifies consoleWarn; |
|
|
|
|
- Removes window._ assignment. |
|
|
|
|
*/ |
|
|
|
|
/** |
|
|
|
|
* Copyright (c) 2011-2013 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'; |
|
|
|
|
|
|
|
|
@ -36,13 +31,44 @@ document.webL10n = (function(window, document, undefined) {
@@ -36,13 +31,44 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
var gMacros = {}; |
|
|
|
|
var gReadyState = 'loading'; |
|
|
|
|
|
|
|
|
|
// read-only setting -- we recommend to load l10n resources synchronously
|
|
|
|
|
var gAsyncResourceLoading = true; |
|
|
|
|
|
|
|
|
|
// debug helpers
|
|
|
|
|
/** |
|
|
|
|
* Synchronously loading l10n resources significantly minimizes flickering |
|
|
|
|
* from displaying the app with non-localized strings and then updating the |
|
|
|
|
* strings. Although this will block all script execution on this page, we |
|
|
|
|
* expect that the l10n resources are available locally on flash-storage. |
|
|
|
|
* |
|
|
|
|
* As synchronous XHR is generally considered as a bad idea, we're still |
|
|
|
|
* loading l10n resources asynchronously -- but we keep this in a setting, |
|
|
|
|
* just in case... and applications using this library should hide their |
|
|
|
|
* content until the `localized' event happens.
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
var gAsyncResourceLoading = true; // read-only
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Debug helpers |
|
|
|
|
* |
|
|
|
|
* gDEBUG == 0: don't display any console message |
|
|
|
|
* gDEBUG == 1: display only warnings, not logs |
|
|
|
|
* gDEBUG == 2: display all console messages |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
var gDEBUG = 1; |
|
|
|
|
|
|
|
|
|
function consoleLog(message) { |
|
|
|
|
if (gDEBUG >= 2) { |
|
|
|
|
console.log('[l10n] ' + message); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function consoleWarn(message) { |
|
|
|
|
console.log('[l10n] ' + message); |
|
|
|
|
}; |
|
|
|
|
if (gDEBUG) { |
|
|
|
|
console.warn('[l10n] ' + message); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* DOM helpers for the so-called "HTML API". |
|
|
|
@ -55,6 +81,12 @@ document.webL10n = (function(window, document, undefined) {
@@ -55,6 +81,12 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
return document.querySelectorAll('link[type="application/l10n"]'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function getL10nDictionary() { |
|
|
|
|
var script = document.querySelector('script[type="application/l10n"]'); |
|
|
|
|
// TODO: support multiple and external JSON dictionaries
|
|
|
|
|
return script ? JSON.parse(script.innerHTML) : null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function getTranslatableChildren(element) { |
|
|
|
|
return element ? element.querySelectorAll('*[data-l10n-id]') : []; |
|
|
|
|
} |
|
|
|
@ -78,9 +110,41 @@ document.webL10n = (function(window, document, undefined) {
@@ -78,9 +110,41 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
|
|
|
|
|
function fireL10nReadyEvent(lang) { |
|
|
|
|
var evtObject = document.createEvent('Event'); |
|
|
|
|
evtObject.initEvent('localized', false, false); |
|
|
|
|
evtObject.initEvent('localized', true, false); |
|
|
|
|
evtObject.language = lang; |
|
|
|
|
window.dispatchEvent(evtObject); |
|
|
|
|
document.dispatchEvent(evtObject); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function xhrLoadText(url, onSuccess, onFailure, asynchronous) { |
|
|
|
|
onSuccess = onSuccess || function _onSuccess(data) {}; |
|
|
|
|
onFailure = onFailure || function _onFailure() { |
|
|
|
|
consoleWarn(url + ' not found.'); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
onSuccess(xhr.responseText); |
|
|
|
|
} else { |
|
|
|
|
onFailure(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
xhr.onerror = onFailure; |
|
|
|
|
xhr.ontimeout = onFailure; |
|
|
|
|
|
|
|
|
|
// in Firefox OS with the app:// protocol, trying to XHR a non-existing
|
|
|
|
|
// URL will raise an exception here -- hence this ugly try...catch.
|
|
|
|
|
try { |
|
|
|
|
xhr.send(null); |
|
|
|
|
} catch (e) { |
|
|
|
|
onFailure(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -108,7 +172,7 @@ document.webL10n = (function(window, document, undefined) {
@@ -108,7 +172,7 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
function parseResource(href, lang, successCallback, failureCallback) { |
|
|
|
|
var baseURL = href.replace(/\/[^\/]*$/, '/'); |
|
|
|
|
var baseURL = href.replace(/[^\/]*$/, '') || './'; |
|
|
|
|
|
|
|
|
|
// handle escaped characters (backslashes) in a string
|
|
|
|
|
function evalString(text) { |
|
|
|
@ -171,16 +235,17 @@ document.webL10n = (function(window, document, undefined) {
@@ -171,16 +235,17 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
|
|
|
|
|
// key-value pair
|
|
|
|
|
var tmp = line.match(reSplit); |
|
|
|
|
if (tmp && tmp.length == 3) |
|
|
|
|
if (tmp && tmp.length == 3) { |
|
|
|
|
dictionary[tmp[1]] = evalString(tmp[2]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// import another *.properties file
|
|
|
|
|
function loadImport(url) { |
|
|
|
|
loadResource(url, function(content) { |
|
|
|
|
xhrLoadText(url, function(content) { |
|
|
|
|
parseRawLines(content, false); // don't allow recursive imports
|
|
|
|
|
}, false, false); // load synchronously
|
|
|
|
|
}, null, false); // load synchronously
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// fill the dictionary
|
|
|
|
@ -188,29 +253,8 @@ document.webL10n = (function(window, document, undefined) {
@@ -188,29 +253,8 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
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) { |
|
|
|
|
xhrLoadText(href, function(response) { |
|
|
|
|
gTextData += response; // mostly for debug
|
|
|
|
|
|
|
|
|
|
// parse *.properties text data into an l10n dictionary
|
|
|
|
@ -233,13 +277,16 @@ document.webL10n = (function(window, document, undefined) {
@@ -233,13 +277,16 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// trigger callback
|
|
|
|
|
if (successCallback) |
|
|
|
|
if (successCallback) { |
|
|
|
|
successCallback(); |
|
|
|
|
} |
|
|
|
|
}, failureCallback, gAsyncResourceLoading); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// load and parse all resources for the specified locale
|
|
|
|
|
function loadLocale(lang, callback) { |
|
|
|
|
callback = callback || function _callback() {}; |
|
|
|
|
|
|
|
|
|
clear(); |
|
|
|
|
gLanguage = lang; |
|
|
|
|
|
|
|
|
@ -247,8 +294,17 @@ document.webL10n = (function(window, document, undefined) {
@@ -247,8 +294,17 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
// and load the resource files
|
|
|
|
|
var langLinks = getL10nResourceLinks(); |
|
|
|
|
var langCount = langLinks.length; |
|
|
|
|
if (langCount == 0) { |
|
|
|
|
consoleWarn('no resource to load, early way out'); |
|
|
|
|
if (langCount === 0) { |
|
|
|
|
// we might have a pre-compiled dictionary instead
|
|
|
|
|
var dict = getL10nDictionary(); |
|
|
|
|
if (dict && dict.locales && dict.default_locale) { |
|
|
|
|
consoleLog('using the embedded JSON directory, early way out'); |
|
|
|
|
gL10nData = dict.locales[lang] || dict.locales[dict.default_locale]; |
|
|
|
|
callback(); |
|
|
|
|
} else { |
|
|
|
|
consoleLog('no resource to load, early way out'); |
|
|
|
|
} |
|
|
|
|
// early way out
|
|
|
|
|
fireL10nReadyEvent(lang); |
|
|
|
|
gReadyState = 'complete'; |
|
|
|
|
return; |
|
|
|
@ -260,15 +316,14 @@ document.webL10n = (function(window, document, undefined) {
@@ -260,15 +316,14 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
onResourceLoaded = function() { |
|
|
|
|
gResourceCount++; |
|
|
|
|
if (gResourceCount >= langCount) { |
|
|
|
|
if (callback) // execute the [optional] callback
|
|
|
|
|
callback(); |
|
|
|
|
callback(); |
|
|
|
|
fireL10nReadyEvent(lang); |
|
|
|
|
gReadyState = 'complete'; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// load all resource files
|
|
|
|
|
function l10nResourceLink(link) { |
|
|
|
|
function L10nResourceLink(link) { |
|
|
|
|
var href = link.href; |
|
|
|
|
var type = link.type; |
|
|
|
|
this.load = function(lang, callback) { |
|
|
|
@ -282,7 +337,7 @@ document.webL10n = (function(window, document, undefined) {
@@ -282,7 +337,7 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (var i = 0; i < langCount; i++) { |
|
|
|
|
var resource = new l10nResourceLink(langLinks[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'); |
|
|
|
@ -723,8 +778,9 @@ document.webL10n = (function(window, document, undefined) {
@@ -723,8 +778,9 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
return str; |
|
|
|
|
|
|
|
|
|
// initialize _pluralRules
|
|
|
|
|
if (!gMacros._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
|
|
|
|
@ -736,6 +792,8 @@ document.webL10n = (function(window, document, undefined) {
@@ -736,6 +792,8 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
str = gL10nData[key + '[two]'][prop]; |
|
|
|
|
} else if ((key + index) in gL10nData) { |
|
|
|
|
str = gL10nData[key + index][prop]; |
|
|
|
|
} else if ((key + '[other]') in gL10nData) { |
|
|
|
|
str = gL10nData[key + '[other]'][prop]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return str; |
|
|
|
@ -750,7 +808,7 @@ document.webL10n = (function(window, document, undefined) {
@@ -750,7 +808,7 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
function getL10nData(key, args, fallback) { |
|
|
|
|
var data = gL10nData[key]; |
|
|
|
|
if (!data) { |
|
|
|
|
consoleWarn('#' + key + ' missing for [' + gLanguage + ']'); |
|
|
|
|
consoleWarn('#' + key + ' is undefined.'); |
|
|
|
|
if (!fallback) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
@ -766,7 +824,7 @@ document.webL10n = (function(window, document, undefined) {
@@ -766,7 +824,7 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
for (var prop in data) { |
|
|
|
|
var str = data[prop]; |
|
|
|
|
str = substIndexes(str, args, key, prop); |
|
|
|
|
str = substArguments(str, args); |
|
|
|
|
str = substArguments(str, args, key); |
|
|
|
|
rv[prop] = str; |
|
|
|
|
} |
|
|
|
|
return rv; |
|
|
|
@ -799,8 +857,8 @@ document.webL10n = (function(window, document, undefined) {
@@ -799,8 +857,8 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// replace {{arguments}} with their values
|
|
|
|
|
function substArguments(str, args) { |
|
|
|
|
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/; |
|
|
|
|
function substArguments(str, args, key) { |
|
|
|
|
var reArgs = /\{\{\s*(.+?)\s*\}\}/; |
|
|
|
|
var match = reArgs.exec(str); |
|
|
|
|
while (match) { |
|
|
|
|
if (!match || match.length < 2) |
|
|
|
@ -808,12 +866,12 @@ document.webL10n = (function(window, document, undefined) {
@@ -808,12 +866,12 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
|
|
|
|
|
var arg = match[1]; |
|
|
|
|
var sub = ''; |
|
|
|
|
if (arg in args) { |
|
|
|
|
if (args && arg in args) { |
|
|
|
|
sub = args[arg]; |
|
|
|
|
} else if (arg in gL10nData) { |
|
|
|
|
sub = gL10nData[arg][gTextProp]; |
|
|
|
|
} else { |
|
|
|
|
consoleWarn('could not find argument {{' + arg + '}}'); |
|
|
|
|
consoleLog('argument {{' + arg + '}} for #' + key + ' is undefined.'); |
|
|
|
|
return str; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -833,23 +891,21 @@ document.webL10n = (function(window, document, undefined) {
@@ -833,23 +891,21 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
// get the related l10n object
|
|
|
|
|
var data = getL10nData(l10n.id, l10n.args); |
|
|
|
|
if (!data) { |
|
|
|
|
consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']'); |
|
|
|
|
consoleWarn('#' + l10n.id + ' is undefined.'); |
|
|
|
|
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) { |
|
|
|
|
if (getChildElementCount(element) === 0) { |
|
|
|
|
element[gTextProp] = data[gTextProp]; |
|
|
|
|
} else { |
|
|
|
|
var children = element.childNodes, |
|
|
|
|
found = false; |
|
|
|
|
// this element has element children: replace the content of the first
|
|
|
|
|
// (non-empty) child textNode and clear other child textNodes
|
|
|
|
|
var children = element.childNodes; |
|
|
|
|
var 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 (children[i].nodeType === 3 && /\S/.test(children[i].nodeValue)) { |
|
|
|
|
if (found) { |
|
|
|
|
children[i].nodeValue = ''; |
|
|
|
|
} else { |
|
|
|
@ -858,8 +914,11 @@ document.webL10n = (function(window, document, undefined) {
@@ -858,8 +914,11 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// if no (non-empty) textNode is found, insert a textNode before the
|
|
|
|
|
// first element child.
|
|
|
|
|
if (!found) { |
|
|
|
|
consoleWarn('unexpected error, could not translate element content'); |
|
|
|
|
var textNode = document.createTextNode(data[gTextProp]); |
|
|
|
|
element.insertBefore(textNode, element.firstChild); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
delete data[gTextProp]; |
|
|
|
@ -870,6 +929,21 @@ document.webL10n = (function(window, document, undefined) {
@@ -870,6 +929,21 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// webkit browsers don't currently support 'children' on SVG elements...
|
|
|
|
|
function getChildElementCount(element) { |
|
|
|
|
if (element.children) { |
|
|
|
|
return element.children.length; |
|
|
|
|
} |
|
|
|
|
if (typeof element.childElementCount !== 'undefined') { |
|
|
|
|
return element.childElementCount; |
|
|
|
|
} |
|
|
|
|
var count = 0; |
|
|
|
|
for (var i = 0; i < element.childNodes.length; i++) { |
|
|
|
|
count += element.nodeType === 1 ? 1 : 0; |
|
|
|
|
} |
|
|
|
|
return count; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// translate an HTML subtree
|
|
|
|
|
function translateFragment(element) { |
|
|
|
|
element = element || document.documentElement; |
|
|
|
@ -885,13 +959,169 @@ document.webL10n = (function(window, document, undefined) {
@@ -885,13 +959,169 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
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. |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
// load the default locale on startup
|
|
|
|
|
function l10nStartup() { |
|
|
|
|
gReadyState = 'interactive'; |
|
|
|
|
|
|
|
|
|
// most browsers expose the UI language as `navigator.language'
|
|
|
|
|
// but IE uses `navigator.userLanguage' instead
|
|
|
|
|
var userLocale = navigator.language || navigator.userLanguage; |
|
|
|
|
consoleLog('loading [' + userLocale + '] resources, ' + |
|
|
|
|
(gAsyncResourceLoading ? 'asynchronously.' : 'synchronously.')); |
|
|
|
|
|
|
|
|
|
// load the default locale and translate the document if required
|
|
|
|
|
if (document.documentElement.lang === userLocale) { |
|
|
|
|
loadLocale(userLocale); |
|
|
|
|
} else { |
|
|
|
|
loadLocale(userLocale, translateFragment); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// browser-specific startup
|
|
|
|
|
if (document.addEventListener) { // modern browsers and IE9+
|
|
|
|
|
if (document.readyState === 'loading') { |
|
|
|
|
// the document is not fully loaded yet: wait for DOMContentLoaded.
|
|
|
|
|
document.addEventListener('DOMContentLoaded', l10nStartup); |
|
|
|
|
} else { |
|
|
|
|
// l10n.js is being loaded with <script defer> or <script async>,
|
|
|
|
|
// the DOM is ready for parsing.
|
|
|
|
|
window.setTimeout(l10nStartup); |
|
|
|
|
} |
|
|
|
|
} 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!
|
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// XMLHttpRequest for IE6
|
|
|
|
|
if (!window.XMLHttpRequest) { |
|
|
|
|
xhrLoadText = function(url, onSuccess, onFailure, asynchronous) { |
|
|
|
|
onSuccess = onSuccess || function _onSuccess(data) {}; |
|
|
|
|
onFailure = onFailure || function _onFailure() { |
|
|
|
|
consoleWarn(url + ' not found.'); |
|
|
|
|
}; |
|
|
|
|
var xhr = new ActiveXObject('Microsoft.XMLHTTP'); |
|
|
|
|
xhr.open('GET', url, asynchronous); |
|
|
|
|
xhr.onreadystatechange = function() { |
|
|
|
|
if (xhr.readyState == 4) { |
|
|
|
|
if (xhr.status == 200) { |
|
|
|
|
onSuccess(xhr.responseText); |
|
|
|
|
} else { |
|
|
|
|
onFailure(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
xhr.send(null); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// worst hack ever for IE6 and IE7
|
|
|
|
|
if (!window.JSON) { |
|
|
|
|
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('could not parse arguments for #' + l10nId); |
|
|
|
|
} |
|
|
|
|
return { id: l10nId, args: args }; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// override `getTranslatableChildren' and `getL10nResourceLinks'
|
|
|
|
|
if (!document.querySelectorAll) { |
|
|
|
|
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; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// override `getL10nDictionary'
|
|
|
|
|
if (!window.JSON || !document.querySelectorAll) { |
|
|
|
|
getL10nDictionary = function() { |
|
|
|
|
var scripts = document.getElementsByName('script'); |
|
|
|
|
for (var i = 0; i < scripts.length; i++) { |
|
|
|
|
if (scripts[i].type == 'application/l10n') { |
|
|
|
|
return eval(scripts[i].innerHTML); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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'; |
|
|
|
|
l10nStartup(); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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, {textContent: fallback}); |
|
|
|
|
if (data) { // XXX double-check this
|
|
|
|
|
return 'textContent' in data ? data.textContent : ''; |
|
|
|
|
get: function(key, args, fallbackString) { |
|
|
|
|
var index = key.lastIndexOf('.'); |
|
|
|
|
var prop = gTextProp; |
|
|
|
|
if (index > 0) { // An attribute has been specified
|
|
|
|
|
prop = key.substr(index + 1); |
|
|
|
|
key = key.substring(0, index); |
|
|
|
|
} |
|
|
|
|
var fallback; |
|
|
|
|
if (fallbackString) { |
|
|
|
|
fallback = {}; |
|
|
|
|
fallback[prop] = fallbackString; |
|
|
|
|
} |
|
|
|
|
var data = getL10nData(key, args, fallback); |
|
|
|
|
if (data && prop in data) { |
|
|
|
|
return data[prop]; |
|
|
|
|
} |
|
|
|
|
return '{{' + key + '}}'; |
|
|
|
|
}, |
|
|
|
@ -916,7 +1146,27 @@ document.webL10n = (function(window, document, undefined) {
@@ -916,7 +1146,27 @@ document.webL10n = (function(window, document, undefined) {
|
|
|
|
|
translate: translateFragment, |
|
|
|
|
|
|
|
|
|
// this can be used to prevent race conditions
|
|
|
|
|
getReadyState: function() { return gReadyState; } |
|
|
|
|
getReadyState: function() { return gReadyState; }, |
|
|
|
|
ready: function(callback) { |
|
|
|
|
if (!callback) { |
|
|
|
|
return; |
|
|
|
|
} else if (gReadyState == 'complete' || gReadyState == 'interactive') { |
|
|
|
|
window.setTimeout(callback); |
|
|
|
|
} else if (document.addEventListener) { |
|
|
|
|
document.addEventListener('localized', callback); |
|
|
|
|
} else if (document.attachEvent) { |
|
|
|
|
document.documentElement.attachEvent('onpropertychange', function(e) { |
|
|
|
|
if (e.propertyName === 'localized') { |
|
|
|
|
callback(); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
}) (window, document); |
|
|
|
|
|
|
|
|
|
// gettext-like shortcut for document.webL10n.get
|
|
|
|
|
if (window._ === undefined) { |
|
|
|
|
var _ = document.webL10n.get; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|