// ==UserScript== // @name Smart Auto Translate // @namespace smart-auto-translate // @version 2.1.0 // @description 自动检测设备语言,智能翻译网页内容并直接替换,支持Google/Microsoft/Tencent引擎,UA伪装 // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @connect translate.googleapis.com // @connect translate.google.com // @connect api-edge.cognitive.microsofttranslator.com // @connect edge.microsoft.com // @connect transmart.qq.com // @run-at document-idle // @noframes // @downloadURL none // ==/UserScript== (async () => { 'use strict'; try { if (/xml|json|image/.test(document.contentType || '')) return; } catch (_) {} if (!document.body) return; // ── 工具函数 ── function gmFetch(opts) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ timeout: 15000, ...opts, onload: resolve, onerror: reject, ontimeout: reject }); }); } const delay = ms => new Promise(r => setTimeout(r, ms)); const shortLang = c => c ? c.split('-')[0].toLowerCase() : ''; function normZh(code) { if (!code || !code.toLowerCase().startsWith('zh')) return code; return /tw|hant|hk/i.test(code) ? 'zh-TW' : 'zh-CN'; } const rawDeviceLang = navigator.language || 'zh-CN'; const deviceShort = shortLang(rawDeviceLang); function getPageLang() { return (document.documentElement.getAttribute('lang') || '').trim(); } function sameFamily(a, b) { return a && b && shortLang(a) === shortLang(b); } // ── 各引擎对应的UA伪装 ── // Google → Chrome桌面版(模拟网页版Google翻译) // Microsoft → Edge浏览器(Edge内置翻译) // Tencent → QQ浏览器(QQ内置翻译) const UA = { google: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', microsoft: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0', tencent: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36 QBCore/4.0.1.400 QQBrowser/11.5.5250.400', }; // ── 文本分析 ── function analyzeText(text) { if (!text) return 'unknown'; const cjk = (text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g) || []).length; const latin = (text.match(/[a-zA-ZÀ-ÿ]/g) || []).length; const total = cjk + latin || 1; if (cjk / total > 0.5) return 'cjk'; if (latin / total > 0.5) return 'latin'; return 'other'; } function likelyTarget(text, targetShort) { if (!text || text.trim().length < 2) return true; const dom = analyzeText(text); if (targetShort === 'zh' && dom === 'cjk') return true; if (targetShort === 'ja') { if ((text.match(/[\u3040-\u30ff]/g) || []).length > 0) return true; } if (targetShort === 'ko') { if ((text.match(/[\uac00-\ud7af]/g) || []).length > text.length * 0.3) return true; } if (targetShort === 'ru' && (text.match(/[\u0400-\u04ff]/g) || []).length > text.length * 0.3) return true; if (targetShort === 'ar' && (text.match(/[\u0600-\u06ff]/g) || []).length > text.length * 0.3) return true; if (targetShort === 'th' && (text.match(/[\u0e00-\u0e7f]/g) || []).length > text.length * 0.3) return true; // 拉丁语系:只有页面语言和目标同族才跳过 if (['en','fr','de','es','pt','it','tr','id','vi'].includes(targetShort) && dom === 'latin') { if (shortLang(getPageLang()) === targetShort) return true; } return false; } function isTranslatable(t) { if (!t) return false; t = t.trim(); if (t.length < 2) return false; if (/^[\d,.\s\-+%$€¥£]+$/.test(t)) return false; if (/^[\s\p{P}\p{S}]+$/u.test(t)) return false; if (/^https?:\/\//i.test(t)) return false; if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)) return false; if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(t) && t.length < 30) return false; if (/^#[0-9a-fA-F]{3,8}$/.test(t)) return false; if (/^v?\d+\.\d+(\.\d+)*$/.test(t)) return false; return true; } // ── 配置 ── const Config = { engine: 'microsoft', targetLang: deviceShort === 'zh' ? normZh(rawDeviceLang) : deviceShort, autoMode: true, excludedHosts: [], async load() { this.engine = await GM_getValue('st_engine', this.engine); this.targetLang = await GM_getValue('st_targetLang', this.targetLang); this.autoMode = await GM_getValue('st_autoMode', this.autoMode); try { this.excludedHosts = JSON.parse(await GM_getValue('st_excludedHosts', '[]')); } catch (_) { this.excludedHosts = []; } if (!['google','microsoft','tencent'].includes(this.engine)) this.engine = 'microsoft'; }, async save(k, v) { this[k] = v; await GM_setValue('st_' + k, k === 'excludedHosts' ? JSON.stringify(v) : v); }, isExcluded(h) { return this.excludedHosts.includes(h); } }; // ── 认证 ── const Auth = { ms: { token: null, expiry: 0, async getToken() { if (this.token && Date.now() < this.expiry) return this.token; const res = await gmFetch({ method: 'GET', url: 'https://edge.microsoft.com/translate/auth', headers: { 'User-Agent': UA.microsoft } }); if (res.status !== 200) throw new Error('MS auth ' + res.status); this.token = res.responseText; this.expiry = Date.now() + 480000; return this.token; } }, tencent: { _key: null, get() { if (!this._key) { try { this._key = localStorage.getItem('st_tk'); } catch (_) {} if (!this._key) { this._key = `browser-qq-${Date.now()}-${Math.random().toString(36).slice(2)}`; try { localStorage.setItem('st_tk', this._key); } catch (_) {} } } return this._key; } } }; // ── 语言代码映射 ── function mapLang(engine, lang) { const isZhTW = lang === 'zh-TW'; const maps = { google: { 'zh-CN':'zh-CN','zh-TW':'zh-TW' }, microsoft: { 'zh-CN':'zh-Hans','zh-TW':'zh-Hant' }, tencent: { 'zh-CN':'zh','zh-TW':'zh-TW' }, }; const m = maps[engine] || {}; if (m[lang]) return m[lang]; const s = shortLang(lang); if (s === 'zh') return m[isZhTW ? 'zh-TW' : 'zh-CN']; return s; } // ── 翻译引擎 ── const Engines = { google: { name: 'Google', async translate(texts, targetLang) { const to = mapLang('google', targetLang); const results = new Array(texts.length); const CONC = 8; let idx = 0; const work = async () => { while (idx < texts.length) { const i = idx++; try { const url = `https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=auto&tl=${to}&q=${encodeURIComponent(texts[i])}`; const res = await gmFetch({ method: 'GET', url, headers: { 'User-Agent': UA.google, 'Referer': 'https://translate.google.com/', 'Origin': 'https://translate.google.com', } }); if (res.status !== 200) throw new Error(res.status); const d = JSON.parse(res.responseText); results[i] = d[0].filter(s => s && s[0]).map(s => s[0]).join(''); } catch (_) { results[i] = texts[i]; } } }; await Promise.all(Array.from({ length: Math.min(CONC, texts.length) }, () => work())); return results; } }, microsoft: { name: 'Microsoft', async translate(texts, targetLang) { const to = mapLang('microsoft', targetLang); const results = new Array(texts.length); const BATCH = 50; for (let s = 0; s < texts.length; s += BATCH) { const batch = texts.slice(s, s + BATCH); try { const token = await Auth.ms.getToken(); const res = await gmFetch({ method: 'POST', url: `https://api-edge.cognitive.microsofttranslator.com/translate?from=&to=${to}&api-version=3.0`, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': UA.microsoft, 'Referer': 'https://www.bing.com/translator', 'Origin': 'https://www.bing.com', }, data: JSON.stringify(batch.map(t => ({ Text: t }))), }); if (res.status !== 200) throw new Error(res.status); const data = JSON.parse(res.responseText); for (let j = 0; j < batch.length; j++) { results[s + j] = data[j]?.translations?.[0]?.text || texts[s + j]; } } catch (_) { for (let j = 0; j < batch.length; j++) results[s + j] = texts[s + j]; } } return results; } }, tencent: { name: 'Tencent', async translate(texts, targetLang) { const to = mapLang('tencent', targetLang); const results = new Array(texts.length); const BATCH = 30; for (let s = 0; s < texts.length; s += BATCH) { const batch = texts.slice(s, s + BATCH); try { const res = await gmFetch({ method: 'POST', url: 'https://transmart.qq.com/api/imt', headers: { 'Content-Type': 'application/json', 'User-Agent': UA.tencent, 'Referer': 'https://transmart.qq.com/', 'Origin': 'https://transmart.qq.com', }, data: JSON.stringify({ header: { fn: 'auto_translation_block', session: '', client_key: Auth.tencent.get(), user: '' }, type: 'plain', model_category: 'normal', text_domain: 'general', source: { lang: 'auto', text_list: batch }, target: { lang: to }, }), }); if (res.status !== 200) throw new Error(res.status); const data = JSON.parse(res.responseText); if (data.auto_translation) { for (let j = 0; j < batch.length; j++) results[s + j] = data.auto_translation[j] || texts[s + j]; } else { for (let j = 0; j < batch.length; j++) results[s + j] = texts[s + j]; } } catch (_) { for (let j = 0; j < batch.length; j++) results[s + j] = texts[s + j]; } } return results; } }, }; // 回退机制 async function translateFallback(texts, targetLang, primary) { for (const name of [primary, ...['microsoft','google','tencent'].filter(e => e !== primary)]) { try { return await Engines[name].translate(texts, targetLang); } catch (_) { continue; } } return [...texts]; } // ── 缓存 ── const Cache = { _m: new Map(), MAX: 3000, _k(e, l, t) { return `${e}|${l}|${t}`; }, get(e, l, t) { const k = this._k(e, l, t), v = this._m.get(k); if (v !== undefined) { this._m.delete(k); this._m.set(k, v); } return v; }, set(e, l, t, r) { const k = this._k(e, l, t); if (this._m.size >= this.MAX) this._m.delete(this._m.keys().next().value); this._m.set(k, r); }, lookup(e, l, texts) { const cached = new Map(), uncached = []; texts.forEach((t, i) => { const v = this.get(e, l, t); v !== undefined ? cached.set(i, v) : uncached.push(i); }); return { cached, uncached }; }, clear() { this._m.clear(); } }; // ── DOM操作 ── const SKIP_TAGS = new Set(['SCRIPT','STYLE','CODE','PRE','SVG','MATH','NOSCRIPT','IFRAME','CANVAS','VIDEO','AUDIO','IMG','BR','HR','INPUT','SELECT','OPTION','TEXTAREA','KBD','SAMP','VAR']); const SKIP_CLS = /notranslate|katex|mathjax|highlight|hljs|prism|code-|CodeMirror|ace_editor|monaco|st-ui/i; function skipEl(el) { if (!el) return true; if (SKIP_TAGS.has(el.tagName)) return true; if (el.isContentEditable) return true; if (SKIP_CLS.test(el.className || '')) return true; if (el.getAttribute('translate') === 'no') return true; if (el.hasAttribute('data-st-orig')) return true; return false; } function collectNodes(root) { const nodes = [], ts = shortLang(Config.targetLang); function walk(n) { if (!n) return; if (n.nodeType === 1) { if (skipEl(n)) return; if (n.shadowRoot) walk(n.shadowRoot); for (let i = 0; i < n.childNodes.length; i++) walk(n.childNodes[i]); return; } if (n.nodeType === 3) { const t = n.textContent; if (!t || !t.trim()) return; if (n.parentElement?.hasAttribute('data-st-orig')) return; if (!isTranslatable(t.trim())) return; if (likelyTarget(t.trim(), ts)) return; nodes.push(n); } } walk(root); return nodes; } function collectPH(root) { const els = [], ts = shortLang(Config.targetLang); try { root.querySelectorAll('input[placeholder],textarea[placeholder]').forEach(el => { if (el.hasAttribute('data-st-orig-ph')) return; const p = el.placeholder.trim(); if (p && isTranslatable(p) && !likelyTarget(p, ts)) els.push(el); }); } catch (_) {} return els; } function applyTrans(nodes, trans) { for (let i = 0; i < nodes.length; i++) { if (!trans[i] || trans[i] === nodes[i].textContent.trim()) continue; const p = nodes[i].parentElement; if (!p) continue; const span = document.createElement('span'); span.setAttribute('data-st-orig', nodes[i].textContent); span.textContent = trans[i]; span.style.cssText = 'display:contents;'; try { p.replaceChild(span, nodes[i]); } catch (_) {} } } function applyPH(els, trans) { for (let i = 0; i < els.length; i++) { if (!trans[i]) continue; els[i].setAttribute('data-st-orig-ph', els[i].placeholder); els[i].placeholder = trans[i]; } } function restoreAll() { document.querySelectorAll('[data-st-orig]').forEach(span => { try { span.parentElement.replaceChild(document.createTextNode(span.getAttribute('data-st-orig')), span); } catch (_) {} }); document.querySelectorAll('[data-st-orig-ph]').forEach(el => { el.placeholder = el.getAttribute('data-st-orig-ph'); el.removeAttribute('data-st-orig-ph'); }); if (_origTitle) document.title = _origTitle; } // ── 翻译管道 ── let _busy = false, _origTitle = '', _titleDone = false; async function translatePage(root) { if (_busy) return; _busy = true; try { root = root || document.body; if (!root) return; UI.setLoading(true); const textNodes = collectNodes(root); const phEls = collectPH(root); if (!textNodes.length && !phEls.length) { if (!_titleDone) await translateTitle(); return; } const engine = Config.engine, lang = Config.targetLang; const texts = textNodes.map(n => n.textContent.trim()); const phTexts = phEls.map(el => el.placeholder.trim()); // 文本翻译(带缓存) const { cached, uncached } = Cache.lookup(engine, lang, texts); const textResults = new Array(texts.length); for (const [i, v] of cached) textResults[i] = v; if (uncached.length) { const tr = await translateFallback(uncached.map(i => texts[i]), lang, engine); uncached.forEach((idx, j) => { textResults[idx] = tr[j]; if (tr[j] && tr[j] !== texts[idx]) Cache.set(engine, lang, texts[idx], tr[j]); }); } // Placeholder翻译 const { cached: phC, uncached: phU } = Cache.lookup(engine, lang, phTexts); const phResults = new Array(phTexts.length); for (const [i, v] of phC) phResults[i] = v; if (phU.length) { const tr = await translateFallback(phU.map(i => phTexts[i]), lang, engine); phU.forEach((idx, j) => { phResults[idx] = tr[j]; if (tr[j] && tr[j] !== phTexts[idx]) Cache.set(engine, lang, phTexts[idx], tr[j]); }); } applyTrans(textNodes, textResults); applyPH(phEls, phResults); if (!_titleDone) await translateTitle(); } finally { _busy = false; UI.setLoading(false); } } async function translateTitle() { const t = document.title?.trim(); if (!t || !isTranslatable(t) || likelyTarget(t, shortLang(Config.targetLang))) return; _origTitle = document.title; try { const c = Cache.get(Config.engine, Config.targetLang, t); if (c) { document.title = c; _titleDone = true; return; } const r = await translateFallback([t], Config.targetLang, Config.engine); if (r[0] && r[0] !== t) { Cache.set(Config.engine, Config.targetLang, t, r[0]); document.title = r[0]; _titleDone = true; } } catch (_) {} } // ── 动态内容观察 ── let _mtimer = null, _observing = false, _pending = new Set(); const observer = new MutationObserver(muts => { if (!Config.autoMode || !_observing) return; for (const m of muts) for (const n of m.addedNodes) { if (n.nodeType === 1 && !skipEl(n) && !n.hasAttribute?.('data-st-orig') && !n.classList?.contains('st-ui')) _pending.add(n); } if (!_pending.size) return; clearTimeout(_mtimer); _mtimer = setTimeout(async () => { const roots = [..._pending]; _pending.clear(); for (const r of roots) { if (!document.body.contains(r)) continue; const was = _busy; _busy = false; try { await translatePage(r); } catch (_) {} if (was) _busy = was; } }, 1200); }); let _stimer = null, _lastH = 0; function onScroll() { if (!Config.autoMode) return; clearTimeout(_stimer); _stimer = setTimeout(() => { const h = document.documentElement.scrollHeight; if (h > _lastH + 200) { _lastH = h; translatePage(); } }, 1000); } function startObs() { if (_observing) return; _observing = true; _lastH = document.documentElement.scrollHeight; observer.observe(document.body, { childList: true, subtree: true }); window.addEventListener('scroll', onScroll, { passive: true }); } function stopObs() { _observing = false; observer.disconnect(); window.removeEventListener('scroll', onScroll); } // ── UI ── const UI = { container: null, btn: null, panel: null, _toast: null, _tt: null, init() { GM_addStyle(` .st-ui{position:fixed;bottom:20px;right:20px;z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:13px;line-height:1.4;-webkit-tap-highlight-color:transparent} .st-btn{width:44px;height:44px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:rgba(60,60,60,.65);color:#fff;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 2px 12px rgba(0,0,0,.2);transition:all .25s;touch-action:manipulation;user-select:none;position:relative} .st-btn:active{transform:scale(.9)}.st-btn.active{background:rgba(59,130,246,.8)} .st-btn.loading::after{content:'';position:absolute;inset:-3px;border-radius:50%;border:2px solid transparent;border-top-color:rgba(255,255,255,.8);animation:st-spin .8s linear infinite} @keyframes st-spin{to{transform:rotate(360deg)}} .st-panel{position:absolute;bottom:54px;right:0;width:220px;background:rgba(255,255,255,.96);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-radius:14px;box-shadow:0 8px 32px rgba(0,0,0,.15);padding:14px;display:none;color:#333;animation:st-in .2s} .st-panel.show{display:block} @keyframes st-in{from{opacity:0;transform:scale(.92) translateY(8px)}to{opacity:1;transform:none}} .st-panel label{display:block;margin:0 0 4px;font-size:11px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:.5px} .st-panel label:not(:first-child){margin-top:10px} .st-panel select{width:100%;padding:7px 10px;border:1px solid rgba(0,0,0,.1);border-radius:8px;font-size:13px;background:rgba(0,0,0,.03);color:#333;outline:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;transition:border-color .2s} .st-panel select:focus{border-color:rgba(59,130,246,.5);box-shadow:0 0 0 3px rgba(59,130,246,.1)} .st-acts{display:flex;gap:6px;margin-top:12px} .st-acts button{flex:1;padding:8px 0;border:none;border-radius:8px;font-size:12px;font-weight:500;cursor:pointer;transition:all .2s;touch-action:manipulation} .st-r{background:rgba(0,0,0,.06);color:#555}.st-r:active{background:rgba(0,0,0,.12)} .st-t{background:rgba(59,130,246,.9);color:#fff}.st-t:active{background:rgba(59,130,246,1)} .st-acts2{display:flex;gap:6px;margin-top:6px} .st-acts2 button{flex:1;padding:7px 0;border:none;border-radius:8px;font-size:11px;cursor:pointer;transition:all .2s;touch-action:manipulation} .st-ex{background:rgba(239,68,68,.1);color:#ef4444}.st-ex:active{background:rgba(239,68,68,.2)} .st-au{background:rgba(16,185,129,.1);color:#059669}.st-au.off{background:rgba(0,0,0,.05);color:#999} .st-toast{position:fixed;top:20px;left:50%;transform:translateX(-50%) translateY(-100%);padding:10px 20px;background:rgba(30,30,30,.9);color:#fff;border-radius:10px;font-size:13px;z-index:2147483647;backdrop-filter:blur(10px);box-shadow:0 4px 16px rgba(0,0,0,.2);transition:all .3s;opacity:0;pointer-events:none} .st-toast.show{transform:translateX(-50%) translateY(0);opacity:1} @media(prefers-color-scheme:dark){.st-panel{background:rgba(30,30,30,.96);color:#e0e0e0;box-shadow:0 8px 32px rgba(0,0,0,.4)}.st-panel select{background:rgba(255,255,255,.06);color:#e0e0e0;border-color:rgba(255,255,255,.12)}.st-r{background:rgba(255,255,255,.1);color:#ccc}} `); const langs = [['zh-CN','简体中文'],['zh-TW','繁體中文'],['en','English'],['ja','日本語'],['ko','한국어'],['fr','Français'],['de','Deutsch'],['es','Español'],['ru','Русский'],['pt','Português'],['ar','العربية'],['th','ไทย'],['vi','Tiếng Việt'],['it','Italiano'],['tr','Türkçe'],['id','Bahasa Indonesia']]; const li = this._findIdx(langs, Config.targetLang); const c = document.createElement('div'); c.className = 'st-ui'; c.innerHTML = `
`; this.container = c; this.btn = c.querySelector('.st-btn'); this.panel = c.querySelector('.st-panel'); const toast = document.createElement('div'); toast.className = 'st-toast'; document.body.appendChild(toast); this._toast = toast; document.body.appendChild(c); // 事件 this.btn.onclick = e => { e.stopPropagation(); this.panel.classList.toggle('show'); }; document.addEventListener('click', e => { if (!c.contains(e.target)) this.panel.classList.remove('show'); }); c.querySelector('.se').onchange = async function() { await Config.save('engine', this.value); Cache.clear(); }; c.querySelector('.sl').onchange = async function() { await Config.save('targetLang', this.value); Cache.clear(); _titleDone = false; restoreAll(); if (Config.autoMode) await translatePage(); }; c.querySelector('.st-t').onclick = async () => { this.panel.classList.remove('show'); this.btn.classList.add('active'); _titleDone = false; await translatePage(); this.toast('✓'); }; c.querySelector('.st-r').onclick = () => { this.panel.classList.remove('show'); this.btn.classList.remove('active'); restoreAll(); }; const ab = c.querySelector('.st-au'); ab.onclick = async () => { const m = !Config.autoMode; await Config.save('autoMode', m); ab.textContent = `自动:${m?'ON':'OFF'}`; ab.classList.toggle('off', !m); this.btn.classList.toggle('active', m); m ? (startObs(), await translatePage()) : stopObs(); }; c.querySelector('.st-ex').onclick = async () => { const h = location.host; if (!Config.excludedHosts.includes(h)) { Config.excludedHosts.push(h); await Config.save('excludedHosts', Config.excludedHosts); } restoreAll(); stopObs(); c.remove(); }; }, _findIdx(opts, lang) { let i = opts.findIndex(([c]) => c === lang); if (i >= 0) return i; i = opts.findIndex(([c]) => shortLang(c) === shortLang(lang)); return i >= 0 ? i : 0; }, setLoading(v) { this.btn?.classList.toggle('loading', v); }, toast(msg, dur = 1500) { if (!this._toast) return; this._toast.textContent = msg; this._toast.classList.add('show'); clearTimeout(this._tt); this._tt = setTimeout(() => this._toast.classList.remove('show'), dur); } }; // ── 启动 ── await Config.load(); if (Config.isExcluded(location.host)) return; UI.init(); GM_registerMenuCommand('🌐 翻译页面', () => translatePage()); GM_registerMenuCommand('↩️ 还原页面', () => restoreAll()); GM_registerMenuCommand('⚙️ 切换自动', async () => { await Config.save('autoMode', !Config.autoMode); Config.autoMode ? (startObs(), translatePage()) : stopObs(); }); if (Config.autoMode) startObs(); if (Config.autoMode && !sameFamily(getPageLang(), Config.targetLang)) { await delay(1500); await translatePage(); } })();