Inline-Translator

在每个段落之后加上中文翻译

当前为 2024-05-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @id Inline-Translator
  3. // @name Inline-Translator
  4. // @version 0.5
  5. // @namespace 12425
  6. // @author 12425
  7. // @description 在每个段落之后加上中文翻译
  8. // @license MIT
  9. // @include http://*
  10. // @include https://*
  11. // @run-at document-idle
  12. // @grant GM_registerMenuCommand
  13. // @grant GM_xmlhttpRequest
  14. // ==/UserScript==
  15.  
  16.  
  17. 'use strict';
  18.  
  19. (function() {
  20.  
  21. GM_registerMenuCommand("翻译成双语网页", () => {
  22. const root = document.body;
  23. if (root.classList.contains('_myscript_translated')) {
  24. return;
  25. }
  26. root.classList.add('_myscript_translated');
  27. translate(root);
  28. });
  29.  
  30.  
  31. const TranslationTags = new Set([
  32. "blockquote",
  33. "h1",
  34. "h2",
  35. "h3",
  36. "h4",
  37. "h5",
  38. "h6",
  39. "li",
  40. "ol",
  41. "p",
  42. ]);
  43.  
  44.  
  45. function fetch(text, callback, arg1, arg2) {
  46. GM_xmlhttpRequest({
  47. method: 'GET',
  48. url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=zh&dt=t&q=${encodeURIComponent(text)}`,
  49. onload: function(resp) {
  50. let json = null;
  51. try {
  52. json = JSON.parse(resp.responseText)[0];
  53. } catch {
  54. return;
  55. }
  56.  
  57. if (!json) {
  58. return;
  59. }
  60.  
  61. const data = json.map(function(item) {
  62. return item[0].trim();
  63. }).join('');
  64. if (text !== data) {
  65. callback(data, arg1, arg2);
  66. }
  67. },
  68. });
  69. }
  70.  
  71.  
  72. function simplify(node, attrs) {
  73. // return false if node is removed
  74. if (!removeEmptyNode(node)) {
  75. return false;
  76. }
  77. attrs.push(extractAttrs(node));
  78. Array.from(node.querySelectorAll('*')).forEach(function(el) {
  79. attrs.push(extractAttrs(el));
  80. });
  81. return true;
  82. }
  83.  
  84.  
  85. function removeEmptyNode(node) {
  86. // return false if node is removed
  87. const children = node.querySelectorAll('*');
  88. if (children.length) {
  89. let toDelete = true;
  90. for (const child of children) {
  91. if (removeEmptyNode(child)) {
  92. toDelete = false;
  93. }
  94. }
  95. if (!toDelete) {
  96. return true;
  97. }
  98. }
  99.  
  100. if (!node.innerHTML.trim().length) {
  101. node.remove();
  102. return false;
  103. }
  104.  
  105. return true;
  106. }
  107.  
  108.  
  109. function extractAttrs(node) {
  110. let attr = {};
  111. Array.from(node.attributes).forEach(attr => {
  112. attr[attr.name] = attr.value;
  113. node.removeAttribute(attr.name);
  114. });
  115. return attr;
  116. }
  117.  
  118.  
  119. function resumeAttrs(node, attrs) {
  120. Object.keys(attrs).forEach(key => {
  121. node.setAttribute(key, attrs[key]);
  122. });
  123. }
  124.  
  125.  
  126. function updateNode(data, node, attrs) {
  127. let newNode = document.createElement(node.tagName);
  128. newNode.innerHTML = data;
  129. node.insertAdjacentElement('afterend', newNode);
  130. resumeAttrs(newNode, attrs[0]);
  131. let i = 1;
  132. Array.from(node.querySelectorAll('*')).forEach(function(el) {
  133. resumeAttrs(el, attrs[i]);
  134. i++;
  135. });
  136. if (node.parentNode.tagName !== 'SMALL') {
  137. const smallNode = document.createElement('small');
  138. node.insertAdjacentElement('afterend', smallNode);
  139. smallNode.appendChild(node);
  140. }
  141. }
  142.  
  143.  
  144. function translate(node) {
  145. const tag = node.tagName.toLowerCase();
  146. if (!tag) {
  147. return;
  148. }
  149. if (!TranslationTags.has(tag)) {
  150. Array.from(node.children).forEach(translate);
  151. return;
  152. }
  153. let newNode = node.cloneNode(true);
  154. let attrs = [];
  155. if (simplify(newNode, attrs)) {
  156. fetch(newNode.innerHTML, updateNode, node, attrs);
  157. }
  158. }
  159.  
  160.  
  161. })();