🏠 Home 

外语终结者

识别非中文字符,如果长度大于5且翻译文本中不含中文,则翻译并且替换原始文本到中文


Installer dette script?
Skaberens foreslåede script

Du vil måske også kunne lide Greasyfork Lim håndværker


Installer dette script
  1. // ==UserScript==
  2. // @name 外语终结者
  3. // @namespace https://github.com/#####GodMan/UserScripts
  4. // @version 1.4.0.0
  5. // @description 识别非中文字符,如果长度大于5且翻译文本中不含中文,则翻译并且替换原始文本到中文
  6. // @name:zh-CN 外语终结者
  7. // @description:zh-CN 识别非中文字符,如果长度大于5且翻译文本中不含中文,则翻译并且替换原始文本到中文
  8. // @license MIT
  9. // @author 人民的勤务员 <china.qinwuyuan@gmail.com>
  10. // @match *://*/*
  11. // @grant GM_xmlhttpRequest
  12. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAzFBMVEUAAADqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTInqTIn////yp7387vL2zdjvka7uian99ff30dv52eH++Pr64ujwnLXtgKPyrL/rVY32yNT1xtPrXpHrWY764ef99/j++vvqUIr65Or53+bwm7TugqTtfKHteqD1wc/88fT2ytX76Oz41N30u8vxpLvzscPsapdcPEFQAAAAHXRSTlMA2JQoSQiLNvfnfz8Q8My5sW/fX1ROGr+hmCAbpYFuRDsAAAOsSURBVHja7dtrU9pAFMbxVYvi/X7vWQLBEA0QInewivX7f6dqu7OHsJDNMrN7pmN+L8U2/4aHRNrCFlzt/7Br54CtVLq82QX7jrZ3lh/+1PrRseGSKX6cgUvXdyxtGxw7vEid/mNwb4+hE6BwSnT+0b44/j4QORPXhA2gssm+nAOZ8h2eABrbjLEdILRL9xLAF8IRkMCrURkonbAtILVRBOQLmNSED9CwFFDlQgU0ioBvHOB7KWEwHcZOA2pc0X2rEQWgZEQcwLuznnnAR2XRgAtV5SEMWCGIjQNq3ID+F/Vj4gAe9IgD+Iw6oDtyEBD7KcPpC0eJ/QBVL2pwyXcYgMZ1+V1vJAEwbsgVdNYK8KqCx4VBVejmCYBIdg7XCghWX4rruQJ6colTmgCY4m9FEzDkQkgU4Ms5FQFFQBFQBKwVED4KAy60xReGjXwBI08YOL4dq4qAbxTQrgsBTUDzWT40IQmIuNQmCQi5VO+sGTB5WORxIVQegrQnPued4C+pfvE5ifuADk4QZ+gyIOIpbecBIU957jgNwAniDF0G4ARR4jQAJ4gmTgMirmg5DQjl+F7l1bDpMAAn2Ipwhg4DcIJPcQNn6CygKQ8aAlQ1MzQLwB8y3nNNsDL/dLRd/bMdTrDxNbx+1gwtBKT+zMr5UNgIaOEE4VM8twiFjYDm4gFxhmNQ6QMGnjAClGuCkfKUrBXgccFfZ4KQmmHPZoB6FVzysrQdoE4wPcPEfkBHHuylIvVxhtYDIp6pZT0g5JnqTcsBI65RsRzQ4hqJpQCcoM7ENACf1UfQqnCtlnFAoLmQKrG6GZoGTOULu6e/CuZQMQ0Y4s0l/wQfqovkOgamAXFX3l3GunfEGVt/wBkaBsAbPn/ZBZWs0+zjDE0DfC41oowd4G33ednQXnGGhgGQcPTSHvop8fIbseo3nh/TgFGXr1ZbfiNWxThD0wCY5QnACYagUH42NAvoBVkB6gR1W2qbBkDc1wRoJogzxO8wCoA40AQoE9TM0DQAerPuigD1HbH+XpnoA1SjJCugk+f9D0aODQKQ/9ZdEqCdIBrhDI0CUGc4DUIvxReXek94xYFlvMvqiy/8f/+rVioCioAiwEJACUhdk33IBj9mcwOULhi7BELlA8ZKh0DnhH3aAxL4ebsDulNwwv46BSLlLfbPMdA4ZcLBGVD4yaStXXDvuMTQ/RG4dlti80qb4NThOVu043CK5c0rhjDh1s0UNvbw8ErD+famXXsX92zeHzuqGriI4sATAAAAAElFTkSuQmCC
  13. // @iconbak https://immersive-translate.owenyoung.com/favicon.png
  14. // @grant GM_getValue
  15. // @grant GM_addStyle
  16. // @grant GM_setValue
  17. // @grant GM_registerMenuCommand
  18. // @connect translate.googleapis.com
  19. // @supportURL https://github.com/#####GodMan/UserScripts/issues
  20. // @homepageURL https://github.com/#####GodMan/UserScripts
  21. // ==/UserScript==
  22. //https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=zh-CN&dj=1&dt=t&dt=rm&q=你好
  23. (function () {
  24. 'use strict'
  25. // 添加一个对象来跟踪已经翻译过的节点
  26. var translatedNodes = {}
  27. var skipClasses = GM_getValue('skipClasses') || ['prettyprint', 'linenums', 'lang-js']
  28. // 创建一个 MutationObserver 实例
  29. var observer = new MutationObserver(function (mutationsList) {
  30. // 遍历每一个发生变化的 mutation
  31. for (var mutation of mutationsList) {
  32. // 检查是否有子节点被添加到文档中
  33. //if (mutation.type === 'childList') {
  34. // 遍历新增加的节点
  35. mutation.addedNodes.forEach(function (addedNode) {
  36. // 如果新增节点是元素节点,可以递归扫描子节点以查找文本内容
  37. if (addedNode.nodeType === Node.ELEMENT_NODE) {
  38. if (!isInsidePreCode(addedNode)) {
  39. scanTextNodes(addedNode)
  40. }
  41. // scanTextNodes(addedNode);
  42. // console.log("New element node added:", addedNode.nodeName);
  43. }
  44. })
  45. // }
  46. }
  47. })
  48. function isInsidePreCode(node) {
  49. while (node) {
  50. if (node.matches && node.matches('pre')) {
  51. return true
  52. }
  53. node = node.parentElement
  54. }
  55. return false
  56. }
  57. // 配置 MutationObserver 监听的目标节点和观察的属性
  58. var config = { childList: true, subtree: true }
  59. // 开始监听文档的变化
  60. observer.observe(document.body, config)
  61. // 添加或移除翻译功能
  62. function toggleTranslation() {
  63. var currentSite = window.location.hostname
  64. var enabledSites = GM_getValue('enabledSites') || []
  65. if (enabledSites.includes(currentSite)) {
  66. enabledSites = enabledSites.filter(function (site) {
  67. return site !== currentSite
  68. })
  69. GM_setValue('enabledSites', enabledSites)
  70. Toast('已移出翻译!', 3000, 'rgba(0, 128, 0, 0.7)', '#fff')
  71. location.reload()
  72. } else {
  73. enabledSites.push(currentSite)
  74. GM_setValue('enabledSites', enabledSites)
  75. Toast('已添加翻译!', 3000, 'rgba(0, 128, 0, 0.7)', '#fff')
  76. location.reload()
  77. }
  78. }
  79. function shouldSkipElement(element) {
  80. // 跳过类名包含 "prettyprint"、"linenums" 和 "lang-js" 的元素
  81. if (element.classList.contains('prettyprint') ||
  82. element.classList.contains('linenums') ||
  83. element.classList.contains('lang-js')) {
  84. return true
  85. }
  86. // 还可以根据其他需要跳过的条件进行判断
  87. return false
  88. }
  89. // 编辑生效站点列表
  90. function editSites() {
  91. // 检查是否已经存在编辑窗口
  92. if (document.getElementById('editorContainer')) {
  93. return
  94. }
  95. var enabledSites = GM_getValue('enabledSites') || []
  96. var siteList = enabledSites.join('\n')
  97. var editorWindow = document.createElement('div')
  98. editorWindow.innerHTML = `
  99. <div id="editorContainer" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; border-radius: 5px; z-index: 9999;">
  100. <textarea id="siteList" style="width: 300px; height: 200px; margin-bottom: 10px;">${siteList}</textarea><br>
  101. <button id="saveButton" style="padding: 5px 10px; background: #007bff; color: #fff; border: none; border-radius: 3px; cursor: pointer;">保存</button>
  102. <button id="cancelButton" style="margin-left: 10px; padding: 5px 10px; background: #ccc; color: #333; border: none; border-radius: 3px; cursor: pointer;">取消</button>
  103. </div>
  104. <div id="overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 999;"></div>
  105. `
  106. document.body.appendChild(editorWindow)
  107. var saveButton = document.getElementById('saveButton')
  108. var cancelButton = document.getElementById('cancelButton')
  109. var siteListTextarea = document.getElementById('siteList')
  110. var overlay = document.getElementById('overlay')
  111. saveButton.addEventListener('click', function () {
  112. var editedSites = siteListTextarea.value.split('\n').map(function (site) {
  113. return site.trim()
  114. }).filter(Boolean)
  115. GM_setValue('enabledSites', editedSites)
  116. Toast('已保存生效站点列表!', 3000, 'rgba(0, 128, 0, 0.7)', '#fff')
  117. document.body.removeChild(editorWindow)
  118. document.body.removeChild(overlay)
  119. })
  120. cancelButton.addEventListener('click', function () {
  121. document.body.removeChild(editorWindow)
  122. document.body.removeChild(overlay)
  123. })
  124. }
  125. // 注册油猴菜单命令
  126. GM_registerMenuCommand(isCurrentSiteEnabled() ? '移出翻译' : '添加翻译', toggleTranslation)
  127. GM_registerMenuCommand('编辑生效站点', editSites)
  128. // GM_registerMenuCommand('开启/关闭高亮和删除功能', toggleMouseListeners)
  129. // 递归遍历给定节点及其后代(深度优先搜索)
  130. function scanTextNodes(node) {
  131. if (!node.parentNode || !document.body.contains(node)) {
  132. return
  133. }
  134. switch (node.nodeType) {
  135. case Node.ELEMENT_NODE:
  136. if (node.tagName.toLowerCase() === 'script' || node.isContentEditable) {
  137. return
  138. }
  139. if (shouldSkipElement(node)) {
  140. return
  141. }
  142. if (shouldTranslateNode(node)) { // 检查是否应该翻译该节点
  143. node.childNodes.forEach(scanTextNodes)
  144. }
  145. break
  146. case Node.TEXT_NODE:
  147. var text = node.nodeValue.trim()
  148. if (!containsChinese(text) && text.length > 5 && isCurrentSiteEnabled() && shouldTranslateNode(node.parentNode)) {
  149. translateText(node, text)
  150. }
  151. break
  152. }
  153. }
  154. // 检查当前站点是否在生效站点列表中
  155. function isCurrentSiteEnabled() {
  156. var currentSite = window.location.hostname
  157. var enabledSites = GM_getValue('enabledSites') || []
  158. return enabledSites.includes(currentSite)
  159. }
  160. // 检查字符串是否含中文或者片假字
  161. function containsChinese(str) {
  162. // 日文片假字范围:\u3040-\u30FF\uFF66-\uFF9F
  163. var katakanaRegex = /[\u3040-\u30FF\uFF66-\uFF9F]/
  164. if (katakanaRegex.test(str)) {
  165. return false // 如果包含日文片假字,则返回 false
  166. }
  167. // 中文字符范围:\u4E00-\u9FA5
  168. var chineseRegex = /[\u4E00-\u9FA5]/
  169. return chineseRegex.test(str) // 返回是否包含中文字符
  170. }
  171. // 检查字符串是否含有数字
  172. function containsNumbers(str) {
  173. return /^[a-z\u4E00-\u9FA5\s]+$/i.test(str)
  174. }
  175. // 检查字符串是否为网址
  176. function isURL(str) {
  177. return /((https?:\/\/|www\.)[\x21-\x7e]+[\w/=]|\w([\w.\-])+@\w[\w.\-]+\.(com|cn|org|net|info|tv|cc|gov|edu)|(\w[\w.\-]+\.(com|cn|org|net|info|tv|cc|gov|edu))(\/[\x21-\x7e]*[\w/])?|ed2k:\/\/[\x21-\x7e]+\|\/|thunder:\/\/[\x21-\x7e]+=)/i.test(str)
  178. }
  179. // 检查是否应该翻译该节点
  180. function shouldTranslateNode(node) {
  181. // 检查是否已经翻译过该节点
  182. return !translatedNodes[node.textContent]
  183. }
  184. // 翻译文本
  185. function translateText(node, text) {
  186. // 检查是否已经翻译过该节点
  187. if (translatedNodes[node.textContent]) {
  188. return
  189. }
  190. var api = 'https://translate.googleapis.com/translate_a/single'
  191. var params = {
  192. client: 'gtx',
  193. dt: 't',
  194. sl: 'auto',
  195. tl: 'zh-CN',
  196. q: text
  197. }
  198. GM_xmlhttpRequest({
  199. method: 'GET',
  200. url: api + buildQueryString(params),
  201. onload: function (response) {
  202. try {
  203. var data = JSON.parse(response.responseText.replace('\'', '\u2019'))
  204. var translatedText = data[0].reduce((acc, item) => acc + item[0], '')
  205. showTranslation(node, text, translatedText)
  206. // 标记该节点已经翻译过
  207. translatedNodes[node.textContent] = true
  208. } catch (error) {
  209. console.error('翻译失败:', error)
  210. }
  211. },
  212. onerror: function (response) {
  213. console.error('请求翻译失败:', response.statusText)
  214. }
  215. })
  216. }
  217. // 构建查询字符串
  218. function buildQueryString(params) {
  219. return '?' + Object.keys(params).map(function (key) {
  220. return encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
  221. }).join('&')
  222. }
  223. // 显示翻译
  224. // 显示翻译
  225. function showTranslation(node, originalText, translatedText) {
  226. // 创建 <ruby> 元素和 <rt> 元素
  227. var rubyElement = document.createElement('ruby') // 创建包含音标的元素
  228. var rtElement = document.createElement('rt') // 创建音标元素
  229. // 设置音标元素的文本内容
  230. rtElement.textContent = translatedText
  231. // 设置音标元素的字体大小
  232. rtElement.style.fontSize = 'smaller'
  233. // 设置音标元素的文本左对齐
  234. rtElement.style.textAlign = 'left'
  235. rtElement.style.color = 'red' // 设置音标元素的颜色
  236. // 将音标元素添加到包含音标的元素中
  237. rubyElement.appendChild(rtElement)
  238. // 在中文翻译节点之后插入包含音标的元素
  239. //node.parentNode.insertBefore(rubyElement, node.nextSibling);
  240. // 将原始的非中文字符串替换为翻译后的文本
  241. node.textContent = translatedText // 替换原始的非中文字符串
  242. }
  243. // 主函数
  244. function main() {
  245. // 在页面加载时进行一次扫描
  246. scanTextNodes(document.body)
  247. }
  248. // 启动脚本
  249. main()
  250. // 弹出提示信息
  251. function Toast(msg, duration, backgroundColor, textColor) {
  252. duration = isNaN(duration) ? 3000 : duration
  253. backgroundColor = backgroundColor || 'rgba(0, 0, 0, 0.7)'
  254. textColor = textColor || 'rgb(255, 255, 255)'
  255. var m = document.createElement('div')
  256. m.innerHTML = msg
  257. m.style.cssText = 'max-width:60%;min-width: 150px;padding:0 14px;height: 40px;color: ' + textColor + ';line-height: 40px;text-align: center;border-radius: 12px;position: fixed;top: 95%;left: 50%;transform: translate(-50%, -50%);z-index: 2147483647;background: ' + backgroundColor + ';font-size: 16px;'
  258. document.body.appendChild(m)
  259. setTimeout(function () {
  260. var d = 0.5
  261. m.style.transition = '-webkit-transform ' + d + 's ease-in, opacity ' + d + 's ease-in'
  262. m.style.opacity = '0'
  263. setTimeout(function () {
  264. document.body.removeChild(m)
  265. }, d * 1000)
  266. }, duration)
  267. }
  268. })()