2017-03-26 02:30:59 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const template = {};
|
2015-03-27 10:30:07 +00:00
|
|
|
tDocLoader();
|
|
|
|
|
2017-03-26 02:30:59 +00:00
|
|
|
|
2012-04-16 01:56:12 +00:00
|
|
|
function t(key, params) {
|
2017-04-17 15:54:39 +00:00
|
|
|
const cache = !params && t.cache[key];
|
|
|
|
const s = cache || chrome.i18n.getMessage(key, params);
|
2017-07-16 18:02:00 +00:00
|
|
|
if (s === '') {
|
2017-03-26 02:30:59 +00:00
|
|
|
throw `Missing string "${key}"`;
|
|
|
|
}
|
2017-04-17 15:54:39 +00:00
|
|
|
if (!params && !cache) {
|
|
|
|
t.cache[key] = s;
|
|
|
|
}
|
2017-03-26 02:30:59 +00:00
|
|
|
return s;
|
2012-04-16 01:56:12 +00:00
|
|
|
}
|
2017-03-26 02:30:59 +00:00
|
|
|
|
|
|
|
|
2017-07-19 10:06:02 +00:00
|
|
|
function tHTML(html, tag) {
|
|
|
|
// body is a text node without HTML tags
|
2017-07-19 11:34:31 +00:00
|
|
|
if (typeof html === 'string' && !tag && /<\w+/.test(html) === false) {
|
2017-07-19 10:06:02 +00:00
|
|
|
return document.createTextNode(html);
|
2017-03-26 02:30:59 +00:00
|
|
|
}
|
2017-07-19 10:06:02 +00:00
|
|
|
if (typeof html === 'string') {
|
2017-08-18 14:00:06 +00:00
|
|
|
// spaces are removed; use for an explicit space
|
|
|
|
html = html.replace(/>\s+</g, '><').trim();
|
2017-07-19 10:06:02 +00:00
|
|
|
if (tag) {
|
|
|
|
html = `<${tag}>${html}</${tag}>`;
|
|
|
|
}
|
2017-07-22 04:37:13 +00:00
|
|
|
const body = t.DOMParser.parseFromString(html, 'text/html').body;
|
2017-07-19 10:06:02 +00:00
|
|
|
if (html.includes('i18n-')) {
|
2017-07-22 04:37:13 +00:00
|
|
|
tNodeList(body.getElementsByTagName('*'));
|
2017-07-19 10:06:02 +00:00
|
|
|
}
|
2017-08-18 14:00:06 +00:00
|
|
|
// the html string may contain more than one top-level node
|
|
|
|
if (!body.childNodes[1]) {
|
|
|
|
return body.firstChild;
|
2017-07-22 04:37:13 +00:00
|
|
|
}
|
|
|
|
const fragment = document.createDocumentFragment();
|
2017-08-18 14:00:06 +00:00
|
|
|
while (body.firstChild) {
|
|
|
|
fragment.appendChild(body.firstChild);
|
2017-07-22 04:37:13 +00:00
|
|
|
}
|
|
|
|
return fragment;
|
2017-07-19 10:06:02 +00:00
|
|
|
}
|
|
|
|
return html;
|
2015-03-27 10:30:07 +00:00
|
|
|
}
|
|
|
|
|
2017-03-26 02:30:59 +00:00
|
|
|
|
2015-03-27 10:30:07 +00:00
|
|
|
function tNodeList(nodes) {
|
2017-04-17 15:54:39 +00:00
|
|
|
const PREFIX = 'i18n-';
|
|
|
|
for (let n = nodes.length; --n >= 0;) {
|
|
|
|
const node = nodes[n];
|
2017-03-26 02:30:59 +00:00
|
|
|
// skip non-ELEMENT_NODE
|
2017-07-16 18:02:00 +00:00
|
|
|
if (node.nodeType !== 1) {
|
2017-03-26 02:30:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
2017-07-16 18:02:00 +00:00
|
|
|
if (node.localName === 'template') {
|
2017-04-17 15:54:39 +00:00
|
|
|
const elements = node.content.querySelectorAll('*');
|
|
|
|
tNodeList(elements);
|
|
|
|
template[node.dataset.id] = elements[0];
|
2017-04-07 12:24:55 +00:00
|
|
|
// compress inter-tag whitespace to reduce number of DOM nodes by 25%
|
2017-04-17 15:54:39 +00:00
|
|
|
const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
|
|
|
|
const toRemove = [];
|
|
|
|
while (walker.nextNode()) {
|
|
|
|
const textNode = walker.currentNode;
|
|
|
|
if (!textNode.nodeValue.trim()) {
|
|
|
|
toRemove.push(textNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
toRemove.forEach(el => el.remove());
|
2017-03-26 02:30:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
2017-04-17 15:54:39 +00:00
|
|
|
for (let a = node.attributes.length; --a >= 0;) {
|
|
|
|
const attr = node.attributes[a];
|
|
|
|
const name = attr.nodeName;
|
|
|
|
if (!name.startsWith(PREFIX)) {
|
2017-03-26 02:30:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
2017-04-17 15:54:39 +00:00
|
|
|
const type = name.substr(PREFIX.length);
|
2017-03-26 02:30:59 +00:00
|
|
|
const value = t(attr.value);
|
2017-04-17 15:54:39 +00:00
|
|
|
switch (type) {
|
2017-03-26 02:30:59 +00:00
|
|
|
case 'text':
|
|
|
|
node.insertBefore(document.createTextNode(value), node.firstChild);
|
|
|
|
break;
|
2017-04-10 06:47:09 +00:00
|
|
|
case 'text-append':
|
|
|
|
node.appendChild(document.createTextNode(value));
|
|
|
|
break;
|
2017-03-26 02:30:59 +00:00
|
|
|
case 'html':
|
2017-07-19 12:13:24 +00:00
|
|
|
// localized strings only allow having text nodes and links
|
|
|
|
node.textContent = '';
|
|
|
|
[...tHTML(value, 'div').childNodes]
|
|
|
|
.filter(a => a.nodeType === a.TEXT_NODE || a.tagName === 'A')
|
|
|
|
.forEach(n => node.appendChild(n));
|
2017-03-26 02:30:59 +00:00
|
|
|
break;
|
|
|
|
default:
|
2017-04-17 15:54:39 +00:00
|
|
|
node.setAttribute(type, value);
|
2017-03-26 02:30:59 +00:00
|
|
|
}
|
2017-04-17 15:54:39 +00:00
|
|
|
node.removeAttribute(name);
|
2017-03-26 02:30:59 +00:00
|
|
|
}
|
|
|
|
}
|
2015-03-27 10:30:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-26 02:30:59 +00:00
|
|
|
function tDocLoader() {
|
2017-07-22 04:37:13 +00:00
|
|
|
t.DOMParser = new DOMParser();
|
2017-09-12 12:08:09 +00:00
|
|
|
t.cache = (function () {
|
|
|
|
try {
|
|
|
|
return JSON.parse(localStorage.L10N);
|
|
|
|
} catch (e) {}
|
|
|
|
})() || {};
|
2017-07-11 15:03:35 +00:00
|
|
|
|
|
|
|
// reset L10N cache on UI language change
|
|
|
|
const UIlang = chrome.i18n.getUILanguage();
|
2017-07-16 18:02:00 +00:00
|
|
|
if (t.cache.browserUIlanguage !== UIlang) {
|
2017-07-11 15:03:35 +00:00
|
|
|
t.cache = {browserUIlanguage: UIlang};
|
|
|
|
localStorage.L10N = JSON.stringify(t.cache);
|
|
|
|
}
|
|
|
|
|
2017-04-17 15:54:39 +00:00
|
|
|
const cacheLength = Object.keys(t.cache).length;
|
2017-07-11 15:03:35 +00:00
|
|
|
|
2017-03-26 02:30:59 +00:00
|
|
|
// localize HEAD
|
2017-04-09 06:43:51 +00:00
|
|
|
tNodeList(document.getElementsByTagName('*'));
|
2017-03-23 17:49:23 +00:00
|
|
|
|
2017-03-26 02:30:59 +00:00
|
|
|
// localize BODY
|
2017-04-09 05:10:25 +00:00
|
|
|
const process = mutations => {
|
2017-03-26 02:30:59 +00:00
|
|
|
for (const mutation of mutations) {
|
|
|
|
tNodeList(mutation.addedNodes);
|
|
|
|
}
|
2017-04-09 05:10:25 +00:00
|
|
|
};
|
|
|
|
const observer = new MutationObserver(process);
|
2017-03-26 02:30:59 +00:00
|
|
|
const onLoad = () => {
|
|
|
|
tDocLoader.stop();
|
2017-04-09 05:10:25 +00:00
|
|
|
process(observer.takeRecords());
|
2017-07-16 18:02:00 +00:00
|
|
|
if (cacheLength !== Object.keys(t.cache).length) {
|
2017-04-17 15:54:39 +00:00
|
|
|
localStorage.L10N = JSON.stringify(t.cache);
|
|
|
|
}
|
2017-03-26 02:30:59 +00:00
|
|
|
};
|
|
|
|
tDocLoader.start = () => {
|
|
|
|
observer.observe(document, {subtree: true, childList: true});
|
|
|
|
};
|
|
|
|
tDocLoader.stop = () => {
|
|
|
|
observer.disconnect();
|
|
|
|
document.removeEventListener('DOMContentLoaded', onLoad);
|
|
|
|
};
|
|
|
|
tDocLoader.start();
|
|
|
|
document.addEventListener('DOMContentLoaded', onLoad);
|
2015-03-27 10:30:07 +00:00
|
|
|
}
|