Translate webpage

Mimicks chrome's built-in page translator. Can be used as a userscript (from the context menu or always on) or as a bookmarklet (clicking the bookmark translates the page). For Firefox and chrome

  1. // ==UserScript==
  2. // @name Translate webpage
  3. // @namespace https://github.com/Procyon-b
  4. // @version 0.5.1
  5. // @description Mimicks chrome's built-in page translator. Can be used as a userscript (from the context menu or always on) or as a bookmarklet (clicking the bookmark translates the page). For Firefox and chrome
  6. // @author Achernar
  7. // @match *://*/*
  8. // @run-at context-menu
  9. // @run-at document-idle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_deleteValue
  13. // ==/UserScript==
  14.  
  15. javascript:
  16. (function(){
  17. "use strict";
  18.  
  19. var defLang="",toolbarLang="";
  20.  
  21. if (document.querySelector('body > .skiptranslate > iframe')) return;
  22. try {
  23. if (window.frameElement.classList.contains('skiptranslate')) return;
  24. }catch(e){}
  25.  
  26. var nLang=navigator.language.split('-')[0];
  27.  
  28. if (defLang == '?') defLang=nLang;
  29. if (toolbarLang == '?') toolbarLang=nLang;
  30.  
  31. function getCookies() {
  32. var r={}, a=document.cookie;
  33. a.split(';').forEach(function(e){
  34. var p=e.split('=');
  35. if (p[0]) r[p.shift().trim()]=p.join('=');
  36. });
  37. return r;
  38. };
  39.  
  40. function clearCookie(n) {
  41. var h=location.host, s=h.split('.'), a=[' ', h, '.'+h], d, old=(new Date(0)).toUTCString();
  42. while (s.length > 2) {
  43. if (s[0]) s[0]='';
  44. else s.shift();
  45. a.push(s.join('.'));
  46. }
  47. while (d=a.shift()) {
  48. document.cookie= n+'=;path=/;expires='+old+';'+(d==' ' ? '':'domain='+d+';');
  49. if (!getCookies()[n]) break;
  50. }
  51. }
  52.  
  53. var translate_to=defLang || '',
  54. translate_toS='',
  55. done=false,
  56. force=true,
  57. btnAdded=true,
  58. curVBtn,
  59. setDefBtn,
  60. closeBtn,
  61. GM=false;
  62.  
  63. try{
  64. [translate_to, toolbarLang]=getDef();
  65. btnAdded=false;
  66. GM=true;
  67. }catch(e){}
  68.  
  69. var curLang='', curLangS='';
  70. function getDef() {
  71. return [GM_getValue('translate_to',''), GM_getValue('toolbarLang','')];
  72. }
  73. function setDef() {
  74. GM_setValue('translate_to', curLang);
  75. translate_to=curLang;
  76. translate_toS=curLangS;
  77. updCurBtn();
  78. }
  79.  
  80. function updCurBtn() {
  81. if (curVBtn) curVBtn.innerText=(translate_toS && translate_toS+' ('+translate_to+')') || translate_to;
  82. toggleBtn();
  83. }
  84.  
  85. function toggleSite(force) {
  86. var h='no:'+location.host;
  87. try{
  88. aBlocked=GM_getValue('no-all', false);
  89. blocked= force !== undefined ? !force : GM_getValue(h, false);
  90. if (force === null) return;
  91. if (blocked) {
  92. if (aBlocked) GM_setValue(h, false);
  93. else GM_deleteValue(h);
  94. }
  95. else GM_setValue(h, true);
  96. }catch(e){}
  97. }
  98.  
  99. var trans=true, blocked=false, aBlocked=false, cookies=getCookies();
  100. if (cookies.googtransopt) trans=false;
  101.  
  102. function isBlocked() {
  103. let blocked=GM_getValue('no:'+location.host, null);
  104. aBlocked=GM_getValue('no-all', false);
  105.  
  106. if (aBlocked && (blocked === false) ) return false;
  107. return aBlocked || Boolean(blocked);
  108. }
  109.  
  110. try{
  111. blocked=isBlocked();
  112. trans=!blocked;
  113. if (trans && cookies.googtransopt) clearCookie('googtransopt');
  114. }catch(e){}
  115.  
  116. if (!GM && trans && translate_to && !getCookies().googtrans) document.cookie='googtrans=/auto/'+translate_to+'/; path=/';
  117.  
  118. var eael=Element.prototype.addEventListener;
  119. Element.prototype.addEventListener=function(ev){
  120. var e=this;
  121. if (ev == 'mouseover') {
  122. if (String(arguments[1]).startsWith('function(c){return a.call(')) return;
  123. }
  124. return eael.apply(e,arguments);
  125. };
  126.  
  127. var sa=HTMLElement.prototype.setAttribute;
  128. HTMLElement.prototype.setAttribute=function(a){
  129. var e=this;
  130. if (a == 'aria-label') {
  131. let err=(new Error()).stack;
  132. if (err.includes('https://translate.googleapis.com/_/translate_http/_/js/')) return;
  133. }
  134. return sa.apply(e,arguments);
  135. };
  136.  
  137. var pm=Performance.prototype.measure;
  138. Performance.prototype.measure=function() {
  139. var e=this;
  140. try{
  141. return pm.apply(e, arguments);
  142. }catch(e){}
  143. return false;
  144. };
  145.  
  146. new MutationObserver(function(muts) {
  147. for (let mut of muts) {
  148. if (mut.addedNodes.length) {
  149. for (let i=0,n; n=mut.addedNodes[i]; i++) {
  150. if (n.nodeName == 'IFRAME' && n.classList.contains('skiptranslate') ) {
  151. addSt(frameSt, n.contentWindow);
  152. n.onload=function(){
  153. let e=this;
  154. frameMut(e);
  155. }
  156. }
  157. else if (n.nodeName == 'DIV') {
  158. if (n.classList.contains('skiptranslate') ) {
  159. let tb=document.body.querySelector('body > .skiptranslate > iframe.skiptranslate');
  160. if (tb) {
  161. addSt(frameSt, tb.contentWindow);
  162. frameMut(tb);
  163. }
  164. }
  165. else if (n.parentNode && (n.attributes.length ==1) && n.className && /^[^-_]{6}-[^-_]{6}-[^-_]{6}-[^-_]{6}/i.test(n.className) ) {
  166. let s=getComputedStyle(n);
  167. if ( (s.zIndex == '1000') && (s.transitionDelay == '0.6s') ) addSt('.'+n.className.split(' ')[0]+' {display: none;}');
  168. }
  169. }
  170. }
  171. }
  172. }
  173. }).observe(document.body, { attributes: false, subtree: false, childList: true });
  174.  
  175. function frameMut(f) {
  176. try{
  177. f.dataset.done=true;
  178. new MutationObserver(function(muts) {
  179. handleFrame(f);
  180. }).observe(f.contentWindow.document.body, { attributes: false, subtree: true, childList: true });
  181. handleFrame(f);
  182. }catch(e){}
  183. }
  184.  
  185. function clickIF(ev) {
  186. var v=this.value;
  187. if (v === undefined) return;
  188. if (v == 'turn_off_site') {
  189. toggleSite(true);
  190. toggleMenu(false);
  191. }
  192. if (v == 'turn_on_site') {
  193. clearCookie('googtransopt');
  194. menuFrame.style.display='none';
  195. toggleSite(false);
  196. toggleMenu(true);
  197. }
  198. else if (v == 'turn_off_all') {
  199. GM_setValue('no-all', true);
  200. menuFrame.style.display='none';
  201. toggleSite(null);
  202. toggleMenu(!isBlocked());
  203. closeBtn && closeBtn.click();
  204. }
  205. else if (v == 'turn_off_all_m') {
  206. GM_setValue('no-all', true);
  207. menuFrame.style.display='none';
  208. toggleSite(false);
  209. toggleMenu(!isBlocked());
  210. }
  211. else if (v == 'turn_on_all') {
  212. GM_deleteValue('no-all');
  213. menuFrame.style.display='none';
  214. toggleSite(null);
  215. toggleMenu(!isBlocked());
  216. }
  217. else if (v == 'forceMnuLang') {
  218. let L=null;
  219.  
  220. if ( (ev.target.nodeName == 'SELECT') || (ev.target.nodeName == 'OPTION') ) L=ev.target.value;
  221. else if (!LSelect) L=toolbarLang || nLang;
  222.  
  223. if (!LSelect) {
  224. let r=this.querySelector('div:not(.select)');
  225. if (r) {
  226. r.classList.add('select');
  227. let s='';
  228. LSelect=document.createElement('select');
  229. for (let k of Object.keys(CCode).sort() ) {
  230. s+='<option value="'+k+'">'+k+'</option>';
  231. }
  232. LSelect.innerHTML=s;
  233. r.appendChild(LSelect);
  234. }
  235. }
  236.  
  237. if (L !== null) {
  238. toolbarLang=L;
  239. if (L) GM_setValue('toolbarLang', L);
  240. else GM_deleteValue('toolbarLang');
  241. if (LSelect) LSelect.value=toolbarLang;
  242. }
  243. }
  244. else {
  245. curLang=v;
  246. curLangS=this.innerText;
  247. updCurBtn();
  248. }
  249. }
  250.  
  251. function toggleMenu(t) {
  252. if (!menuFrame) return;
  253. if (t !== undefined) trans=t;
  254. menuFrame.contentWindow.document.body.classList.toggle('t_off', !trans);
  255. menuFrame.contentWindow.document.body.classList.toggle('t_all_off', aBlocked);
  256. }
  257.  
  258.  
  259. function toggleBtn() {
  260. if (curVBtn) {
  261. curVBtn.parentNode.classList.toggle('hidden', !translate_to);
  262. curVBtn.parentNode.classList.toggle('disabled', curLang == translate_to );
  263. }
  264. if (setDefBtn) {
  265. setDefBtn.parentNode.classList.toggle('disabled', curLang == translate_to );
  266. }
  267. }
  268.  
  269. function fixLinks(a, s) {
  270. if (s) void(0);
  271. var r;
  272. for (let i=0, e; e=a[i]; i++) {
  273. if (e.attributes.href.value == "#") {
  274. e.href='javascript:void(0)';
  275. if (!e._cIF_) e.addEventListener('click', clickIF);
  276. e._cIF_=true;
  277. if (s && !r && (e.value == s)) r=e;
  278. }
  279. }
  280. return r;
  281. }
  282.  
  283. var langFrame, menuFrame, CCode={}, LSelect;
  284.  
  285. function forceLang(L, A, set=true) {
  286. var a= A || (langFrame && langFrame.contentWindow.document.body.querySelectorAll('a')) || [];
  287. if (!L) {
  288. let i, e, c, cl={};
  289. for (i=0; e=a[i]; i++) {
  290. c=e.classList.value;
  291. if (!cl[c]) cl[c]={c: 0, e: null};
  292. cl[c].c++;
  293. cl[c].e=e;
  294. if (set) CCode[e.value]=e.innerText;
  295. if (translate_to && !translate_toS && (translate_to == e.value) ) {
  296. translate_toS=e.innerText;
  297. updCurBtn();
  298. }
  299. }
  300. for (c in cl) {
  301. if ( (cl[c].c == 1) && cl[c].e.value) {
  302. curLang=cl[c].e.value;
  303. curLangS=cl[c].e.innerText;
  304. updCurBtn();
  305. break;
  306. }
  307. }
  308. return;
  309. }
  310. for (let i=0, e; e=a[i]; i++) {
  311. if (e.value == L) {
  312. if (set) e.click();
  313. return true;
  314. }
  315. }
  316. }
  317.  
  318. function handleFrame(f) {
  319. if (!langFrame) langFrame=f;
  320. var a;
  321. try{
  322. a=f.contentWindow.document.body.querySelectorAll('a');
  323. }catch(e){
  324. return;}
  325. var mnu=fixLinks(a, menuFrame?'':'turn_off_site');
  326. if (!menuFrame && mnu) {
  327. function createMnu(mnu, cfg) {
  328. let e=mnu.cloneNode(true);
  329. e.addEventListener('click', clickIF);
  330. e._cIF_=true;
  331. e.classList.add(cfg.c);
  332. e.value=cfg.v;
  333. let t=e.querySelector('.text');
  334. if (t) {
  335. t.innerHTML=cfg.s;
  336. }
  337. if (cfg.p == 'last') mnu.parentNode.appendChild(e);
  338. else mnu.parentNode.insertBefore(e, cfg.ns);
  339. return e;
  340. }
  341. menuFrame=f;
  342. if (GM) {
  343. let ns=mnu.nextSibling;
  344. let e=createMnu(mnu,{v: 'turn_on_site', c: 't_on', s: locS('tr_on'), ns});
  345. createMnu(mnu, {ns, v: 'turn_off_all', c: 't_off_all', s: locS('tr_all_off') });
  346. createMnu(mnu, {ns, v: 'turn_off_all_m', c: 't_off_allm', s: locS('tr_all_off_s') });
  347. createMnu(mnu, {ns, v: 'turn_on_all', c: 't_on_all', s: locS('tr_all_on') });
  348. createMnu(mnu, {ns, v: 'forceMnuLang', c: 'forceMnu', s: '&iquest;?', p: 'last' });
  349. mnu.classList.add('t_off');
  350. toggleMenu(trans);
  351. }
  352. }
  353. if (!curLang && (langFrame == f)) forceLang(null, a);
  354.  
  355. if (!done) {
  356. fixLinks(document.body.querySelectorAll('body > #google_translate_element a'));
  357. }
  358.  
  359. var cookies=getCookies();
  360.  
  361. if (translate_to && cookies.googtrans && !done) force=false;
  362. if (translate_to && (!cookies.googtrans || !force) && !done) {
  363. if (trans && forceLang(translate_to, a, force)) {
  364. done=true;
  365. return;
  366. }
  367. }
  368.  
  369. function createBt(r, cfg, d=document) {
  370. var m=r.cloneNode(true),
  371. b=m.querySelector('button[id$="restore"]'),
  372. e=d.createElement('button');
  373. e.innerText=cfg.text || '';
  374. e.id=cfg.id;
  375. b.parentNode.classList.add(cfg.id);
  376. b.parentNode.classList.add('addedBtn');
  377. b.parentNode.replaceChild(e, b);
  378. r.parentNode.appendChild(m);
  379. return e;
  380. }
  381.  
  382. if (!btnAdded) {
  383. let btn=f.contentWindow.document.body.querySelector('button[id$="restore"]');
  384. if (btn) {
  385. let r=btn.closest('td');
  386. setDefBtn=createBt(r, {text: locS('set_def'), id: 'setDefLn'} );
  387. setDefBtn.onclick=setDef;
  388. curVBtn=createBt(r, {text: (translate_toS && translate_toS+' ('+translate_to+')') || translate_to, id: 'curDefLn'} );
  389. curVBtn.onclick=function(){forceLang(translate_to);};
  390. toggleBtn();
  391. btnAdded=true;
  392. closeBtn=f.contentWindow.document.body.querySelector('a[id$="close"]');
  393. }
  394. }
  395. }
  396.  
  397.  
  398. function addSt(s, r) {
  399. try {
  400. var st=document.createElement('style');
  401. var R= r || window;
  402. R.document.documentElement.appendChild(st);
  403. st.textContent=s;
  404. }catch(e){
  405. setTimeout(function(){addSt(s, r)},0); }
  406. };
  407.  
  408. addSt(`
  409. html[style*="height: 100%;"] {
  410. height: auto !important;
  411. }
  412. body[style*="position: relative; min-height: 100%; top: 40px;"],
  413. body[style*="position: relative; min-height: 100%; top: 0px;"] {
  414. min-height: initial !important;
  415. top: unset !important;
  416. position: initial !important;
  417. }
  418. body > .skiptranslate > iframe:not(:hover) {
  419. height: 4px;
  420. opacity: 0;
  421. }
  422. iframe.skiptranslate,
  423. body > #google_translate_element {
  424. zoom: ${1/window.getComputedStyle(document.body).zoom};
  425. }
  426. iframe.skiptranslate[title] {
  427. max-width: 95% !important;
  428. margin-right: 1em;
  429. }
  430. iframe.skiptranslate[title][style*="; left: 0px;"] {
  431. left: 1em !important;
  432. }
  433. body > .skiptranslate:not([id]):not([style*="display:"]) ~ #google_translate_element,
  434. body > #google_translate_element:empty {
  435. display: none;
  436. }
  437. body > #google_translate_element {
  438. position: fixed;
  439. top: 0px;
  440. right: 0px;
  441. z-index: 9999999;
  442. }
  443. body > #google_translate_element.T:hover {
  444. background: white;
  445. color: black;
  446. padding: 0.5em;
  447. border: 2px solid red;
  448. font: bold 14px arial;
  449. }
  450. body > #google_translate_element.T:not(:hover) {
  451. opacity: 0;
  452. max-width: 3px;
  453. max-height: 3px;
  454. }
  455.  
  456. no #google_translate_element img {
  457. display: none !important;
  458. }
  459. `);
  460.  
  461. var frameSt=`
  462. body[scroll="no"] {
  463. overflow-x: auto !important;
  464. }
  465. body[scroll="no"] > div {
  466. padding-bottom: 20px !important;
  467. }
  468. body[scroll="no"] .setDefLn {
  469. margin-left: 2em !important;
  470. }
  471. body[scroll="no"] .addedBtn {
  472. margin-left: 0.5em;
  473. }
  474. body[scroll="no"] .addedBtn.hidden,
  475. body[scroll="no"]:not(.t_off) .t_on,
  476. body[scroll="no"].t_off .t_off,
  477. body[scroll="no"]:not(.t_all_off) .t_on_all,
  478. body[scroll="no"].t_all_off .t_off_all,
  479. body[scroll="no"].t_all_off .t_off_allm {
  480. display: none !important;
  481. }
  482. body[scroll="no"] .addedBtn.disabled {
  483. opacity: 0.6;
  484. pointer-events: none;
  485. }
  486. body[scroll="no"] .forceMnu select {
  487. margin: 0 1em;
  488. }
  489. `;
  490.  
  491. function startGT() {
  492. var r=document.head || document.documentElement;
  493. var el=document.createElement('script');
  494. el.textContent=`
  495. function googleTranslateElementInit() {
  496. new google.translate.TranslateElement({
  497. pageLanguage: '',
  498. autoDisplay: false,
  499. layout: google.translate.TranslateElement.InlineLayout.SIMPLE
  500. }, 'google_translate_element');
  501. }`;
  502. r.insertBefore(el,r.firstChild);
  503.  
  504. el=document.createElement('script');
  505. el.src='https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit&ok='+(toolbarLang?'&hl='+toolbarLang:'');
  506. r.insertBefore(el,r.firstChild);
  507. }
  508.  
  509. var gte=document.createElement('div');
  510. gte.id='google_translate_element';
  511. document.body.appendChild(gte);
  512.  
  513. var gmi='';
  514. try{
  515. gmi=GM_info.script['run-at'];
  516. }catch(e){}
  517.  
  518. function locS(id) {
  519. if (!gtLang) {
  520. try{if (gtLang=google && google.translate && google.translate._const && google.translate._const._cl) loc='';
  521. }catch(e){}
  522. }
  523. if (!loc) {
  524. loc= local[gtLang] || local[gtLang.split('-')[0]] || local[toolbarLang] || local[toolbarLang.split('-')[0]] || local[navigator.language] || local[navigator.language.split('-')[0]] || local.en;
  525. if (loc._ && (Object.keys(loc).length == 1) ) {
  526. let L=loc._.split(/\n/);
  527. [loc.tr_on, loc.tr_all_off, loc.tr_all_off_s, loc.tr_all_on, loc.set_def, loc.float_tit]=L;
  528. }
  529. for (let k in local.en) {
  530. if (!loc[k]) loc[k]=local.en[k];
  531. }
  532. }
  533. return loc[id];
  534. }
  535.  
  536. var loc, gtLang='';
  537. const local = {
  538. en: {
  539. tr_on: 'Turn on translation for this site',
  540. tr_all_off: 'Turn off for all sites',
  541. tr_all_off_s: 'Turn off for all sites except this one',
  542. tr_all_on: 'Allow translation back on all sites',
  543. set_def: 'Set as default language',
  544. float_tit: 'Click to translate'
  545. },
  546. fr: {
  547. tr_on: 'Activer la traduction pour le site',
  548. tr_all_off: 'Désactiver pour tous les sites',
  549. tr_all_off_s: 'Désactiver pour tous les sites sauf celui-ci',
  550. tr_all_on: 'Réactiver la traduction sur tous les sites',
  551. set_def: 'Choisir comme langue par défaut',
  552. float_tit: 'Cliquer pour traduire'
  553. },
  554. nl: { _: `Schakel in vertaling voor deze site
  555. Schakel uit voor alle sites
  556. Schakel uit voor alle sites behalve deze
  557. Sta vertaling weer toe op alle sites
  558. Instellen als standaardtaal
  559. Klik om te vertalen`},
  560. es: { _: `Activar la traducción para este sitio
  561. Desactivar para todos los sitios
  562. Desactivar para todos los sitios excepto este
  563. Permitir la traducción en todos los sitios
  564. Establecer como idioma predeterminado
  565. Haga clic para traducir`},
  566. it: { _:`Attiva la traduzione per questo sito
  567. Disabilita per tutti i siti
  568. Disabilita per tutti i siti tranne questo
  569. Reattivare la traduzione su tutti i siti
  570. Scegli come lingua predefinita
  571. Fai clic per tradurre`},
  572. de: { _: `Aktivieren Sie die Übersetzung für diese Site
  573. Deaktivieren Sie für alle Websites
  574. Deaktivieren Sie für alle Websites außer diesem
  575. Reaktivieren Sie die Übersetzung an allen Standorten
  576. Wählen Sie als Standardsprache
  577. Klicken Sie hier, um zu übersetzen`},
  578. pt: { _: `Ative a tradução para este site
  579. Desativar para todos os sites
  580. Desativar para todos os sites, exceto isso
  581. Reativar a tradução em todos os sites
  582. Escolha como linguagem padrão
  583. Clique para traduzir`},
  584. ja: { _: `このサイトの翻訳をオンにする
  585. すべてのサイトの電源を切る
  586. それ以外のすべてのサイトの電源を切る
  587. すべてのサイトで翻訳を返す
  588. デフォルト言語として設定する
  589. クリックして翻訳する`},
  590. ar: { _: `تنشيط الترجمة لهذا الموقع
  591. تعطيل لجميع المواقع
  592. تعطيل لجميع المواقع باستثناء هذا
  593. إعادة تنشيط الترجمة على جميع المواقع
  594. اختر كلغة افتراضية
  595. انقر للترجمةة`},
  596. iw: { _: `הפעל תרגום לאתר זה
  597. כבה לכל האתרים
  598. כבה לכל האתרים למעט זה
  599. אפשר תרגום בחזרה לכל האתרים
  600. הגדר כשפת ברירת מחדל
  601. לחץ לתרגום`},
  602. 'zh-CN': { _: `打开此网站的翻译
  603. 关闭所有站点
  604. 除此之外,所有网站都关闭
  605. 允许在所有站点上翻译
  606. 设置为默认语言
  607. 单击以翻译`},
  608. 'zh-TW': { _: `打開此網站的翻譯
  609. 關閉所有站點
  610. 除此之外,所有網站都關閉
  611. 允許在所有站點上翻譯
  612. 設置為默認語言
  613. 單擊以翻譯`}
  614. };
  615.  
  616.  
  617. if (trans || getCookies().googtrans || !GM || ( gmi && (gmi == 'context-menu') ) && (gmi && !gmi.startsWith('document-') ) ) startGT();
  618. else {
  619. gte.innerHTML='&#167;';
  620. gte.title=locS('float_tit');
  621. gte.classList.add('T');
  622. gte.addEventListener('click', function(){
  623. this.innerHTML='';
  624. this.title='';
  625. this.classList.remove('T');
  626. startGT();
  627. }, {once: true});
  628. }
  629.  
  630. })()

QingJ © 2025

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