// ==UserScript== // @name SwiftTranslate // @name:zh-CN 迅捷翻译 // @description 高性能网页翻译工具,支持实时双语对照 // @version 2.0.0 // @author SwiftTranslate Team // @namespace https://example.com/swifttranslate // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @connect translate.googleapis.com // @connect api.cognitive.microsofttranslator.com // @connect api.transmart.qq.com // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; // ========== 核心性能优化配置 ========== const CONFIG = { // 翻译相关 engine: 'google', targetLang: 'zh-CN', displayMode: 'translated', // translated, bilingual, original autoTranslate: true, // 性能优化 batchSize: 30, concurrency: 6, cacheSize: 2000, translationDelay: 200, // 防抖延迟 scrollThreshold: 1000, // 滚动检测阈值 // UI相关 uiPosition: { x: -1, y: -1 }, hideAfterUse: false, vibration: true }; // ========== 高性能缓存系统 ========== class FastCache { constructor(maxSize = 2000) { this.map = new Map(); this.maxSize = maxSize; this.hits = 0; this.misses = 0; } get(key) { const value = this.map.get(key); if (value) { this.map.delete(key); this.map.set(key, value); this.hits++; return value; } this.misses++; return null; } set(key, value) { if (this.map.size >= this.maxSize) { const firstKey = this.map.keys().next().value; this.map.delete(firstKey); } this.map.set(key, value); } clear() { this.map.clear(); this.hits = 0; this.misses = 0; } } // ========== 轻量级翻译引擎 ========== const TranslationEngine = { google: { name: 'Google', async translate(text, lang) { const cacheKey = `g_${lang}_${text}`; const cached = cache.get(cacheKey); if (cached) return cached; try { const response = await fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${lang}&dt=t&q=${encodeURIComponent(text)}`); const data = await response.json(); const result = data[0].map(item => item[0]).join(''); if (result) { cache.set(cacheKey, result); return result; } } catch (e) { console.warn('[SwiftTranslate] Google翻译失败:', e); } return null; } }, microsoft: { name: 'Microsoft', async translate(text, lang) { const cacheKey = `m_${lang}_${text}`; const cached = cache.get(cacheKey); if (cached) return cached; try { // 获取认证令牌 const authResponse = await fetch('https://edge.microsoft.com/translate/auth'); const token = await authResponse.text(); const response = await fetch( `https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=${lang}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify([{ Text: text }]) } ); const data = await response.json(); const result = data[0]?.translations[0]?.text; if (result) { cache.set(cacheKey, result); return result; } } catch (e) { console.warn('[SwiftTranslate] Microsoft翻译失败:', e); } return null; } }, tencent: { name: 'Tencent', async translate(text, lang) { const cacheKey = `t_${lang}_${text}`; const cached = cache.get(cacheKey); if (cached) return cached; try { const clientKey = `swift_${Date.now()}_${Math.random().toString(36).slice(2)}`; const response = await fetch('https://api.transmart.qq.com/api/imt', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ header: { fn: 'auto_translation', client_key: clientKey }, type: 'plain', model_category: 'normal', source: { lang: 'auto', text_list: [text] }, target: { lang: lang === 'zh-CN' ? 'zh' : lang } }) }); const data = await response.json(); const result = data.auto_translation?.[0]; if (result) { cache.set(cacheKey, result); return result; } } catch (e) { console.warn('[SwiftTranslate] 腾讯翻译失败:', e); } return null; } } }; // ========== 高性能DOM处理器 ========== class DOMProcessor { constructor() { this.skipTags = new Set(['SCRIPT', 'STYLE', 'CODE', 'PRE', 'SVG', 'CANVAS']); this.translatedElements = new WeakSet(); this.observer = null; } isSkippable(element) { if (!element) return true; // 标签过滤 if (this.skipTags.has(element.tagName)) return true; // 类名过滤 if (element.classList) { if (element.classList.contains('swift-translated') || element.classList.contains('notranslate')) { return true; } } // 属性过滤 if (element.isContentEditable) return true; if (element.dataset?.swiftTranslated) return true; return false; } isChineseText(text) { // 快速中文检测 const chineseChars = text.match(/[\u4e00-\u9fff]/g); return chineseChars && chineseChars.length > text.length * 0.3; } collectTextNodes(root) { const nodes = []; const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { if (this.isSkippable(node.parentElement)) { return NodeFilter.FILTER_REJECT; } const text = node.textContent.trim(); // 基本过滤 if (!text || text.length < 2 || /^\d+$/.test(text)) { return NodeFilter.FILTER_REJECT; } // 语言检测 if (CONFIG.targetLang.startsWith('zh') && this.isChineseText(text)) { return NodeFilter.FILTER_REJECT; } // 已翻译过滤 if (this.translatedElements.has(node)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } } ); let node; while ((node = walker.nextNode())) { nodes.push(node); } return nodes; } async translateNodes(nodes) { if (nodes.length === 0) return; const texts = nodes.map(node => node.textContent.trim()); const results = []; // 批量翻译 for (let i = 0; i < texts.length; i += CONFIG.batchSize) { const batch = texts.slice(i, i + CONFIG.batchSize); const batchResults = await Promise.all( batch.map(text => TranslationEngine[CONFIG.engine].translate(text, CONFIG.targetLang)) ); results.push(...batchResults); // 分批应用结果,避免UI卡顿 setTimeout(() => { for (let j = 0; j < batchResults.length; j++) { const index = i + j; if (batchResults[j] && nodes[index]) { this.applyTranslation(nodes[index], batchResults[j]); } } }, 0); } } applyTranslation(node, translation) { if (!node || !translation) return; const parent = node.parentElement; if (!parent) return; this.translatedElements.add(node); parent.dataset.swiftTranslated = 'true'; if (CONFIG.displayMode === 'translated') { node.textContent = translation; } else if (CONFIG.displayMode === 'bilingual') { const span = document.createElement('span'); span.className = 'swift-bilingual'; span.style.cssText = ` display: inline-block; margin-left: 4px; color: #666; font-size: 0.9em; `; span.textContent = `[${translation}]`; if (node.nextSibling) { parent.insertBefore(span, node.nextSibling); } else { parent.appendChild(span); } } } restore() { document.querySelectorAll('[data-swift-translated]').forEach(el => { const spans = el.querySelectorAll('.swift-bilingual'); spans.forEach(span => span.remove()); delete el.dataset.swiftTranslated; }); this.translatedElements = new WeakSet(); } } // ========== 极简UI系统 ========== class MinimalUI { constructor() { this.isVisible = true; this.createUI(); } createUI() { // 注入CSS GM_addStyle(` .swift-ui { position: fixed; z-index: 10000; bottom: 20px; right: 20px; font-family: -apple-system, system-ui, sans-serif; } .swift-button { width: 44px; height: 44px; border-radius: 50%; background: #2563eb; color: white; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.2); transition: all 0.2s; } .swift-button:hover { background: #1d4ed8; transform: scale(1.05); } .swift-button:active { transform: scale(0.95); } .swift-panel { position: absolute; bottom: 50px; right: 0; width: 180px; background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 8px; display: none; } .swift-panel.show { display: block; } .swift-option { padding: 6px 8px; border: none; background: none; width: 100%; text-align: left; cursor: pointer; border-radius: 4px; font-size: 12px; } .swift-option:hover { background: #f3f4f6; } .swift-divider { height: 1px; background: #e5e7eb; margin: 4px 0; } `); // 创建主容器 this.container = document.createElement('div'); this.container.className = 'swift-ui'; // 创建按钮 this.button = document.createElement('button'); this.button.className = 'swift-button'; this.button.innerHTML = '译'; this.button.title = '迅捷翻译'; // 创建面板 this.panel = document.createElement('div'); this.panel.className = 'swift-panel'; this.panel.innerHTML = this.getPanelHTML(); // 组装 this.container.appendChild(this.button); this.container.appendChild(this.panel); document.body.appendChild(this.container); // 事件绑定 this.setupEvents(); } getPanelHTML() { return `
`; } setupEvents() { // 按钮点击 this.button.addEventListener('click', (e) => { e.stopPropagation(); this.panel.classList.toggle('show'); }); // 面板选项 this.panel.addEventListener('click', (e) => { const button = e.target.closest('.swift-option'); if (!button) return; const action = button.dataset.action; this.handleAction(action); this.panel.classList.remove('show'); }); // 点击外部关闭 document.addEventListener('click', (e) => { if (!this.container.contains(e.target)) { this.panel.classList.remove('show'); } }); } handleAction(action) { switch(action) { case 'translate': translator.translatePage(); break; case 'restore': translator.restore(); break; case 'toggle-mode': this.toggleDisplayMode(); break; case 'switch-engine': this.switchEngine(); break; } } toggleDisplayMode() { const modes = ['translated', 'bilingual', 'original']; const currentIndex = modes.indexOf(CONFIG.displayMode); CONFIG.displayMode = modes[(currentIndex + 1) % modes.length]; this.showToast(`模式: ${CONFIG.displayMode}`); } switchEngine() { const engines = Object.keys(TranslationEngine); const currentIndex = engines.indexOf(CONFIG.engine); CONFIG.engine = engines[(currentIndex + 1) % engines.length]; this.showToast(`引擎: ${TranslationEngine[CONFIG.engine].name}`); } showToast(message, duration = 2000) { const toast = document.createElement('div'); toast.textContent = message; toast.style.cssText = ` position: fixed; bottom: 80px; right: 20px; background: #1f2937; color: white; padding: 8px 16px; border-radius: 6px; font-size: 12px; z-index: 10001; animation: fadeInOut 2s; `; GM_addStyle(` @keyframes fadeInOut { 0%, 100% { opacity: 0; transform: translateY(10px); } 10%, 90% { opacity: 1; transform: translateY(0); } } `); document.body.appendChild(toast); setTimeout(() => toast.remove(), duration); } } // ========== 主翻译控制器 ========== class Translator { constructor() { this.domProcessor = new DOMProcessor(); this.cache = new FastCache(CONFIG.cacheSize); this.ui = new MinimalUI(); this.isTranslating = false; this.pendingTranslate = false; this.scrollTimer = null; this.init(); } init() { // 加载配置 this.loadConfig(); // 设置观察者 this.setupObservers(); // 自动翻译 if (CONFIG.autoTranslate) { setTimeout(() => this.translatePage(), 1000); } // 创建菜单命令 this.createMenuCommands(); } loadConfig() { // 从GM_getValue加载配置 Object.keys(CONFIG).forEach(key => { const saved = GM_getValue(key); if (saved !== undefined) { CONFIG[key] = saved; } }); } saveConfig() { Object.keys(CONFIG).forEach(key => { GM_setValue(key, CONFIG[key]); }); } setupObservers() { // DOM变化观察 this.observer = new MutationObserver((mutations) => { if (!CONFIG.autoTranslate || this.isTranslating) return; let hasChanges = false; for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === 1 && !this.domProcessor.isSkippable(node)) { hasChanges = true; break; } } if (hasChanges) break; } if (hasChanges) { this.scheduleTranslate(); } }); this.observer.observe(document.body, { childList: true, subtree: true }); // 滚动加载 window.addEventListener('scroll', () => { if (!CONFIG.autoTranslate || this.isTranslating) return; const scrollBottom = document.documentElement.scrollHeight - window.innerHeight - window.scrollY; if (scrollBottom < CONFIG.scrollThreshold) { this.scheduleTranslate(); } }, { passive: true }); } scheduleTranslate() { if (this.pendingTranslate) return; this.pendingTranslate = true; setTimeout(() => { this.pendingTranslate = false; this.translatePage(); }, CONFIG.translationDelay); } async translatePage(root = document.body) { if (this.isTranslating) { this.scheduleTranslate(); return; } this.isTranslating = true; try { const nodes = this.domProcessor.collectTextNodes(root); if (nodes.length === 0) { this.isTranslating = false; return; } // 显示进度 this.ui.showToast(`翻译中... (${nodes.length}条)`); // 分批处理避免阻塞 await this.domProcessor.translateNodes(nodes); this.ui.showToast(`翻译完成`); } catch (error) { console.error('[SwiftTranslate] 翻译失败:', error); } finally { this.isTranslating = false; } } restore() { this.domProcessor.restore(); this.ui.showToast('已恢复原文'); } createMenuCommands() { GM_registerMenuCommand('翻译页面', () => this.translatePage()); GM_registerMenuCommand('恢复原文', () => this.restore()); GM_registerMenuCommand('切换翻译引擎', () => this.ui.switchEngine()); GM_registerMenuCommand('切换显示模式', () => this.ui.toggleDisplayMode()); } } // ========== 全局缓存 ========== const cache = new FastCache(CONFIG.cacheSize); // ========== 启动应用 ========== // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => new Translator(), 100); }); } else { setTimeout(() => new Translator(), 100); } })();