UserScript Translation Engine

Translate strings by given translations

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/7081/28938/UserScript%20Translation%20Engine.js

  1. // ==UserScript==
  2. // @name UserScript Translation Engine
  3. // @namespace org.jixun.us.translation
  4. // @description Translate strings by given translations
  5. // @version 1.0
  6. // @run-at document-start
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. var Translation = (function () {
  11. var _each = function (arr, eachCb, defValue) {
  12. if (!arr || !arr.length) return ;
  13.  
  14. for (var i = arr.length, ret; i-- ; )
  15. // If there's something to return, then return it.
  16. if (ret = eachCb (arr[i], i))
  17. return ret;
  18.  
  19. return defValue ;
  20. };
  21. var _filterPop = function (arr, fn) {
  22. if (arr && arr.length)
  23. for (var i = arr.length; i--; )
  24. if (fn(arr[i]))
  25. return arr[i];
  26. };
  27.  
  28. var extend = function (src) {
  29. var args = arguments, argl = args.length;
  30. for (var i = 1; i < argl; i++) {
  31. for (var x in args[i]) {
  32. if (args[i].hasOwnProperty(x)) {
  33. if (src[x] instanceof Object) {
  34. extend (src[x], args[i][x]);
  35. } else {
  36. src[x] = args[i][x];
  37. }
  38. }
  39. }
  40. }
  41.  
  42. return src;
  43. };
  44.  
  45. var Translation = function (lang) {
  46. this.resetLang ();
  47. this.setLang (lang);
  48. };
  49.  
  50. // 获取浏览器预设语言
  51. Translation.getLang = function (lang) {
  52. var x = _filterPop(navigator.languages.slice().reverse(), function (x) { return lang[x] });
  53. return lang[x] || {};
  54. };
  55.  
  56. Translation.prototype = {
  57. run: function (node) {
  58. var self = this;
  59. node = node || document.body || document;
  60.  
  61. this.mo = new MutationObserver (function (m) {
  62. _each (m, function (q) {
  63. _each (q.addedNodes, function (e) {
  64. // Firebug keep injects their stuff, ignore
  65. if (e.className && e.className.indexOf ('firebug') != -1)
  66. return ;
  67.  
  68. self.translateNode (e);
  69. });
  70.  
  71. if (q.type == 'attributes') {
  72. var x = self.findAttrTranslation (q.target, q.attributeName, q.target.getAttribute(q.attributeName));
  73. if (x) {
  74. q.target.setAttribute (q.attributeName, x);
  75. }
  76. }
  77. });
  78. });
  79.  
  80. this.mo.observe (node, {
  81. childList: true,
  82. subtree: true,
  83. characterData: true,
  84. attributes: true
  85. });
  86.  
  87. this.translateNode (node);
  88. },
  89.  
  90. excludeTags: ['code', 'pre', 'script', 'style', 'link', 'meta'],
  91. excludeFromTag: function (node, tagName) {
  92. var n = node;
  93. while (n = n.parentNode)
  94. if (-1 != this.excludeTags.indexOf(n))
  95. return true;
  96.  
  97. return false;
  98.  
  99. },
  100.  
  101. translateNode: function (node) {
  102. _each(node.getElementsByTagName('*'), this.applyAttr.bind(this));
  103.  
  104. var self = this;
  105.  
  106. var walker = document.createTreeWalker (node, 4 /* NodeFilter.SHOW_TEXT */, function (textNode) {
  107. return (
  108. self.excludeFromTag(textNode.parentNode)
  109. || textNode.nodeValue.trim() === ''
  110. ? 2 /* NodeFilter.FILTER_REJECT */
  111. : 1 /* NodeFilter.FILTER_ACCEPT */
  112. );
  113. }, false);
  114.  
  115. // Loop through text nodes.
  116. while (node = walker.nextNode())
  117. this.applyNode (node);
  118. },
  119.  
  120. applyNode: function (node) {
  121. this.applyText (node);
  122. this.applyAttr (node.parentNode);
  123. },
  124.  
  125. findTranslation: function (lang, str) {
  126. var args = arguments;
  127. var stack = [ lang ];
  128. var r = lang;
  129. for (var i = 2; i < args.length; i++) {
  130. r = r[args[i]];
  131. if (!r) break;
  132.  
  133. stack.push (r);
  134. }
  135.  
  136. if (stack.length) {
  137. for (i = stack.length; i--; ) {
  138. if (stack[i][str] && 'string' == typeof stack[i][str]) {
  139. return stack[i][str];
  140. }
  141.  
  142. if (stack[i].regex) {
  143. r = _filterPop(stack[i].regex, function (re) {
  144. return re[0].test( str );
  145. });
  146. if (r) return str.replace(r[0], r[1]);
  147. }
  148. }
  149. }
  150. },
  151.  
  152. applyText: function (node) {
  153. var self = this;
  154. var v = node.nodeValue.trim();
  155. if (!v) return ;
  156.  
  157. var l = (this.findTranslation (this.lang.node, v, 'tag', node.parentNode.tagName)
  158. || this.findTranslation (this.lang.node, v, 'str'));
  159.  
  160. if (l) node.nodeValue = l;
  161. },
  162.  
  163. findAttrTranslation: function (node, attrName, attrVal) {
  164. return (this.findTranslation (this.lang.attr, attrVal, 'tag', node.tagName, attrName)
  165. || this.findTranslation (this.lang.attr, attrVal, 'str', attrName))
  166. },
  167.  
  168. applyAttr: function (node) {
  169. var self = this;
  170. var tag = node.tagName;
  171. var l;
  172.  
  173. _each(node.attributes, function (attr) {
  174. l = self.findAttrTranslation (node, attr.name, attr.value);
  175.  
  176. if (l) attr.value = l;
  177. });
  178. },
  179.  
  180. /**
  181. * Set translation profile
  182. * @param {Object} lang Language translation hashmap.
  183. */
  184. setLang: function (lang) {
  185. if (lang instanceof Object)
  186. extend (this.lang, lang);
  187. },
  188.  
  189. /**
  190. * Get translation
  191. * @return {Object} Language Translation hashmap
  192. */
  193. getLang: function () {
  194. return extend({}, this.lang);
  195. },
  196.  
  197. resetLang: function () {
  198. this.lang = {node: {tag: {}, str: {}}, attr: {tag: {}, str: {}}};
  199. }
  200. };
  201.  
  202. return Translation;
  203. })();