| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- 'use strict';
- const template = {};
- tDocLoader();
- function t(key, params) {
- const cache = !params && t.cache[key];
- const s = cache || chrome.i18n.getMessage(key, params);
- if (s === '') {
- throw `Missing string "${key}"`;
- }
- if (!params && !cache) {
- t.cache[key] = s;
- }
- return s;
- }
- function tHTML(html, tag) {
- // body is a text node without HTML tags
- if (typeof html === 'string' && !tag && /<\w+/.test(html) === false) {
- return document.createTextNode(html);
- }
- if (typeof html === 'string') {
- // spaces are removed; use for an explicit space
- html = html.replace(/>\s+</g, '><').trim();
- if (tag) {
- html = `<${tag}>${html}</${tag}>`;
- }
- const body = t.DOMParser.parseFromString(html, 'text/html').body;
- if (html.includes('i18n-')) {
- tNodeList(body.getElementsByTagName('*'));
- }
- // the html string may contain more than one top-level node
- if (!body.childNodes[1]) {
- return body.firstChild;
- }
- const fragment = document.createDocumentFragment();
- while (body.firstChild) {
- fragment.appendChild(body.firstChild);
- }
- return fragment;
- }
- return html;
- }
- function tNodeList(nodes) {
- const PREFIX = 'i18n-';
- for (let n = nodes.length; --n >= 0;) {
- const node = nodes[n];
- if (node.nodeType !== Node.ELEMENT_NODE) {
- continue;
- }
- if (node.localName === 'template') {
- createTemplate(node);
- continue;
- }
- for (let a = node.attributes.length; --a >= 0;) {
- const attr = node.attributes[a];
- const name = attr.nodeName;
- if (!name.startsWith(PREFIX)) {
- continue;
- }
- const type = name.substr(PREFIX.length);
- const value = t(attr.value);
- let toInsert, before;
- switch (type) {
- case 'word-break':
- // we already know that: hasWordBreak
- break;
- case 'text':
- before = node.firstChild;
- // fallthrough to text-append
- case 'text-append':
- toInsert = createText(value);
- break;
- case 'html': {
- toInsert = createHtml(value);
- break;
- }
- default:
- node.setAttribute(type, value);
- }
- tDocLoader.pause();
- if (toInsert) {
- node.insertBefore(toInsert, before || null);
- }
- node.removeAttribute(name);
- }
- }
- function createTemplate(node) {
- const elements = node.content.querySelectorAll('*');
- tNodeList(elements);
- template[node.dataset.id] = elements[0];
- // compress inter-tag whitespace to reduce number of DOM nodes by 25%
- 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);
- }
- }
- tDocLoader.pause();
- toRemove.forEach(el => el.remove());
- }
- function createText(str) {
- return document.createTextNode(tWordBreak(str));
- }
- function createHtml(value) {
- // <a href=foo>bar</a> are the only recognizable HTML elements
- const rx = /(?:<a\s([^>]*)>([^<]*)<\/a>)?([^<]*)/gi;
- const bin = document.createDocumentFragment();
- for (let m; (m = rx.exec(value)) && m[0];) {
- const [, linkParams, linkText, nextText] = m;
- if (linkText) {
- const href = /\bhref\s*=\s*(\S+)/.exec(linkParams);
- const a = bin.appendChild(document.createElement('a'));
- a.href = href && href[1].replace(/^(["'])(.*)\1$/, '$2') || '';
- a.appendChild(createText(linkText));
- }
- if (nextText) {
- bin.appendChild(createText(nextText));
- }
- }
- return bin;
- }
- }
- function tDocLoader() {
- t.DOMParser = new DOMParser();
- t.cache = (() => {
- try {
- return JSON.parse(localStorage.L10N);
- } catch (e) {}
- })() || {};
- // reset L10N cache on UI language change
- const UIlang = chrome.i18n.getUILanguage();
- if (t.cache.browserUIlanguage !== UIlang) {
- t.cache = {browserUIlanguage: UIlang};
- localStorage.L10N = JSON.stringify(t.cache);
- }
- const cacheLength = Object.keys(t.cache).length;
- Object.assign(tDocLoader, {
- observer: new MutationObserver(process),
- start() {
- if (!tDocLoader.observing) {
- tDocLoader.observing = true;
- tDocLoader.observer.observe(document, {subtree: true, childList: true});
- }
- },
- stop() {
- tDocLoader.pause();
- document.removeEventListener('DOMContentLoaded', onLoad);
- },
- pause() {
- if (tDocLoader.observing) {
- tDocLoader.observing = false;
- tDocLoader.observer.disconnect();
- }
- },
- });
- tNodeList(document.getElementsByTagName('*'));
- tDocLoader.start();
- document.addEventListener('DOMContentLoaded', onLoad);
- function process(mutations) {
- for (const mutation of mutations) {
- tNodeList(mutation.addedNodes);
- }
- tDocLoader.start();
- }
- function onLoad() {
- tDocLoader.stop();
- process(tDocLoader.observer.takeRecords());
- if (cacheLength !== Object.keys(t.cache).length) {
- localStorage.L10N = JSON.stringify(t.cache);
- }
- }
- }
- function tWordBreak(text) {
- // adds soft hyphens every 10 characters to ensure the long words break before breaking the layout
- return text.length <= 10 ? text : text.replace(/[\d\w\u0080-\uFFFF]{10}|((?!\s)\W){10}/g, '$&\u00AD');
- }
|