localization.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. 'use strict';
  2. const template = {};
  3. tDocLoader();
  4. function t(key, params) {
  5. const cache = !params && t.cache[key];
  6. const s = cache || chrome.i18n.getMessage(key, params);
  7. if (s == '') {
  8. throw `Missing string "${key}"`;
  9. }
  10. if (!params && !cache) {
  11. t.cache[key] = s;
  12. }
  13. return s;
  14. }
  15. function tE(id, key, attr, esc) {
  16. if (attr) {
  17. document.getElementById(id).setAttribute(attr, t(key));
  18. } else if (typeof esc == 'undefined' || esc) {
  19. document.getElementById(id).appendChild(document.createTextNode(t(key)));
  20. } else {
  21. document.getElementById(id).innerHTML = t(key);
  22. }
  23. }
  24. function tHTML(html) {
  25. const node = document.createElement('div');
  26. node.innerHTML = html.replace(/>\s+</g, '><'); // spaces are removed; use &nbsp; for an explicit space
  27. if (html.includes('i18n-')) {
  28. tNodeList(node.getElementsByTagName('*'));
  29. }
  30. return node.firstElementChild;
  31. }
  32. function tNodeList(nodes) {
  33. const PREFIX = 'i18n-';
  34. for (let n = nodes.length; --n >= 0;) {
  35. const node = nodes[n];
  36. // skip non-ELEMENT_NODE
  37. if (node.nodeType != 1) {
  38. continue;
  39. }
  40. if (node.localName == 'template') {
  41. const elements = node.content.querySelectorAll('*');
  42. tNodeList(elements);
  43. template[node.dataset.id] = elements[0];
  44. // compress inter-tag whitespace to reduce number of DOM nodes by 25%
  45. const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
  46. const toRemove = [];
  47. while (walker.nextNode()) {
  48. const textNode = walker.currentNode;
  49. if (!textNode.nodeValue.trim()) {
  50. toRemove.push(textNode);
  51. }
  52. }
  53. toRemove.forEach(el => el.remove());
  54. continue;
  55. }
  56. for (let a = node.attributes.length; --a >= 0;) {
  57. const attr = node.attributes[a];
  58. const name = attr.nodeName;
  59. if (!name.startsWith(PREFIX)) {
  60. continue;
  61. }
  62. const type = name.substr(PREFIX.length);
  63. const value = t(attr.value);
  64. switch (type) {
  65. case 'text':
  66. node.insertBefore(document.createTextNode(value), node.firstChild);
  67. break;
  68. case 'text-append':
  69. node.appendChild(document.createTextNode(value));
  70. break;
  71. case 'html':
  72. node.insertAdjacentHTML('afterbegin', value);
  73. break;
  74. default:
  75. node.setAttribute(type, value);
  76. }
  77. node.removeAttribute(name);
  78. }
  79. }
  80. }
  81. function tDocLoader() {
  82. t.cache = tryJSONparse(localStorage.L10N) || {};
  83. const cacheLength = Object.keys(t.cache).length;
  84. // localize HEAD
  85. tNodeList(document.getElementsByTagName('*'));
  86. // localize BODY
  87. const process = mutations => {
  88. for (const mutation of mutations) {
  89. tNodeList(mutation.addedNodes);
  90. }
  91. };
  92. const observer = new MutationObserver(process);
  93. const onLoad = () => {
  94. tDocLoader.stop();
  95. process(observer.takeRecords());
  96. if (cacheLength != Object.keys(t.cache).length) {
  97. localStorage.L10N = JSON.stringify(t.cache);
  98. }
  99. };
  100. tDocLoader.start = () => {
  101. observer.observe(document, {subtree: true, childList: true});
  102. };
  103. tDocLoader.stop = () => {
  104. observer.disconnect();
  105. document.removeEventListener('DOMContentLoaded', onLoad);
  106. };
  107. tDocLoader.start();
  108. document.addEventListener('DOMContentLoaded', onLoad);
  109. }