Greasy Fork is available in English.

一键去除在线翻译网站的换行符

在各大在线翻译网站的页面上增加了一个“格式化”按钮,用来移除从PDF等复制过来的文本中包含的回车符、换行符、"\n"等,支持DeepL翻译、谷歌翻译、百度翻译、网易有道翻译

  1. // ==UserScript==
  2. // @name 一键去除在线翻译网站的换行符
  3. // @namespace https://greasyfork.org/zh-CN/scripts/390059-%E7%BF%BB%E8%AF%91%E6%8F%92%E4%BB%B6-%E5%8E%BB%E9%99%A4%E6%8D%A2%E8%A1%8C
  4. // @version 2.4.3
  5. // @description 在各大在线翻译网站的页面上增加了一个“格式化”按钮,用来移除从PDF等复制过来的文本中包含的回车符、换行符、"\n"等,支持DeepL翻译、谷歌翻译、百度翻译、网易有道翻译
  6. // @author Kevin Chen
  7. // @match https://fanyi.baidu.com/*
  8. // @match https://fanyi.youdao.com/*
  9. // @match https://translate.google.com/*
  10. // @match https://translate.google.com.hk/*
  11. // @match https://www.deepl.com/translator
  12. // @match https://fanyi.qq.com/*
  13. // @match https://dict.cnki.net/*
  14. // @icon https://translate.google.cn/favicon.ico
  15. // @grant GM_addStyle
  16. // @run-at document-end
  17. // @license MIT
  18. // ==/UserScript==
  19.  
  20. const FORMAT_CN = '格式化'
  21. const LOADING_WAIT_TIME = 1000
  22.  
  23. // DeepL翻译CONFIG配置文件
  24. const DEEPL_TRANSLATE_CONFIG = {
  25. host: 'www.deepl.com',
  26. inputAreaSelector: '#textareasContainer section div d-textarea > div:nth-child(1)',
  27. containerSelector: '#gatsby-focus-wrapper div.contents',
  28. translateButtonSelector: null,
  29. buttonClass: 'myCustomDeepLButtonClass',
  30. createButtonHtml: `<button type="button" tabindex="100" class="myCustomDeepLButtonClass"><span style="outline: none;">${FORMAT_CN}</span></button>`,
  31. }
  32.  
  33. // 谷歌翻译(香港)CONFIG配置文件
  34. const GOOGLE_FANYI_CONFIG = {
  35. host: 'translate.google.com.hk',
  36. inputAreaSelector: 'textarea',
  37. containerSelector: '#yDmH0d > c-wiz nav',
  38. translateButtonSelector: null,
  39. buttonClass: 'myCustomGoogleButtonClass',
  40. createButtonHtml: `<input class="myCustomGoogleButtonClass" type="button" value="${FORMAT_CN}">`,
  41. }
  42.  
  43. // 谷歌翻译(国际)CONFIG配置文件
  44. const GOOGLE_TRANSLATE_CONFIG = {
  45. host: 'translate.google.com',
  46. inputAreaSelector: 'textarea',
  47. containerSelector: '#yDmH0d > c-wiz nav',
  48. translateButtonSelector: null,
  49. buttonClass: 'myCustomGoogleButtonClass',
  50. createButtonHtml: `<input class="myCustomGoogleButtonClass" type="button" value="${FORMAT_CN}">`,
  51. }
  52.  
  53. // 百度翻译CONFIG配置文件
  54. const BAIDU_FANYI_CONFIG = {
  55. host: 'fanyi.baidu.com',
  56. inputAreaSelector: '#baidu_translate_input',
  57. containerSelector: '#main-outer div.trans-operation.clearfix',
  58. translateButtonSelector: '#translate-button',
  59. buttonClass: 'myCustomBaiduButtonClass',
  60. createButtonHtml: `<a href="javascript:" class="myCustomBaiduButtonClass">${FORMAT_CN}</a>`,
  61. }
  62.  
  63. // 有道翻译CONFIG配置文件
  64. const YOUDAO_FANYI_CONFIG = {
  65. host: 'fanyi.youdao.com',
  66. inputAreaSelector: '#js_fanyi_input',
  67. containerSelector: '#app > div.index.os_Mac > div.translate-tab-container > div.tab-header > div.tab-left',
  68. translateButtonSelector: '#TextTranslate > div.fixedBottomActionBar-border-box > div > div.sourceActionContainer > div > div > div.opt-right.yd-form-container > a',
  69. buttonClass: null,
  70. createButtonHtml: `<button class="tab-item color_text_3"><span class="color_text_1">${FORMAT_CN}</span></button>`,
  71. }
  72.  
  73. // 腾讯翻译CONFIG配置文件
  74. const TENCENT_FANYI_CONFIG = {
  75. host: 'fanyi.qq.com',
  76. inputAreaSelector: 'body > div.layout-container > div.textpanel > div.textpanel-container.clearfix > div.textpanel-source.active > div.textpanel-source-textarea > textarea',
  77. containerSelector: '#language-button-group-translate',
  78. translateButtonSelector: '#language-button-group-translate > div',
  79. buttonClass: null,
  80. createButtonHtml: `<div class="language-translate-button">${FORMAT_CN}</div>`
  81. }
  82.  
  83. // CNKI翻译CONFIG配置文件
  84. const CNKI_FANYI_CONFIG = {
  85. host: 'dict.cnki.net',
  86. inputTextSelector: '#app > div > section > div > div:nth-child(2) > div.translate > div.trans-left > div.trans-left-con > p > span > pre',
  87. inputAreaSelector: '#translateLeft',
  88. containerSelector: '#app > div > section > div > div.hanlder > div.hanlder-left > div.hanlder-left-right',
  89. translateButtonSelector: '#app > div > section > div > div.hanlder > div.hanlder-left > div.hanlder-left-right > button',
  90. buttonClass: null,
  91. createButtonHtml: `<button type="button" class="el-button el-button--primary el-button--mini">${FORMAT_CN}</button>`
  92. }
  93.  
  94. // button class styles
  95. GM_addStyle(`
  96. .myCustomGoogleButtonClass {
  97. color: #1967d2;
  98. background: transparent;
  99. border-width: 1px;
  100. border-radius: 4px;
  101. border-style: solid;
  102. padding: 0 15px 0 15px;
  103. height: 36px;
  104. font-size: .875rem;
  105. border-color: #dadce0;
  106. cursor:pointer
  107. }
  108. .myCustomGoogleButtonClass:hover {
  109. background-color: #f1f5f9;
  110. color: #174ea6
  111. }
  112. `)
  113. GM_addStyle(`
  114. .myCustomBaiduButtonClass {
  115. text-align: center;
  116. margin-left: 14px;
  117. width: 106px;
  118. height: 30px;
  119. line-height: 30px;
  120. font-size: 14px;
  121. color: #4395ff;
  122. letter-spacing: 2px;
  123. background-color: #f9f9f9;
  124. border: 1px solid #4395ff;
  125. border-radius: 3px
  126. }
  127. `)
  128. GM_addStyle(`
  129. .myCustomDeepLButtonClass {
  130. background-color: #fff;
  131. border: 1px solid #e3e3e3;
  132. border-radius: 8px;
  133. cursor: pointer;
  134. height: 66px;
  135. min-width: 160px;
  136. box-shadow: 0px 4px 16px rgb(0 0 0 / 8%);
  137. font-weight: 400;
  138. font-size: 20px;
  139. }
  140. `)
  141.  
  142. // convert string to web element
  143. function parseDom(html) {
  144. console.log('【信息】parseDom before, html:', html)
  145. const e = document.createElement('div')
  146. if (window.trustedTypes && window.trustedTypes.createPolicy) {
  147. // 创建一个策略
  148. const policy = window.trustedTypes.createPolicy('default', {
  149. createHTML: (string) => {
  150. // 在这里执行必要的清理和验证
  151. // 假设string已经被安全地处理了
  152. return string;
  153. }
  154. });
  155.  
  156. // 使用策略创建TrustedHTML
  157. const trustedHTML = policy.createHTML(html);
  158.  
  159. // 使用TrustedHTML
  160. e.innerHTML = trustedHTML;
  161. } else {
  162. e.innerHTML = html
  163. }
  164. console.log('【信息】parseDom after')
  165. return e.firstChild
  166. }
  167.  
  168. // get text context of node
  169. function getTextContent(node) {
  170. console.log("【信息】开始获取处理前内容:", node)
  171. if (node.nodeType === Node.TEXT_NODE) {
  172. return node.textContent;
  173. }
  174. let text = ' ';
  175. for (let i = 0, len = node.childNodes.length; i < len; i++) {
  176. text += getTextContent(node.childNodes[i]);
  177. }
  178. return text;
  179. }
  180.  
  181. // simulate textArea input
  182. function simulateInput(inputBox, value) {
  183. const properties = {
  184. value: value,
  185. writable: true,
  186. configurable: true
  187. };
  188.  
  189. // 为input输入元素添加一个值属性
  190. Object.defineProperty(inputBox, 'value', properties);
  191.  
  192. // 创建并分配一个新的input事件
  193. let inputEvent = new Event("input", { bubbles: true });
  194. inputBox.dispatchEvent(inputEvent);
  195. }
  196.  
  197. // format code
  198. function formatByConfig(config) {
  199. const inputArea = document.querySelector(config.inputAreaSelector)
  200.  
  201. if (config.host == DEEPL_TRANSLATE_CONFIG.host) {
  202. let content = format(getTextContent(inputArea)).trim();
  203. inputArea.innerHTML = content;
  204. return
  205. }
  206.  
  207. let txt = inputArea.value != null ? inputArea.value : inputArea.innerHTML
  208. if (inputArea.value != null) {
  209. inputArea.value = format(txt)
  210. } else {
  211. inputArea.innerHTML = format(txt)
  212. }
  213.  
  214. // click translate button
  215. if (config.translateButtonSelector != null) {
  216. const translateButton = document.querySelector(config.translateButtonSelector)
  217. translateButton.click()
  218. }
  219. }
  220.  
  221. function format (txt) {
  222. for (var i = 0; i < txt.length; i++) {
  223. if (txt.indexOf('\n')) txt = txt.replace('\n', ' ')
  224. }
  225. // merge space in txt
  226. return txt.replace(/\s+/g, ' ');
  227. }
  228.  
  229. // create new button by config
  230. function createButtonByConfig(config) {
  231. const newButton = parseDom(config.createButtonHtml)
  232. if (newButton == null) {
  233. console.info('【错误】创建的新按钮为空')
  234. return
  235. }
  236. newButton.onclick = () => {
  237. if (config.host == CNKI_FANYI_CONFIG.host) {
  238. let inputText = document.querySelector(config.inputTextSelector);
  239. let txt = inputText.innerHTML;
  240. txt = format(txt);
  241. simulateInput(document.querySelector(config.inputAreaSelector), txt);
  242. }
  243. formatByConfig(config)
  244. }
  245. const container = document.querySelector(config.containerSelector)
  246. if (container != null) {
  247. container.appendChild(newButton)
  248. } else {
  249. console.info('【错误】无法找到container,请检查站点 ' + config.host + ' 的CSS规则containerSelector是否有效:' + config.containerSelector)
  250. }
  251. }
  252.  
  253. function findConfigByHost(host) {
  254. console.info('【信息】当前网页host:', host)
  255. if (host == GOOGLE_FANYI_CONFIG.host) {
  256. } else if (host == GOOGLE_TRANSLATE_CONFIG.host) {
  257. return GOOGLE_TRANSLATE_CONFIG
  258. } else if (host == BAIDU_FANYI_CONFIG.host) {
  259. return BAIDU_FANYI_CONFIG
  260. } else if (host == YOUDAO_FANYI_CONFIG.host) {
  261. return YOUDAO_FANYI_CONFIG
  262. } else if (host == DEEPL_TRANSLATE_CONFIG.host) {
  263. return DEEPL_TRANSLATE_CONFIG
  264. } else if (host == TENCENT_FANYI_CONFIG.host) {
  265. return TENCENT_FANYI_CONFIG
  266. } else if (host == CNKI_FANYI_CONFIG.host) {
  267. return CNKI_FANYI_CONFIG
  268. }
  269. }
  270.  
  271. ;(function () {
  272. //'use strict';
  273. console.log('【信息】%s毫秒后加载格式化按钮', LOADING_WAIT_TIME)
  274. window.setTimeout(function () {
  275. const config = findConfigByHost(window.location.host)
  276. createButtonByConfig(config)
  277. console.log('【信息】加载格式化按钮完成')
  278. }, LOADING_WAIT_TIME)
  279. })()