- // ==UserScript==
- // @name Translate webpage
- // @namespace https://github.com/Procyon-b
- // @version 0.5.1
- // @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
- // @author Achernar
- // @match *://*/*
- // @run-at context-menu
- // @run-at document-idle
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_deleteValue
- // ==/UserScript==
-
- javascript:
- (function(){
- "use strict";
-
- var defLang="",toolbarLang="";
-
- if (document.querySelector('body > .skiptranslate > iframe')) return;
- try {
- if (window.frameElement.classList.contains('skiptranslate')) return;
- }catch(e){}
-
- var nLang=navigator.language.split('-')[0];
-
- if (defLang == '?') defLang=nLang;
- if (toolbarLang == '?') toolbarLang=nLang;
-
- function getCookies() {
- var r={}, a=document.cookie;
- a.split(';').forEach(function(e){
- var p=e.split('=');
- if (p[0]) r[p.shift().trim()]=p.join('=');
- });
- return r;
- };
-
- function clearCookie(n) {
- var h=location.host, s=h.split('.'), a=[' ', h, '.'+h], d, old=(new Date(0)).toUTCString();
- while (s.length > 2) {
- if (s[0]) s[0]='';
- else s.shift();
- a.push(s.join('.'));
- }
- while (d=a.shift()) {
- document.cookie= n+'=;path=/;expires='+old+';'+(d==' ' ? '':'domain='+d+';');
- if (!getCookies()[n]) break;
- }
- }
-
- var translate_to=defLang || '',
- translate_toS='',
- done=false,
- force=true,
- btnAdded=true,
- curVBtn,
- setDefBtn,
- closeBtn,
- GM=false;
-
- try{
- [translate_to, toolbarLang]=getDef();
- btnAdded=false;
- GM=true;
- }catch(e){}
-
- var curLang='', curLangS='';
- function getDef() {
- return [GM_getValue('translate_to',''), GM_getValue('toolbarLang','')];
- }
- function setDef() {
- GM_setValue('translate_to', curLang);
- translate_to=curLang;
- translate_toS=curLangS;
- updCurBtn();
- }
-
- function updCurBtn() {
- if (curVBtn) curVBtn.innerText=(translate_toS && translate_toS+' ('+translate_to+')') || translate_to;
- toggleBtn();
- }
-
- function toggleSite(force) {
- var h='no:'+location.host;
- try{
- aBlocked=GM_getValue('no-all', false);
- blocked= force !== undefined ? !force : GM_getValue(h, false);
- if (force === null) return;
- if (blocked) {
- if (aBlocked) GM_setValue(h, false);
- else GM_deleteValue(h);
- }
- else GM_setValue(h, true);
- }catch(e){}
- }
-
- var trans=true, blocked=false, aBlocked=false, cookies=getCookies();
- if (cookies.googtransopt) trans=false;
-
- function isBlocked() {
- let blocked=GM_getValue('no:'+location.host, null);
- aBlocked=GM_getValue('no-all', false);
-
- if (aBlocked && (blocked === false) ) return false;
- return aBlocked || Boolean(blocked);
- }
-
- try{
- blocked=isBlocked();
- trans=!blocked;
- if (trans && cookies.googtransopt) clearCookie('googtransopt');
- }catch(e){}
-
- if (!GM && trans && translate_to && !getCookies().googtrans) document.cookie='googtrans=/auto/'+translate_to+'/; path=/';
-
- var eael=Element.prototype.addEventListener;
- Element.prototype.addEventListener=function(ev){
- var e=this;
- if (ev == 'mouseover') {
- if (String(arguments[1]).startsWith('function(c){return a.call(')) return;
- }
- return eael.apply(e,arguments);
- };
-
- var sa=HTMLElement.prototype.setAttribute;
- HTMLElement.prototype.setAttribute=function(a){
- var e=this;
- if (a == 'aria-label') {
- let err=(new Error()).stack;
- if (err.includes('https://translate.googleapis.com/_/translate_http/_/js/')) return;
- }
- return sa.apply(e,arguments);
- };
-
- var pm=Performance.prototype.measure;
- Performance.prototype.measure=function() {
- var e=this;
- try{
- return pm.apply(e, arguments);
- }catch(e){}
- return false;
- };
-
- new MutationObserver(function(muts) {
- for (let mut of muts) {
- if (mut.addedNodes.length) {
- for (let i=0,n; n=mut.addedNodes[i]; i++) {
- if (n.nodeName == 'IFRAME' && n.classList.contains('skiptranslate') ) {
- addSt(frameSt, n.contentWindow);
- n.onload=function(){
- let e=this;
- frameMut(e);
- }
- }
- else if (n.nodeName == 'DIV') {
- if (n.classList.contains('skiptranslate') ) {
- let tb=document.body.querySelector('body > .skiptranslate > iframe.skiptranslate');
- if (tb) {
- addSt(frameSt, tb.contentWindow);
- frameMut(tb);
- }
- }
- else if (n.parentNode && (n.attributes.length ==1) && n.className && /^[^-_]{6}-[^-_]{6}-[^-_]{6}-[^-_]{6}/i.test(n.className) ) {
- let s=getComputedStyle(n);
- if ( (s.zIndex == '1000') && (s.transitionDelay == '0.6s') ) addSt('.'+n.className.split(' ')[0]+' {display: none;}');
- }
- }
- }
- }
- }
- }).observe(document.body, { attributes: false, subtree: false, childList: true });
-
- function frameMut(f) {
- try{
- f.dataset.done=true;
- new MutationObserver(function(muts) {
- handleFrame(f);
- }).observe(f.contentWindow.document.body, { attributes: false, subtree: true, childList: true });
- handleFrame(f);
- }catch(e){}
- }
-
- function clickIF(ev) {
- var v=this.value;
- if (v === undefined) return;
- if (v == 'turn_off_site') {
- toggleSite(true);
- toggleMenu(false);
- }
- if (v == 'turn_on_site') {
- clearCookie('googtransopt');
- menuFrame.style.display='none';
- toggleSite(false);
- toggleMenu(true);
- }
- else if (v == 'turn_off_all') {
- GM_setValue('no-all', true);
- menuFrame.style.display='none';
- toggleSite(null);
- toggleMenu(!isBlocked());
- closeBtn && closeBtn.click();
- }
- else if (v == 'turn_off_all_m') {
- GM_setValue('no-all', true);
- menuFrame.style.display='none';
- toggleSite(false);
- toggleMenu(!isBlocked());
- }
- else if (v == 'turn_on_all') {
- GM_deleteValue('no-all');
- menuFrame.style.display='none';
- toggleSite(null);
- toggleMenu(!isBlocked());
- }
- else if (v == 'forceMnuLang') {
- let L=null;
-
- if ( (ev.target.nodeName == 'SELECT') || (ev.target.nodeName == 'OPTION') ) L=ev.target.value;
- else if (!LSelect) L=toolbarLang || nLang;
-
- if (!LSelect) {
- let r=this.querySelector('div:not(.select)');
- if (r) {
- r.classList.add('select');
- let s='';
- LSelect=document.createElement('select');
- for (let k of Object.keys(CCode).sort() ) {
- s+='<option value="'+k+'">'+k+'</option>';
- }
- LSelect.innerHTML=s;
- r.appendChild(LSelect);
- }
- }
-
- if (L !== null) {
- toolbarLang=L;
- if (L) GM_setValue('toolbarLang', L);
- else GM_deleteValue('toolbarLang');
- if (LSelect) LSelect.value=toolbarLang;
- }
- }
- else {
- curLang=v;
- curLangS=this.innerText;
- updCurBtn();
- }
- }
-
- function toggleMenu(t) {
- if (!menuFrame) return;
- if (t !== undefined) trans=t;
- menuFrame.contentWindow.document.body.classList.toggle('t_off', !trans);
- menuFrame.contentWindow.document.body.classList.toggle('t_all_off', aBlocked);
- }
-
-
- function toggleBtn() {
- if (curVBtn) {
- curVBtn.parentNode.classList.toggle('hidden', !translate_to);
- curVBtn.parentNode.classList.toggle('disabled', curLang == translate_to );
- }
- if (setDefBtn) {
- setDefBtn.parentNode.classList.toggle('disabled', curLang == translate_to );
- }
- }
-
- function fixLinks(a, s) {
- if (s) void(0);
- var r;
- for (let i=0, e; e=a[i]; i++) {
- if (e.attributes.href.value == "#") {
- e.href='javascript:void(0)';
- if (!e._cIF_) e.addEventListener('click', clickIF);
- e._cIF_=true;
- if (s && !r && (e.value == s)) r=e;
- }
- }
- return r;
- }
-
- var langFrame, menuFrame, CCode={}, LSelect;
-
- function forceLang(L, A, set=true) {
- var a= A || (langFrame && langFrame.contentWindow.document.body.querySelectorAll('a')) || [];
- if (!L) {
- let i, e, c, cl={};
- for (i=0; e=a[i]; i++) {
- c=e.classList.value;
- if (!cl[c]) cl[c]={c: 0, e: null};
- cl[c].c++;
- cl[c].e=e;
- if (set) CCode[e.value]=e.innerText;
- if (translate_to && !translate_toS && (translate_to == e.value) ) {
- translate_toS=e.innerText;
- updCurBtn();
- }
- }
- for (c in cl) {
- if ( (cl[c].c == 1) && cl[c].e.value) {
- curLang=cl[c].e.value;
- curLangS=cl[c].e.innerText;
- updCurBtn();
- break;
- }
- }
- return;
- }
- for (let i=0, e; e=a[i]; i++) {
- if (e.value == L) {
- if (set) e.click();
- return true;
- }
- }
- }
-
- function handleFrame(f) {
- if (!langFrame) langFrame=f;
- var a;
- try{
- a=f.contentWindow.document.body.querySelectorAll('a');
- }catch(e){
- return;}
- var mnu=fixLinks(a, menuFrame?'':'turn_off_site');
- if (!menuFrame && mnu) {
- function createMnu(mnu, cfg) {
- let e=mnu.cloneNode(true);
- e.addEventListener('click', clickIF);
- e._cIF_=true;
- e.classList.add(cfg.c);
- e.value=cfg.v;
- let t=e.querySelector('.text');
- if (t) {
- t.innerHTML=cfg.s;
- }
- if (cfg.p == 'last') mnu.parentNode.appendChild(e);
- else mnu.parentNode.insertBefore(e, cfg.ns);
- return e;
- }
- menuFrame=f;
- if (GM) {
- let ns=mnu.nextSibling;
- let e=createMnu(mnu,{v: 'turn_on_site', c: 't_on', s: locS('tr_on'), ns});
- createMnu(mnu, {ns, v: 'turn_off_all', c: 't_off_all', s: locS('tr_all_off') });
- createMnu(mnu, {ns, v: 'turn_off_all_m', c: 't_off_allm', s: locS('tr_all_off_s') });
- createMnu(mnu, {ns, v: 'turn_on_all', c: 't_on_all', s: locS('tr_all_on') });
- createMnu(mnu, {ns, v: 'forceMnuLang', c: 'forceMnu', s: '¿?', p: 'last' });
- mnu.classList.add('t_off');
- toggleMenu(trans);
- }
- }
- if (!curLang && (langFrame == f)) forceLang(null, a);
-
- if (!done) {
- fixLinks(document.body.querySelectorAll('body > #google_translate_element a'));
- }
-
- var cookies=getCookies();
-
- if (translate_to && cookies.googtrans && !done) force=false;
- if (translate_to && (!cookies.googtrans || !force) && !done) {
- if (trans && forceLang(translate_to, a, force)) {
- done=true;
- return;
- }
- }
-
- function createBt(r, cfg, d=document) {
- var m=r.cloneNode(true),
- b=m.querySelector('button[id$="restore"]'),
- e=d.createElement('button');
- e.innerText=cfg.text || '';
- e.id=cfg.id;
- b.parentNode.classList.add(cfg.id);
- b.parentNode.classList.add('addedBtn');
- b.parentNode.replaceChild(e, b);
- r.parentNode.appendChild(m);
- return e;
- }
-
- if (!btnAdded) {
- let btn=f.contentWindow.document.body.querySelector('button[id$="restore"]');
- if (btn) {
- let r=btn.closest('td');
- setDefBtn=createBt(r, {text: locS('set_def'), id: 'setDefLn'} );
- setDefBtn.onclick=setDef;
- curVBtn=createBt(r, {text: (translate_toS && translate_toS+' ('+translate_to+')') || translate_to, id: 'curDefLn'} );
- curVBtn.onclick=function(){forceLang(translate_to);};
- toggleBtn();
- btnAdded=true;
- closeBtn=f.contentWindow.document.body.querySelector('a[id$="close"]');
- }
- }
- }
-
-
- function addSt(s, r) {
- try {
- var st=document.createElement('style');
- var R= r || window;
- R.document.documentElement.appendChild(st);
- st.textContent=s;
- }catch(e){
- setTimeout(function(){addSt(s, r)},0); }
- };
-
- addSt(`
- html[style*="height: 100%;"] {
- height: auto !important;
- }
- body[style*="position: relative; min-height: 100%; top: 40px;"],
- body[style*="position: relative; min-height: 100%; top: 0px;"] {
- min-height: initial !important;
- top: unset !important;
- position: initial !important;
- }
- body > .skiptranslate > iframe:not(:hover) {
- height: 4px;
- opacity: 0;
- }
- iframe.skiptranslate,
- body > #google_translate_element {
- zoom: ${1/window.getComputedStyle(document.body).zoom};
- }
- iframe.skiptranslate[title] {
- max-width: 95% !important;
- margin-right: 1em;
- }
- iframe.skiptranslate[title][style*="; left: 0px;"] {
- left: 1em !important;
- }
- body > .skiptranslate:not([id]):not([style*="display:"]) ~ #google_translate_element,
- body > #google_translate_element:empty {
- display: none;
- }
- body > #google_translate_element {
- position: fixed;
- top: 0px;
- right: 0px;
- z-index: 9999999;
- }
- body > #google_translate_element.T:hover {
- background: white;
- color: black;
- padding: 0.5em;
- border: 2px solid red;
- font: bold 14px arial;
- }
- body > #google_translate_element.T:not(:hover) {
- opacity: 0;
- max-width: 3px;
- max-height: 3px;
- }
-
- no #google_translate_element img {
- display: none !important;
- }
- `);
-
- var frameSt=`
- body[scroll="no"] {
- overflow-x: auto !important;
- }
- body[scroll="no"] > div {
- padding-bottom: 20px !important;
- }
- body[scroll="no"] .setDefLn {
- margin-left: 2em !important;
- }
- body[scroll="no"] .addedBtn {
- margin-left: 0.5em;
- }
- body[scroll="no"] .addedBtn.hidden,
- body[scroll="no"]:not(.t_off) .t_on,
- body[scroll="no"].t_off .t_off,
- body[scroll="no"]:not(.t_all_off) .t_on_all,
- body[scroll="no"].t_all_off .t_off_all,
- body[scroll="no"].t_all_off .t_off_allm {
- display: none !important;
- }
- body[scroll="no"] .addedBtn.disabled {
- opacity: 0.6;
- pointer-events: none;
- }
- body[scroll="no"] .forceMnu select {
- margin: 0 1em;
- }
- `;
-
- function startGT() {
- var r=document.head || document.documentElement;
- var el=document.createElement('script');
- el.textContent=`
- function googleTranslateElementInit() {
- new google.translate.TranslateElement({
- pageLanguage: '',
- autoDisplay: false,
- layout: google.translate.TranslateElement.InlineLayout.SIMPLE
- }, 'google_translate_element');
- }`;
- r.insertBefore(el,r.firstChild);
-
- el=document.createElement('script');
- el.src='https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit&ok='+(toolbarLang?'&hl='+toolbarLang:'');
- r.insertBefore(el,r.firstChild);
- }
-
- var gte=document.createElement('div');
- gte.id='google_translate_element';
- document.body.appendChild(gte);
-
- var gmi='';
- try{
- gmi=GM_info.script['run-at'];
- }catch(e){}
-
- function locS(id) {
- if (!gtLang) {
- try{if (gtLang=google && google.translate && google.translate._const && google.translate._const._cl) loc='';
- }catch(e){}
- }
- if (!loc) {
- loc= local[gtLang] || local[gtLang.split('-')[0]] || local[toolbarLang] || local[toolbarLang.split('-')[0]] || local[navigator.language] || local[navigator.language.split('-')[0]] || local.en;
- if (loc._ && (Object.keys(loc).length == 1) ) {
- let L=loc._.split(/\n/);
- [loc.tr_on, loc.tr_all_off, loc.tr_all_off_s, loc.tr_all_on, loc.set_def, loc.float_tit]=L;
- }
- for (let k in local.en) {
- if (!loc[k]) loc[k]=local.en[k];
- }
- }
- return loc[id];
- }
-
- var loc, gtLang='';
- const local = {
- en: {
- tr_on: 'Turn on translation for this site',
- tr_all_off: 'Turn off for all sites',
- tr_all_off_s: 'Turn off for all sites except this one',
- tr_all_on: 'Allow translation back on all sites',
- set_def: 'Set as default language',
- float_tit: 'Click to translate'
- },
- fr: {
- tr_on: 'Activer la traduction pour le site',
- tr_all_off: 'Désactiver pour tous les sites',
- tr_all_off_s: 'Désactiver pour tous les sites sauf celui-ci',
- tr_all_on: 'Réactiver la traduction sur tous les sites',
- set_def: 'Choisir comme langue par défaut',
- float_tit: 'Cliquer pour traduire'
- },
- nl: { _: `Schakel in vertaling voor deze site
- Schakel uit voor alle sites
- Schakel uit voor alle sites behalve deze
- Sta vertaling weer toe op alle sites
- Instellen als standaardtaal
- Klik om te vertalen`},
- es: { _: `Activar la traducción para este sitio
- Desactivar para todos los sitios
- Desactivar para todos los sitios excepto este
- Permitir la traducción en todos los sitios
- Establecer como idioma predeterminado
- Haga clic para traducir`},
- it: { _:`Attiva la traduzione per questo sito
- Disabilita per tutti i siti
- Disabilita per tutti i siti tranne questo
- Reattivare la traduzione su tutti i siti
- Scegli come lingua predefinita
- Fai clic per tradurre`},
- de: { _: `Aktivieren Sie die Übersetzung für diese Site
- Deaktivieren Sie für alle Websites
- Deaktivieren Sie für alle Websites außer diesem
- Reaktivieren Sie die Übersetzung an allen Standorten
- Wählen Sie als Standardsprache
- Klicken Sie hier, um zu übersetzen`},
- pt: { _: `Ative a tradução para este site
- Desativar para todos os sites
- Desativar para todos os sites, exceto isso
- Reativar a tradução em todos os sites
- Escolha como linguagem padrão
- Clique para traduzir`},
- ja: { _: `このサイトの翻訳をオンにする
- すべてのサイトの電源を切る
- それ以外のすべてのサイトの電源を切る
- すべてのサイトで翻訳を返す
- デフォルト言語として設定する
- クリックして翻訳する`},
- ar: { _: `تنشيط الترجمة لهذا الموقع
- تعطيل لجميع المواقع
- تعطيل لجميع المواقع باستثناء هذا
- إعادة تنشيط الترجمة على جميع المواقع
- اختر كلغة افتراضية
- انقر للترجمةة`},
- iw: { _: `הפעל תרגום לאתר זה
- כבה לכל האתרים
- כבה לכל האתרים למעט זה
- אפשר תרגום בחזרה לכל האתרים
- הגדר כשפת ברירת מחדל
- לחץ לתרגום`},
- 'zh-CN': { _: `打开此网站的翻译
- 关闭所有站点
- 除此之外,所有网站都关闭
- 允许在所有站点上翻译
- 设置为默认语言
- 单击以翻译`},
- 'zh-TW': { _: `打開此網站的翻譯
- 關閉所有站點
- 除此之外,所有網站都關閉
- 允許在所有站點上翻譯
- 設置為默認語言
- 單擊以翻譯`}
- };
-
-
- if (trans || getCookies().googtrans || !GM || ( gmi && (gmi == 'context-menu') ) && (gmi && !gmi.startsWith('document-') ) ) startGT();
- else {
- gte.innerHTML='§';
- gte.title=locS('float_tit');
- gte.classList.add('T');
- gte.addEventListener('click', function(){
- this.innerHTML='';
- this.title='';
- this.classList.remove('T');
- startGT();
- }, {once: true});
- }
-
- })()