超级翻译助手

用鼠标选中文字,按下快捷键(默认为F9),可自动翻译文字。已支持超过 14 种语言。

  1. // ==UserScript==
  2. // @name 超级翻译助手
  3. // @namespace https://github.com/syhyz1990/translate
  4. // @version 1.0.7
  5. // @author YouXiaoHou
  6. // @description 用鼠标选中文字,按下快捷键(默认为F9),可自动翻译文字。已支持超过 14 种语言。
  7. // @license MIT
  8. // @homepage https://www.youxiaohou.com/tool/install-translate.html
  9. // @supportURL https://github.com/syhyz1990/translate
  10. // @match *://*/*
  11. // @require https://unpkg.com/jquery@3.7.0/dist/jquery.min.js
  12. // @require https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.js
  13. // @require https://unpkg.com/hotkeys-js@3.13.3/dist/hotkeys.min.js
  14. // @resource swalStyle https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.css
  15. // @connect translate.youxiaohou.com
  16. // @run-at document-idle
  17. // @noframes
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @grant GM_setClipboard
  21. // @grant GM_xmlhttpRequest
  22. // @grant GM_registerMenuCommand
  23. // @grant GM_getResourceText
  24. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPjxwYXRoIGQ9Ik02NTguMyA0MDIuM2gyOTIuNmM0MC40IDAgNzMuMSAzMi41IDczLjEgNzMuMXY0NzUuNGMwIDQwLjQtMzIuNSA3My4xLTczLjEgNzMuMUg0NzUuNGMtNDAuNCAwLTczLjEtMzIuNS03My4xLTczLjFWNjU4LjNoMTQ2LjNjNjAuOSAwIDEwOS43LTQ5LjEgMTA5LjctMTA5LjdWNDAyLjN6TTAgNzMuMUMwIDMyLjcgMzIuNSAwIDczLjEgMGg0NzUuNGM0MC40IDAgNzMuMSAzMi41IDczLjEgNzMuMXY0NzUuNGMwIDQwLjQtMzIuNSA3My4xLTczLjEgNzMuMUg3My4xYy00MC40LjEtNzMuMS0zMi40LTczLjEtNzNWNzMuMXptMTQ2LjMgMzE1LjhoNTMuNHYtMjguM2g3N3YxMzUuMmg1Ni42VjM2MC42aDc4LjZ2MjMuNkg0NzBWMjA1SDMzMy4zdi0zOS4zYzAtMTEuNSAxLjYtMjEuNSA0LjgtMjkuOC44LTEuNSAxLjQtMy4xIDEuNS00LjggMC0xLTMuNy0yLTExLTMuMWgtNTMuNXY3N0gxNDYuM3YxODMuOXpNMTk5LjcgMjQ5aDc3djY5LjFoLTc3VjI0OXptMjEyLjIgNjkuMWgtNzguNlYyNDloNzguNnY2OS4xem0yMzIuOSA1NTcuN2wyMi02MS4zaDExNC43bDIyIDYxLjNoNjIuOGwtMTAyLTI5MC43aC03My45TDU4NS4xIDg3NS45bDU5LjctLjF6bTM3LjgtMTEwTDcyNSA2NDEuN2gxLjZsMzkuMyAxMjQuMWgtODMuM3ptMjY4LjMtNDczLjJoLTczLjFjMC04MC44LTY1LjUtMTQ2LjMtMTQ2LjMtMTQ2LjNWNzMuMWMxMjEuMSAwIDIxOS40IDk4LjMgMjE5LjQgMjE5LjV6TTczLjEgNzMxLjRoNzMuMWMwIDgwLjggNjUuNSAxNDYuMyAxNDYuMyAxNDYuM3Y3My4xYy0xMjEuMS4xLTIxOS40LTk4LjItMjE5LjQtMjE5LjR6IiBmaWxsPSIjZDgxZDQ1Ii8+PC9zdmc+
  25. // ==/UserScript==
  26.  
  27. (function () {
  28. 'use strict';
  29.  
  30. const customClass = {
  31. container: 'translate-container',
  32. popup: 'translate-popup',
  33. content: 'translate-content',
  34. };
  35.  
  36. const toastClass = {
  37. container: 'translate-d-container',
  38. popup: 'translate-d-popup',
  39. };
  40.  
  41. let toast = Swal.mixin({
  42. toast: true,
  43. position: 'top',
  44. showConfirmButton: false,
  45. timerProgressBar: false,
  46. customClass: toastClass,
  47. didOpen: (toast) => {
  48. toast.addEventListener('mouseenter', Swal.stopTimer);
  49. toast.addEventListener('mouseleave', Swal.resumeTimer);
  50. }
  51. });
  52.  
  53. let languageMap = {
  54. 'auto': '自动检测',
  55. 'ar': '阿拉伯语',
  56. 'de': '德语',
  57. 'ru': '俄语',
  58. 'fr': '法语',
  59. 'ko': '韩语',
  60. 'la': '拉丁语',
  61. 'pt': '葡萄牙语',
  62. 'ja': '日语',
  63. 'th': '泰语',
  64. 'es': '西班牙语',
  65. 'it': '意大利语',
  66. 'en': '英语',
  67. 'zh-CN': '简体中文',
  68. 'zh-TW': '繁体中文',
  69. };
  70.  
  71. let util = {
  72.  
  73. getValue(name) {
  74. return GM_getValue(name);
  75. },
  76.  
  77. setValue(name, value) {
  78. GM_setValue(name, value);
  79. },
  80.  
  81. sleep(time) {
  82. return new Promise((resolve) => setTimeout(resolve, time));
  83. },
  84.  
  85. addStyle(id, tag, css) {
  86. tag = tag || 'style';
  87. let doc = document, styleDom = doc.getElementById(id);
  88. if (styleDom) return;
  89. let style = doc.createElement(tag);
  90. style.rel = 'stylesheet';
  91. style.id = id;
  92. tag === 'style' ? style.innerHTML = css : style.href = css;
  93. document.head.appendChild(style);
  94. },
  95.  
  96. isType(obj) {
  97. return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();
  98. },
  99.  
  100. post(url, data, headers, type) {
  101. if (this.isType(data) === 'object') {
  102. data = JSON.stringify(data);
  103. }
  104. return new Promise((resolve, reject) => {
  105. GM_xmlhttpRequest({
  106. method: "POST", url, headers, data,
  107. responseType: type || 'json',
  108. onload: (res) => {
  109. type === 'blob' ? resolve(res) : resolve(res.response || res.responseText);
  110. },
  111. onerror: (err) => {
  112. reject(err);
  113. },
  114. });
  115. });
  116. },
  117.  
  118. parseLanguage(language) {
  119. return languageMap[language] || language;
  120. },
  121. };
  122.  
  123. let main = {
  124. untranslatedText: '',
  125. translatedText: '',
  126. translating: false,
  127. speechSynthesis: null,
  128. speakPlaySvg: '<svg height="20" width="20" viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 7.97v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>',
  129.  
  130. //初始化配置数据
  131. initValue() {
  132. let value = [{
  133. name: 'setting_success_times',
  134. value: 0
  135. }, {
  136. name: 'hotkey',
  137. value: 'f9'
  138. }, {
  139. name: 'from',
  140. value: 'auto'
  141. }, {
  142. name: 'to',
  143. value: 'zh-CN'
  144. }];
  145.  
  146. value.forEach((v) => {
  147. if (util.getValue(v.name) === undefined) {
  148. util.setValue(v.name, v.value);
  149. }
  150. });
  151. },
  152.  
  153. async startTranslate() {
  154. this.translatedText = '';
  155. let select = window.getSelection().toString();
  156. if (select) this.untranslatedText = select;
  157. if (this.untranslatedText) this.translatedText = await this.translate(this.untranslatedText);
  158. await this.showPopup(this.untranslatedText, this.translatedText);
  159. },
  160.  
  161. async translate(text, showToast = true) {
  162. let btn = $('.translate-box .translate-btn');
  163. try {
  164. if (!text) return '';
  165. if (this.translating) return;
  166. this.translating = true;
  167. showToast && toast.fire({title: '正在翻译...', icon: 'info'});
  168. btn.length > 0 && btn.html('<span class="translate-loading-css"></span>翻译中');
  169. let res = await util.post('https://translate.youxiaohou.com', {
  170. str: text,
  171. from: util.getValue('from'),
  172. to: util.getValue('to')
  173. }, {
  174. 'Content-Type': 'application/json'
  175. });
  176. this.translating = false;
  177. btn.length > 0 && btn.html('翻译');
  178. if (res.code === 200) {
  179. util.setValue('setting_success_times', util.getValue('setting_success_times') + 1);
  180. return res.data;
  181. }
  182. return res?.msg || '';
  183. } catch (e) {
  184. this.translating = false;
  185. btn.length > 0 && btn.html('翻译失败');
  186. return '';
  187. }
  188. },
  189.  
  190. async showPopup(untranslatedText, translatedText) {
  191. let commonLangFrom = ['auto', 'zh-CN', 'en'];
  192. let commonLangTo = ['zh-CN', 'en', 'zh-TW'];
  193. let langFrom = util.getValue('from');
  194. let langTo = util.getValue('to');
  195. if (!commonLangFrom.includes(langFrom)) commonLangFrom.push(langFrom);
  196. if (!commonLangTo.includes(langTo)) commonLangTo.push(langTo);
  197. let langDomFrom = commonLangFrom.map(val => {
  198. if (val === langFrom) {
  199. return `<div class="item on" data-lang="${val}" data-type="from">${languageMap[val]}</div>`;
  200. } else {
  201. return `<div class="item" data-lang="${val}" data-type="from">${languageMap[val]}</div>`;
  202. }
  203. }).join('');
  204. let langDomTo = commonLangTo.map(val => {
  205. if (val === langTo) {
  206. return `<div class="item on" data-lang="${val}" data-type="to">${languageMap[val]}</div>`;
  207. } else {
  208. return `<div class="item" data-lang="${val}" data-type="to">${languageMap[val]}</div>`;
  209. }
  210. }).join('');
  211. let html = `
  212. <div class="translate-wrapper">
  213. <div class="translate-box">
  214. <div class="from-side">
  215. <div class="translate-title">
  216. <div class="translate-lang">${langDomFrom}<svg id="dropdown-from" width="24" height="24" viewBox="0 0 24 24" class="translate-dropdown"><path d="M5.41 7.59L4 9l8 8 8-8-1.41-1.41L12 14.17"/></svg></div>
  217. <button class="translate-btn" title="点击翻译左侧文本">翻译</button>
  218. </div>
  219. <textarea class="untranslated-text" placeholder="请输入要翻译的内容">${untranslatedText}</textarea>
  220. <svg class="translate-clear" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M806.4 263.2l-45.6-45.6L512 467.2 263.2 217.6l-45.6 45.6L467.2 512 217.6 760.8l45.6 45.6L512 557.6l248.8 248.8 45.6-45.6L557.6 512z"/></svg>
  221. <div class="translate-toolbar">
  222. <div id="speak-from" class="translate-speak">${main.speakPlaySvg}</div>
  223. <div id="word-count">字数:${untranslatedText.length}</div>
  224. </div>
  225. </div>
  226. <div class="to-side">
  227. <div class="translate-title">
  228. <div class="translate-lang">${langDomTo}<svg id="dropdown-to" width="24" height="24" viewBox="0 0 24 24" class="translate-dropdown"><path d="M5.41 7.59L4 9l8 8 8-8-1.41-1.41L12 14.17"/></svg></div>
  229. </div>
  230. <div class="translated-text">${translatedText}</div>
  231. <div class="translate-toolbar">
  232. <div id="speak-to" class="translate-speak">${main.speakPlaySvg}</div>
  233. <div class="translate-copy-box" title="点击复制"><svg class="translate-copy" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18">
  234. <path d="M672 832H224c-52.928 0-96-43.072-96-96V160c0-52.928 43.072-96 96-96h448c52.928 0 96 43.072 96 96v576c0 52.928-43.072 96-96 96zM224 128c-17.632 0-32 14.368-32 32v576c0 17.664 14.368 32 32 32h448c17.664 0 32-14.336 32-32V160c0-17.632-14.336-32-32-32H224z"/>
  235. <path
  236. d="M800 960H320c-17.664 0-32-14.304-32-32s14.336-32 32-32h480c17.664 0 32-14.336 32-32V256c0-17.664 14.304-32 32-32s32 14.336 32 32v608c0 52.928-43.072 96-96 96zM544 320H288c-17.664 0-32-14.336-32-32s14.336-32 32-32h256c17.696 0 32 14.336 32 32s-14.304 32-32 32zm64 160H288.032c-17.664 0-32-14.336-32-32s14.336-32 32-32H608c17.696 0 32 14.336 32 32s-14.304 32-32 32z"/>
  237. <path d="M608 640H288c-17.664 0-32-14.304-32-32s14.336-32 32-32h320c17.696 0 32 14.304 32 32s-14.304 32-32 32z"/>
  238. </svg>
  239. <span class="translate-copy-tip"></span></div>
  240. </div>
  241. </div>
  242. </div>
  243. <div class="translate-footer"><a href="https://www.youxiaohou.com/tool/install-translate.html" target="_blank">油小猴翻译助手</a> 为您提供翻译服务</div>
  244. </div>
  245. `;
  246.  
  247. Swal.fire({
  248. width: '1200px',
  249. position: 'center',
  250. allowOutsideClick: false,
  251. showCloseButton: true,
  252. showConfirmButton: false,
  253. html: html,
  254. customClass
  255. });
  256.  
  257. //自动聚焦
  258. let textarea = document.querySelector('.translate-box .untranslated-text');
  259. if (textarea) {
  260. textarea.focus();
  261. textarea.setSelectionRange(textarea.value.length, textarea.value.length);
  262. }
  263. },
  264.  
  265. async startNewTranslate() {
  266. if (this.translating) return;
  267. $('.translate-box .translated-text').text(await this.translate($('.translate-box .untranslated-text').val(), false));
  268. },
  269.  
  270. addPageListener() {
  271. let body = $('body');
  272.  
  273. body.on('click', '.translate-box .translate-btn', async () => {
  274. await this.startNewTranslate();
  275. })
  276.  
  277. body.on('input', '.translate-box .untranslated-text', async (e) => {
  278. this.untranslatedText = e.target.value;
  279. $('.translate-box #word-count').text(`字数:${this.untranslatedText.length}`);
  280. })
  281.  
  282. body.on('change', '.translate-box .untranslated-text', () => {
  283. setTimeout(async () => {
  284. await this.startNewTranslate();
  285. }, 300);
  286. })
  287.  
  288. body.on('click', '.translate-box .translate-clear', async () => {
  289. this.untranslatedText = '';
  290. $('.translate-box .untranslated-text').val('');
  291. $('.translate-box .translated-text').text('');
  292. $('.translate-box #word-count').text('字数:0');
  293. })
  294.  
  295. body.on('click', '.translate-box #dropdown-from', async () => {
  296. this.setFromLanguage(true);
  297. })
  298.  
  299. body.on('click', '.translate-box #dropdown-to', async () => {
  300. this.setToLanguage(true);
  301. })
  302.  
  303. body.on('click', '.translate-box #speak-from', async () => {
  304. this.speakWord($('.translate-box .untranslated-text').val());
  305. })
  306.  
  307. body.on('click', '.translate-box #speak-to', async () => {
  308. this.speakWord($('.translate-box .translated-text').text());
  309. })
  310.  
  311. body.on('click', '.translate-box .translate-lang .item', async (e) => {
  312. let $current = $(e.target);
  313. let lang = $current.data('lang');
  314. let type = $current.data('type');
  315. $current.siblings().removeClass('on');
  316. $current.addClass('on');
  317. util.setValue(type, lang);
  318. await this.startNewTranslate();
  319. })
  320.  
  321. body.on('click', '.translate-box .translate-copy', async () => {
  322. GM_setClipboard($('.translate-box .translated-text').text());
  323. let copyTip = $('.translate-box .translate-copy-tip');
  324. copyTip.text('复制成功');
  325. setTimeout(() => copyTip.text(''), 3000);
  326. })
  327. },
  328.  
  329. addHotKey() {
  330. hotkeys(util.getValue('hotkey'), async (event, handler) => {
  331. event.preventDefault();
  332. await this.startTranslate();
  333. });
  334. },
  335.  
  336. speakWord(text) {
  337. if ('speechSynthesis' in window && text) {
  338. if (speechSynthesis.speaking) {
  339. speechSynthesis.cancel();
  340. } else {
  341. let msg = new SpeechSynthesisUtterance();
  342. msg.text = text;
  343. speechSynthesis.speak(msg);
  344. }
  345. }
  346. },
  347.  
  348. //重置翻译次数
  349. clearTranslateTimes() {
  350. Swal.fire({
  351. showCancelButton: true,
  352. title: '确定要重置翻译次数吗?',
  353. icon: 'warning',
  354. confirmButtonText: '确定',
  355. cancelButtonText: '取消',
  356. customClass: toastClass,
  357. }).then(res => {
  358. if (res.isConfirmed) {
  359. util.setValue('setting_success_times', 0);
  360. history.go(0);
  361. }
  362. });
  363. },
  364.  
  365. setHotkey() {
  366. Swal.fire({
  367. title: '请选择快捷键',
  368. text: '注意:避免与其他程序产生冲突',
  369. input: 'select',
  370. inputValue: util.getValue('hotkey').toUpperCase(),
  371. inputOptions: {
  372. 'F1': 'F1',
  373. 'F2': 'F2',
  374. 'F3': 'F3',
  375. 'F4': 'F4',
  376. 'F5': 'F5',
  377. 'F6': 'F6',
  378. 'F7': 'F7',
  379. 'F8': 'F8',
  380. 'F9': 'F9(默认)',
  381. 'F10': 'F10',
  382. 'F11': 'F11',
  383. 'F12': 'F12',
  384. },
  385. confirmButtonText: '确定',
  386. customClass: toastClass,
  387. }).then(async (result) => {
  388. if (result.isConfirmed) {
  389. util.setValue('hotkey', result.value.toLowerCase());
  390. history.go(0);
  391. }
  392. });
  393. },
  394.  
  395. setFromLanguage(popAgain = false) {
  396. Swal.fire({
  397. title: '请选择原始语言',
  398. text: '选择您要翻译的原始语言',
  399. input: 'select',
  400. inputValue: util.getValue('from'),
  401. inputOptions: languageMap,
  402. confirmButtonText: '确定',
  403. customClass: toastClass,
  404. }).then(async (result) => {
  405. if (result.isConfirmed) {
  406. util.setValue('from', result.value);
  407. popAgain && this.startTranslate();
  408. }
  409. });
  410. },
  411.  
  412. setToLanguage(popAgain = false) {
  413. let map = JSON.parse(JSON.stringify(languageMap));
  414. delete map.auto;
  415. Swal.fire({
  416. title: '请选择目标语言',
  417. text: '选择您要翻译的目标语言',
  418. input: 'select',
  419. inputValue: util.getValue('to'),
  420. inputOptions: map,
  421. confirmButtonText: '确定',
  422. customClass: toastClass,
  423. }).then(async (result) => {
  424. if (result.isConfirmed) {
  425. util.setValue('to', result.value);
  426. popAgain && this.startTranslate();
  427. }
  428. });
  429. },
  430.  
  431. registerMenuCommand() {
  432. GM_registerMenuCommand(`⚙️ 设置快捷键:${util.getValue('hotkey').toUpperCase()}`, () => {
  433. this.setHotkey();
  434. });
  435. GM_registerMenuCommand(`⚙️ 设置原始语言:${util.parseLanguage(util.getValue('from'))}`, () => {
  436. this.setFromLanguage();
  437. });
  438. GM_registerMenuCommand(`⚙️ 设置目标语言:${util.parseLanguage(util.getValue('to'))}`, () => {
  439. this.setToLanguage();
  440. });
  441. GM_registerMenuCommand('👀 已翻译:' + util.getValue('setting_success_times') + '次', () => {
  442. this.clearTranslateTimes();
  443. });
  444. },
  445.  
  446.  
  447. addPluginStyle() {
  448. let style = `
  449. .translate-container { z-index: 99999!important; }
  450. .translate-popup { font-size: 14px !important;padding:0 !important; border-radius: 10px;}
  451. .translate-d-container { z-index: 999999!important;}
  452. .translate-d-popup { font-size: 14px !important;}
  453. .translate-content { padding:0 !important; }
  454. .translate-setting-label { display: flex;align-items: center;justify-content: space-between;padding-top: 20px; }
  455. .translate-setting-checkbox { width: 16px;height: 16px; }
  456. .translate-wrapper { margin: 28px 24px 10px; font-family: Roboto,RobotoDraft,Helvetica,Arial,sans-serif;}
  457. .translate-box { display: flex; min-height: calc(100vh - 130px);box-shadow: 1px 1px 9px #eaeaea; border-radius: 15px;}
  458. .translate-box .from-side { flex:1; width:50%; border: 1px solid #ddd; border-radius: 15px 0 0 15px; border-right:none;color: #666;position: relative;padding: 0 0 36px;}
  459. .translate-box .to-side { flex:1; width:50%; border:1px solid #ddd; border-radius: 0 15px 15px 0; border-left: none;background: #f5f5f5;color: #000;position: relative;padding: 0 0 36px;}
  460. .translate-box .untranslated-text { width: 100%; height: calc(100% - 50px); border-radius: 15px 0 0 15px; resize: none; line-height: 28px; font-size: 16px; color: #666; border: none; text-align: left; padding: 20px; box-sizing: border-box; outline:none; overflow-wrap: break-word; word-break: break-word; word-wrap: break-word;}
  461. .translate-box .untranslated-text::-webkit-scrollbar { width: 6px; height: 6px;}
  462. .translate-box .untranslated-text::-webkit-scrollbar-thumb { background-color: rgba(85,85,85,.4)}
  463. .translate-box .untranslated-text::-webkit-scrollbar-thumb, .translate-box .untranslated-text::-webkit-scrollbar-thumb:hover { border-radius: 5px; box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);}
  464. .translate-box .translate-toolbar { position: absolute; bottom: 0; font-size: 13px; color: #999; height: 36px; text-align: right; left: 20px; right: 20px; display: flex; align-items: center; justify-content: space-between;}
  465. .translate-box .translated-text { width: 100%; line-height: 28px; font-size: 16px; border: none; text-align: left; white-space: pre-wrap; padding: 20px; box-sizing: border-box; overflow-wrap: break-word; word-break: break-word; word-wrap: break-word; position: relative;}
  466. .translate-box .translate-title { border-bottom: 1px solid #ddd; padding: 0 20px 0 10px; position: sticky; top: -10px;display: flex;align-items:center;justify-content: space-between; z-index: 99999; background: #fff;user-select:none}
  467. .translate-box .translate-lang { display: flex; align-items:center;}
  468. .translate-box .translate-lang .item { position:relative; color:#666;padding: 0 15px; cursor:pointer;font-size:15px;font-weight: 500;height: 48px;display: flex;align-items:center;}
  469. .translate-box .translate-lang .item.on { color:#1a73e8;font-weight: 700}
  470. .translate-box .translate-lang .item.on:after { content: '';display:block;position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 2px; background: #1a73e8;}
  471. .translate-box .translate-lang .item:hover { background: #f6fafe; color:#333}
  472. .translate-box .translate-dropdown { cursor:pointer; fill: #999;margin: 0 10px 0 20px; padding:5px;border-radius:50%}
  473. .translate-box .translate-dropdown:hover { fill: #666; background:#f5f5f5 }
  474. .translate-box .translate-box .from-side .translate-title { border-radius: 15px 0 0 0;}
  475. .translate-box .translate-box .to-side .translate-title { border-radius: 0 15px 0 0;}
  476. .translate-box .translate-btn { border: 0; border-radius: 5px; color: #fff; font-size: 14px; padding: 8px 13px; background: #4396fc; cursor: pointer; line-height: 1; display: flex; align-items: center;}
  477. .translate-box .translate-btn:hover { background: #187efa;}
  478. .translate-box .translate-loading-css { width: 12px; height: 12px; border: 2px solid #FFF; margin-right:6px; border-bottom-color: transparent; border-radius: 50%; display: inline-block; box-sizing: border-box; animation: __rotation 1s linear infinite; }
  479. @keyframes __rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); }}
  480. .translate-box .translate-copy-box {display: flex;align-items:center;gap:5px;}
  481. .translate-box .translate-copy,.translate-speak svg {cursor: pointer;opacity: 0.2;transition: opacity .3s;display: flex;align-items:center;}
  482. .translate-box .translate-copy:hover,.translate-speak svg:hover {opacity: 0.5;transition: opacity .3s}
  483. .translate-box .translate-clear {cursor: pointer; fill: #666; padding:6px; border-radius:50%;position: absolute; top: 66px; right: 13px;background: rgb(255 255 255 / 50%)}
  484. .translate-box .translate-clear:hover {fill: #333; background:#f5f5f5; }
  485. .translate-footer {margin-top: 8px; font-size: 14px; color: #999}
  486. .translate-footer a {color: rgb(26 115 232 / 70%)}
  487. .translate-footer a:hover {color: rgb(26 115 232 / 90%)}
  488. .swal2-close { font-size: 30px!important}
  489. .swal2-close:focus {box-shadow:none!important}
  490. `;
  491.  
  492. if (document.head) {
  493. util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
  494. util.addStyle('translate-style', 'style', style);
  495. }
  496.  
  497. const headObserver = new MutationObserver(() => {
  498. util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
  499. util.addStyle('translate-style', 'style', style);
  500. });
  501. headObserver.observe(document.head, {childList: true, subtree: true});
  502. },
  503.  
  504. isTopWindow() {
  505. return window.self === window.top;
  506. },
  507.  
  508. init() {
  509. this.initValue();
  510. this.addPluginStyle();
  511. this.addHotKey();
  512. this.addPageListener();
  513. this.isTopWindow() && this.registerMenuCommand();
  514. },
  515. };
  516.  
  517. main.init();
  518. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址