// ==UserScript== // @name Yandex/微软自动翻译 // @namespace https://viayoo.com/ // @version 2.24.1 // @description Yandex/微软自动翻译小部件 | 自动翻译整个页面 | 简化界面 | 使用Ctrl+Shift+Y切换 | 增强多引擎支持 | 支持颜色自定义 | 网站专用引擎 | 立即翻译 // @author You // @run-at document-start // @match https://*/* // @match http://*/* // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect api-edge.cognitive.microsofttranslator.com // @connect edge.microsoft.com // @license MIT // ==/UserScript== (function() { 'use strict'; // 脚本配置 const CONFIG = { widgetId: 'ytWidget', storageKey: 'yandex_widget_enabled', whitelistKey: 'yandex_whitelist', translationEngineKey: 'translation_engine', translationLanguageKey: 'translation_language', translationModeKey: 'translation_mode', translationColorKey: 'translation_color', translationBgColorKey: 'translation_bg_color', siteSpecificEnginesKey: 'site_specific_engines', showTranslationIndicatorKey: 'show_translation_indicator', // 新增:控制提示显示 defaultEnabled: true, hotkey: 'Ctrl+Shift+Y', ENGINES: { YANDEX: 'yandex', MICROSOFT: 'microsoft' }, MODES: { REPLACE: 'replace', DUAL: 'dual' }, defaultEngine: 'microsoft', defaultTargetLanguage: 'zh-Hans', defaultMode: 'replace', defaultTextColor: '#0066cc', defaultBgColor: 'rgba(0,102,204,0.1)', russianDetectionThreshold: 0.2, LANGUAGES: { 'zh-Hans': {code: 'zh-Hans', name: '简体中文', icon: '🇨🇳'}, 'en': {code: 'en', name: '英语', icon: '🇺🇸'}, 'ru': {code: 'ru', name: '俄语', icon: '🇷🇺'}, 'ja': {code: 'ja', name: '日语', icon: '🇯🇵'}, 'ko': {code: 'ko', name: '韩语', icon: '🇰🇷'}, 'fr': {code: 'fr', name: '法语', icon: '🇫🇷'}, 'de': {code: 'de', name: '德语', icon: '🇩🇪'}, 'es': {code: 'es', name: '西班牙语', icon: '🇪🇸'}, 'pt': {code: 'pt', name: '葡萄牙语', icon: '🇵🇹'}, 'ar': {code: 'ar', name: '阿拉伯语', icon: '🇸🇦'}, 'it': {code: 'it', name: '意大利语', icon: '🇮🇹'}, 'vi': {code: 'vi', name: '越南语', icon: '🇻🇳'}, 'th': {code: 'th', name: '泰语', icon: '🇹🇭'} }, COLOR_PRESETS: [ {name: '默认蓝色', value: '#0066cc', bg: 'rgba(0,102,204,0.1)'}, {name: '绿色', value: '#00b894', bg: 'rgba(0,184,148,0.1)'}, {name: '红色', value: '#ff4757', bg: 'rgba(255,71,87,0.1)'}, {name: '紫色', value: '#6c5ce7', bg: 'rgba(108,92,231,0.1)'}, {name: '橙色', value: '#ff9f43', bg: 'rgba(255,159,67,0.1)'}, {name: '深色', value: '#2d3436', bg: 'rgba(45,52,54,0.1)'}, {name: '粉色', value: '#fd79a8', bg: 'rgba(253,121,168,0.1)'}, {name: '青色', value: '#00cec9', bg: 'rgba(0,206,201,0.1)'} ], russianDomains: ['.ru', '.рф', '.su', '.by', '.kz', '.ua', '.com.ru'], russianKeywords: [ 'программы', 'андроид', 'скачать', 'русский', 'приложение', 'софт', 'игры', 'утилиты', 'бесплатно', 'android', 'программу', 'для', 'на', 'телефон', 'смартфон', 'установить', 'загрузить' ], russianSiteOptimizations: { 'programmy-dlya-android.ru': { textExtraction: 'enhanced', mergeShortTexts: true, prioritizeLongTexts: true, forceRussianSource: true, excludeSelectors: ['code', '.code', 'pre', '.pre', 'textarea', 'input'], alwaysUseYandex: true }, 'rutracker.org': { textExtraction: 'simple', excludeSelectors: ['.post_body', '.sp-wrap', '.code'], alwaysUseYandex: true } }, MAX_TEXT_LENGTH: 5000, BATCH_SIZE: 50, AUTO_TRANSLATE_DELAY: 100, DOM_CHANGE_DELAY: 800, SCROLL_CHECK_DELAY: 2000, MAX_RETRY_COUNT: 2, YANDEX_CHECK_INTERVAL: 3000, YANDEX_TRANSLATE_TIMEOUT: 5000, TRANSLATING_INDICATOR_DURATION: 3000, // 统一提示显示时间:3秒 TRANSLATING_INDICATOR_FADE_OUT: 1000 }; // 脚本状态 let widgetEnabled = CONFIG.defaultEnabled; let isWhitelistedDomain = false; let mainMenuCommandId = null; let whitelistMenuCommandId = null; let engineMenuCommandId = null; let settingsMenuCommandId = null; let widgetContainer = null; let yandexScript = null; let isModalOpen = false; let cachedWhitelist = null; let currentDomain = ''; let isRussianSite = false; let isRussianContent = false; let currentRussianSiteConfig = null; let currentEngine = CONFIG.ENGINES.MICROSOFT; let targetLanguage = CONFIG.defaultTargetLanguage; let translationMode = CONFIG.defaultMode; let translationTextColor = CONFIG.defaultTextColor; let translationBgColor = CONFIG.defaultBgColor; let siteSpecificEngines = {}; let microsoftAuthToken = null; let microsoftTranslatorActive = false; let pageTranslating = false; let translationCache = new Map(); let autoTranslateTimeout = null; let originalTextMap = new Map(); let domObserver = null; let domChangeTimer = null; let isObservingDOM = false; let scrollCheckTimer = null; let lastScrollY = 0; let scrollCheckCount = 0; let continuousTranslationTimer = null; let translationRetryCount = 0; let lastTranslationTime = 0; let isUsingSiteSpecificEngine = false; let siteSpecificEngineName = ''; let russianContentRatio = 0; let russianDetectionSamples = 0; let russianDetectionTimer = null; let isLeavingRussianSite = false; let isPageLoaded = false; let pageLoadedTimer = null; let immediateTranslationScheduled = false; let menuTranslationFixApplied = false; let yandexTranslationActive = false; let yandexCheckInterval = null; let yandexTranslationAttempts = 0; let yandexMaxAttempts = 5; let currentTranslatingIndicator = null; let indicatorHideTimeout = null; let isTranslatingIndicatorVisible = false; let currentIndicatorText = ''; let showTranslationIndicator = true; // 新增:控制提示显示 // 获取当前域名 function getCurrentDomain() { return location.hostname; } // 检测俄语字符 function containsRussianText(text) { const russianRegex = /[\u0400-\u04FF]/; return russianRegex.test(text); } // 计算文本中俄语字符的比例 function getRussianCharacterRatio(text) { if (!text || text.length === 0) return 0; const russianChars = text.match(/[\u0400-\u04FF]/g) || []; return russianChars.length / text.length; } // 增强的俄语内容检测 function detectRussianByContent() { if (!document.body) return false; try { // 检查特定网站配置是否强制使用Yandex if (currentRussianSiteConfig && currentRussianSiteConfig.alwaysUseYandex) { console.log('检测到强制使用Yandex的俄语网站'); return true; } // 获取页面主要内容的文本 const mainContentSelectors = [ 'main', 'article', '.content', '#content', '.main-content', '.post-content', '.entry-content', '.text', '.article', 'body' ]; let contentText = ''; // 尝试从主要选择器获取内容 for (const selector of mainContentSelectors) { const element = document.querySelector(selector); if (element && element.textContent && element.textContent.length > 100) { contentText = element.textContent; break; } } // 如果没有找到足够的内容,使用body if (!contentText || contentText.length < 100) { contentText = document.body.textContent || ''; } const visibleText = contentText.replace(/\s+/g, ' ').trim(); // 如果文本太短,无法准确判断 if (visibleText.length < 50) return false; const russianRatio = getRussianCharacterRatio(visibleText); russianContentRatio = russianRatio; // 记录检测样本 russianDetectionSamples++; console.log(`俄语内容检测: 字符数 ${visibleText.length}, 俄语比例 ${(russianRatio * 100).toFixed(1)}%, 样本数 ${russianDetectionSamples}`); // 如果俄语字符比例超过阈值,认为是俄语内容 if (russianRatio >= CONFIG.russianDetectionThreshold) { console.log(`检测到俄语内容: 俄语字符比例 ${(russianRatio * 100).toFixed(1)}%`); // 检查是否有俄语关键词 const textLower = visibleText.toLowerCase(); let russianKeywordCount = 0; for (const keyword of CONFIG.russianKeywords) { if (textLower.includes(keyword.toLowerCase())) { russianKeywordCount++; console.log(`发现俄语关键词: ${keyword}`); } } // 如果有多个俄语关键词,确认是俄语网站 if (russianKeywordCount >= 2) { console.log(`检测到 ${russianKeywordCount} 个俄语关键词,确认为俄语网站`); return true; } // 如果俄语比例较高,也认为是俄语网站 if (russianRatio >= 0.25) { console.log(`俄语比例较高(${(russianRatio * 100).toFixed(1)}%),确认为俄语网站`); return true; } return false; } else { console.log(`俄语比例不足(${(russianRatio * 100).toFixed(1)}%),非俄语网站`); return false; } } catch (error) { console.error('检测俄语内容时出错:', error); return false; } } // 快速检测是否为俄语网站 function quickDetectRussianSite() { currentDomain = getCurrentDomain(); let isRussianDomain = false; for (const suffix of CONFIG.russianDomains) { if (currentDomain.includes(suffix)) { isRussianDomain = true; console.log(`检测到俄语域名后缀: ${suffix}`); break; } } const htmlLang = document.documentElement.lang; const isRussianLang = htmlLang && (htmlLang.startsWith('ru') || htmlLang.startsWith('RU')); if (isRussianLang) console.log('检测到HTML语言标签为俄语'); const metaLang = document.querySelector('meta[http-equiv="Content-Language"]'); const isRussianMeta = metaLang && (metaLang.content.startsWith('ru') || metaLang.content.startsWith('RU')); if (isRussianMeta) console.log('检测到meta语言标签为俄语'); return isRussianDomain || isRussianLang || isRussianMeta; } // 获取网站专用引擎配置 function getSiteSpecificEngines() { try { const enginesStr = GM_getValue(CONFIG.siteSpecificEnginesKey, '{}'); return JSON.parse(enginesStr); } catch (e) { console.error('读取网站专用引擎配置失败:', e); return {}; } } // 保存网站专用引擎配置 function saveSiteSpecificEngines(engines) { try { siteSpecificEngines = engines; GM_setValue(CONFIG.siteSpecificEnginesKey, JSON.stringify(engines)); } catch (e) { console.error('保存网站专用引擎配置失败:', e); } } // 检查当前网站是否有专用引擎 function checkSiteSpecificEngine() { const domain = getCurrentDomain(); isUsingSiteSpecificEngine = false; siteSpecificEngineName = ''; if (siteSpecificEngines[domain]) { isUsingSiteSpecificEngine = true; siteSpecificEngineName = siteSpecificEngines[domain]; console.log(`当前网站有专用引擎: ${siteSpecificEngineName}`); return siteSpecificEngines[domain]; } return null; } // 为当前网站设置专用引擎 function setSiteSpecificEngine(engine) { const domain = getCurrentDomain(); siteSpecificEngines[domain] = engine; saveSiteSpecificEngines(siteSpecificEngines); isUsingSiteSpecificEngine = true; siteSpecificEngineName = engine; console.log(`为网站 ${domain} 设置专用引擎: ${engine}`); } // 清除当前网站的专用引擎 function clearSiteSpecificEngine() { const domain = getCurrentDomain(); if (siteSpecificEngines[domain]) { delete siteSpecificEngines[domain]; saveSiteSpecificEngines(siteSpecificEngines); isUsingSiteSpecificEngine = false; siteSpecificEngineName = ''; console.log(`清除网站 ${domain} 的专用引擎`); } } // 获取当前应使用的引擎(考虑网站专用引擎) function getCurrentEngine() { const siteEngine = checkSiteSpecificEngine(); if (siteEngine) { return siteEngine; } return currentEngine; } // 智能引擎检测 function smartEngineDetection() { const domain = getCurrentDomain(); // 清除之前的检测计时器 if (russianDetectionTimer) { clearTimeout(russianDetectionTimer); russianDetectionTimer = null; } // 延迟检测,确保页面内容加载完成 russianDetectionTimer = setTimeout(() => { isRussianContent = detectRussianByContent(); console.log(`俄语内容检测结果: ${isRussianContent ? '是俄语网站' : '非俄语网站'}, 比例: ${(russianContentRatio * 100).toFixed(1)}%`); // 更新菜单命令 registerMenuCommands(); }, 1500); } // 获取俄语网站配置 function getRussianSiteConfig() { if (!isRussianSite) return null; const domain = getCurrentDomain(); for (const [sitePattern, config] of Object.entries(CONFIG.russianSiteOptimizations)) { if (domain.includes(sitePattern)) { console.log(`找到俄语网站优化配置: ${sitePattern}`); return config; } } // 返回默认的俄语网站配置 return { textExtraction: 'enhanced', mergeShortTexts: true, prioritizeLongTexts: true, forceRussianSource: true, excludeSelectors: ['code', '.code', 'pre', '.pre', 'textarea', 'input', '.mce-content-body'] }; } // 获取白名单 function getWhitelist() { if (cachedWhitelist !== null) { return cachedWhitelist; } const whitelistStr = GM_getValue(CONFIG.whitelistKey, '[]'); try { cachedWhitelist = JSON.parse(whitelistStr); } catch (e) { cachedWhitelist = []; } return cachedWhitelist; } // 保存白名单 function saveWhitelist(whitelist) { cachedWhitelist = whitelist; GM_setValue(CONFIG.whitelistKey, JSON.stringify(whitelist)); } // 检查当前域名是否在白名单中 function checkWhitelist() { const currentDomain = getCurrentDomain(); const whitelist = getWhitelist(); isWhitelistedDomain = whitelist.includes(currentDomain); return isWhitelistedDomain; } // 切换当前域名的白名单状态 function toggleWhitelist() { const currentDomain = getCurrentDomain(); let whitelist = getWhitelist(); if (whitelist.includes(currentDomain)) { whitelist = whitelist.filter(domain => domain !== currentDomain); showStatusMessage(`已从白名单中移除: ${currentDomain}`, 2000); isWhitelistedDomain = false; } else { whitelist.push(currentDomain); showStatusMessage(`已添加到白名单: ${currentDomain}`, 2000); isWhitelistedDomain = true; } saveWhitelist(whitelist); // 立即更新翻译状态 if (widgetEnabled || isWhitelistedDomain) { console.log('白名单变更,立即更新翻译状态'); updateWidgetBasedOnSettings(); // 如果是微软翻译且已启用,立即开始翻译 const engineToUse = getCurrentEngine(); if (engineToUse === CONFIG.ENGINES.MICROSOFT && microsoftTranslatorActive) { console.log('白名单开启,立即开始翻译'); scheduleImmediateTranslation(); } } else { deactivateAllTranslators(); } registerMenuCommands(); } // 立即翻译调度 function scheduleImmediateTranslation() { if (immediateTranslationScheduled) return; immediateTranslationScheduled = true; setTimeout(() => { immediateTranslationScheduled = false; if (microsoftTranslatorActive && !pageTranslating) { console.log('调度立即翻译'); translateWholePage(); } }, 100); } // 创建防护层,防止翻译脚本自身内容 function createTranslationProtectionLayer() { const style = document.createElement('style'); style.id = 'yandex-translation-protection'; style.setAttribute('data-no-translate', 'true'); style.setAttribute('translate', 'no'); style.className = 'no-translate yandex-script-element'; style.textContent = ` /* 强制保护脚本创建的UI元素 */ body * { transition: none !important; } /* 防护引擎选择器中的文本 */ .engine-btn, .engine-btn *, .translation-engine-option, .translation-engine-option * { translate: no !important; -webkit-translate: no !important; font-language-override: normal !important; unicode-bidi: isolate !important; } /* 保护特定关键词不被翻译 */ .protect-yandex::before { content: "Yandex"; speak: none; } /* 为所有脚本元素添加额外防护 */ .yandex-translation-protected { unicode-bidi: isolate !important; font-language-override: normal !important; } `; document.head.appendChild(style); console.log('已创建翻译防护层'); } // 初始化函数 function init() { console.log('Yandex/微软自动翻译脚本开始初始化...'); const savedState = GM_getValue(CONFIG.storageKey, CONFIG.defaultEnabled); widgetEnabled = savedState; // 读取用户设置 currentEngine = GM_getValue(CONFIG.translationEngineKey, CONFIG.defaultEngine); targetLanguage = GM_getValue(CONFIG.translationLanguageKey, CONFIG.defaultTargetLanguage); translationMode = GM_getValue(CONFIG.translationModeKey, CONFIG.defaultMode); translationTextColor = GM_getValue(CONFIG.translationColorKey, CONFIG.defaultTextColor); translationBgColor = GM_getValue(CONFIG.translationBgColorKey, CONFIG.defaultBgColor); // 新增:读取翻译提示显示设置 showTranslationIndicator = GM_getValue(CONFIG.showTranslationIndicatorKey, true); // 读取网站专用引擎配置 siteSpecificEngines = getSiteSpecificEngines(); // 检测俄语网站(基于域名) isRussianSite = quickDetectRussianSite(); currentRussianSiteConfig = getRussianSiteConfig(); checkWhitelist(); currentDomain = getCurrentDomain(); // 检查当前网站是否有专用引擎 checkSiteSpecificEngine(); // 重置检测计数器 russianDetectionSamples = 0; russianContentRatio = 0; // 添加样式 addStyles(); updateColorStyles(); // 创建翻译防护层 createTranslationProtectionLayer(); // 设置页面加载完成标记 if (document.readyState === 'complete' || document.readyState === 'interactive') { isPageLoaded = true; pageLoaded(); } else { window.addEventListener('load', function() { isPageLoaded = true; pageLoaded(); }, { once: true }); } // 设置快捷键 setupHotkey(); // 延迟检测俄语内容 setTimeout(() => { smartEngineDetection(); }, 1000); // 立即注册菜单命令(不等待页面加载) registerMenuCommands(); // 监听页面卸载事件 window.addEventListener('beforeunload', function() { console.log('页面卸载'); if (russianDetectionTimer) { clearTimeout(russianDetectionTimer); russianDetectionTimer = null; } if (pageLoadedTimer) { clearTimeout(pageLoadedTimer); pageLoadedTimer = null; } // 停止Yandex检查 if (yandexCheckInterval) { clearInterval(yandexCheckInterval); yandexCheckInterval = null; } // 清除状态提示定时器 if (indicatorHideTimeout) { clearTimeout(indicatorHideTimeout); indicatorHideTimeout = null; } }); } // 页面加载完成处理 function pageLoaded() { console.log('页面加载完成,准备开始翻译'); // 再次注册菜单命令确保显示 setTimeout(() => { registerMenuCommands(); }, 500); // 根据当前设置立即启动翻译 const shouldTranslate = isWhitelistedDomain || widgetEnabled; if (shouldTranslate) { console.log('页面加载完成,立即启动翻译'); updateWidgetBasedOnSettings(); // 显示首次运行提示 if (GM_getValue('firstRun', true)) { setTimeout(function() { const engineToUse = getCurrentEngine(); const engineName = engineToUse === CONFIG.ENGINES.MICROSOFT ? '微软' : 'Yandex'; const languageName = CONFIG.LANGUAGES[targetLanguage]?.name || targetLanguage; let message = `${engineName}自动翻译已启动,目标语言: ${languageName}`; if (isUsingSiteSpecificEngine) { message += ` (网站专用引擎)`; } message += `。按Ctrl+Shift+Y关闭`; showStatusMessage(message, 2500); GM_setValue('firstRun', false); }, 1500); } } } // 添加CSS样式 - 修改翻译状态提示框样式为缩小版 function addStyles() { const css = ` /* 微软翻译文本样式 - 修复文字颜色问题 */ .microsoft-translated { position: relative; border-radius: 3px; transition: all 0.2s; } .microsoft-translated.replace-mode { color: var(--translation-text-color, #0066cc) !important; font-weight: 500; } .microsoft-translated.replace-mode:hover { background-color: var(--translation-bg-color, rgba(0,102,204,0.1)) !important; cursor: pointer; } /* 移除"译"字角标 */ .microsoft-translated.replace-mode::after { display: none !important; } .microsoft-translated.dual-mode { display: inline-flex; flex-direction: column; } .microsoft-translated.dual-mode .original-text { color: #666; font-size: 0.9em; text-decoration: line-through; opacity: 0.7; } .microsoft-translated.dual-mode .translated-text { color: var(--translation-text-color, #0066cc) !important; font-weight: 500; } /* Yandex翻译小部件样式 */ .yandex-translate-widget { position: fixed; bottom: 20px; right: 20px; z-index: 10000; } /* 翻译状态指示器 - 缩小版,与文字一样大 */ .translating-indicator { position: fixed; bottom: 20px; right: 20px; background: rgba(0, 0, 0, 0.7); /* 半透明背景 */ color: #00ffcc; /* 青色文字 */ padding: 4px 8px; /* 减小内边距 */ border-radius: 8px; /* 减小圆角 */ z-index: 2147483647; font-family: Arial, sans-serif; font-size: 12px; /* 减小字体大小 */ display: flex; align-items: center; gap: 6px; /* 减小间隙 */ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); /* 减小阴影 */ backdrop-filter: blur(10px); /* 毛玻璃效果 */ border: 1px solid rgba(0, 255, 204, 0.3); /* 青色边框 */ min-width: 100px; /* 减小最小宽度 */ pointer-events: none; height: auto; box-sizing: border-box; opacity: 0.9; transition: opacity 0.3s, background 0.3s, color 0.3s; } .translating-indicator .spinner { width: 12px; /* 减小spinner大小 */ height: 12px; /* 减小spinner大小 */ border: 2px solid rgba(0, 255, 204, 0.5); border-top: 2px solid #00ffcc; border-radius: 50%; animation: spin 1.2s linear infinite; flex-shrink: 0; } .translating-indicator span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex-shrink: 1; font-weight: 600; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); font-size: 12px; /* 确保文字大小与指示器一致 */ } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 状态消息 */ #yandex-status-msg { position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.9); color: white; padding: 12px 20px; border-radius: 10px; z-index: 2147483646; font-family: Arial, sans-serif; font-size: 14px; transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; box-shadow: 0 4px 20px rgba(0,0,0,0.3); backdrop-filter: blur(5px); border: 1px solid rgba(255, 255, 255, 0.1); opacity: 1; transform: translateY(0); max-width: 300px; word-wrap: break-word; } #yandex-status-msg.fade-out { opacity: 0; transform: translateY(-20px); } /* 设置项样式 */ .setting-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding: 8px; background-color: #f9f9f9; border-radius: 6px; } .setting-label { flex: 1; } .setting-title { font-weight: bold; color: #333; margin-bottom: 2px; font-size: 13px; } .setting-description { color: #666; font-size: 11px; line-height: 1.3; } /* 开关样式 */ .switch { position: relative; display: inline-block; width: 45px; height: 22px; flex-shrink: 0; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 22px; } .slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #0066cc; } input:checked + .slider:before { transform: translateX(23px); } input:focus + .slider { box-shadow: 0 0 1px #0066cc; } /* 网站专用引擎标签 */ .site-engine-badge { display: inline-block; background-color: #00b894; color: white; font-size: 9px; padding: 1px 4px; border-radius: 8px; margin-left: 5px; font-weight: bold; vertical-align: middle; } /* 引擎选择按钮 */ .engine-btn { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; font-size: 12px; display: flex; align-items: center; gap: 5px; transition: all 0.2s; flex: 1; justify-content: center; } .engine-btn.active { border-color: #0066cc; background-color: #f0f7ff; } .engine-btn:hover:not(.active) { border-color: #999; background-color: #f9f9f9; } /* 紧凑的设置项 */ .compact-setting { margin-bottom: 10px; padding: 8px; background-color: #f8f9fa; border-radius: 5px; border-left: 3px solid #6c5ce7; } .compact-setting-title { font-weight: bold; color: #333; margin-bottom: 5px; font-size: 13px; } .compact-setting-desc { color: #666; font-size: 11px; margin-bottom: 8px; line-height: 1.3; } /* 紧凑按钮 */ .compact-btn { padding: 5px 8px; font-size: 11px; border-radius: 4px; border: 1px solid #ddd; background: white; cursor: pointer; transition: all 0.2s; } .compact-btn:hover { border-color: #999; background-color: #f5f5f5; } .compact-btn-primary { background-color: #0066cc; color: white; border-color: #0066cc; } .compact-btn-primary:hover { background-color: #0052a3; } /* 紧凑输入框 */ .compact-input { padding: 5px 8px; font-size: 12px; border: 1px solid #ddd; border-radius: 4px; width: 100%; box-sizing: border-box; } /* 增强防护:确保所有脚本相关元素不被翻译 */ [data-no-translate], .no-translate, .yandex-no-translate, .yandex-translate-widget, .yandex-translate-widget *, .microsoft-translated, .microsoft-translated *, .translating-indicator, .translating-indicator *, #yandex-status-msg, #yandex-status-msg *, .translation-settings-modal, .translation-settings-modal *, .tm-script-menu, .tm-script-menu *, .vm-script-menu, .vm-script-menu *, .gm-script-menu, .gm-script-menu *, [class*="userscript"], [class*="userscript"] *, [id*="userscript"], [id*="userscript"] *, .yandex-script-element, .yandex-script-element * { translate: no !important; -webkit-translate: no !important; -moz-translate: no !important; -ms-translate: no !important; -o-translate: no !important; font-language-override: normal !important; text-orientation: mixed !important; } /* 防止特定文本被翻译 */ .engine-btn, .engine-btn *, .site-engine-badge, .site-engine-badge * { translate: no !important; font-language-override: normal !important; } /* 确保"Yandex"文本不被翻译 */ .yandex-protected-text, .yandex-protected-text * { translate: no !important; -webkit-translate: no !important; font-language-override: normal !important; unicode-bidi: isolate !important; } /* 为特定元素添加额外的防护 */ [data-protect-translation="true"], [data-protect-translation="true"] * { translate: no !important; -webkit-translate: no !important; font-language-override: normal !important; } `; GM_addStyle(css); } // 更新颜色样式 - 修复文字颜色功能 function updateColorStyles() { const oldStyle = document.querySelector('#translation-color-styles'); if (oldStyle) oldStyle.remove(); const style = document.createElement('style'); style.id = 'translation-color-styles'; style.textContent = ` :root { --translation-text-color: ${translationTextColor}; --translation-bg-color: ${translationBgColor}; } /* 直接应用颜色到翻译元素 */ .microsoft-translated.replace-mode { color: ${translationTextColor} !important; } .microsoft-translated.replace-mode:hover { background-color: ${translationBgColor} !important; } .microsoft-translated.dual-mode .translated-text { color: ${translationTextColor} !important; } /* 更新现有翻译元素的颜色 */ .microsoft-translated.replace-mode, .microsoft-translated.dual-mode .translated-text { color: ${translationTextColor} !important; } `; document.head.appendChild(style); console.log('已更新翻译颜色样式:', translationTextColor); } // 根据设置更新翻译功能 function updateWidgetBasedOnSettings() { const engineToUse = getCurrentEngine(); console.log('更新翻译设置,当前状态:', { widgetEnabled, isWhitelistedDomain, mainEngine: currentEngine, siteSpecificEngine: engineToUse !== currentEngine ? engineToUse : '无', isUsingSiteSpecificEngine, isRussianContent, russianContentRatio: (russianContentRatio * 100).toFixed(1) + '%' }); if (autoTranslateTimeout) { clearTimeout(autoTranslateTimeout); autoTranslateTimeout = null; } stopAllObservations(); // 停止Yandex检查 if (yandexCheckInterval) { clearInterval(yandexCheckInterval); yandexCheckInterval = null; } const shouldShowWidget = isWhitelistedDomain || widgetEnabled; if (shouldShowWidget) { if (engineToUse === CONFIG.ENGINES.YANDEX) { console.log('启用Yandex翻译器'); initYandexTranslator(); } else if (engineToUse === CONFIG.ENGINES.MICROSOFT) { console.log('启用微软翻译器'); initMicrosoftTranslator(); } } else { console.log('翻译已禁用'); deactivateAllTranslators(); } // 更新菜单命令 registerMenuCommands(); } // 初始化Yandex翻译器 - 增强版,确保持续工作 function initYandexTranslator() { if (microsoftTranslatorActive) { deactivateMicrosoftTranslator(); } if (!widgetContainer) { createYandexWidget(); } // 启动Yandex翻译状态检查 startYandexTranslationCheck(); yandexTranslationActive = true; yandexTranslationAttempts = 0; // Yandex翻译时也显示状态提示(根据设置) if (showTranslationIndicator) { showTranslatingIndicator('Yandex翻译已启动'); } } // 启动Yandex翻译状态检查 function startYandexTranslationCheck() { if (yandexCheckInterval) { clearInterval(yandexCheckInterval); } console.log('启动Yandex翻译状态检查'); // 立即检查一次 checkAndActivateYandexTranslation(); // 设置定时检查 yandexCheckInterval = setInterval(() => { checkAndActivateYandexTranslation(); }, CONFIG.YANDEX_CHECK_INTERVAL); } // 检查和激活Yandex翻译 function checkAndActivateYandexTranslation() { const engineToUse = getCurrentEngine(); const shouldShowWidget = isWhitelistedDomain || widgetEnabled; // 如果当前不应该显示翻译,或者不是Yandex引擎,则停止检查 if (!shouldShowWidget || engineToUse !== CONFIG.ENGINES.YANDEX) { if (yandexCheckInterval) { clearInterval(yandexCheckInterval); yandexCheckInterval = null; } return; } // 检查Yandex翻译是否正常工作 if (!isYandexTranslatorActive()) { console.log('Yandex翻译器未激活,尝试激活...'); yandexTranslationAttempts++; if (yandexTranslationAttempts > yandexMaxAttempts) { console.log('Yandex翻译激活尝试次数过多,停止尝试'); if (yandexCheckInterval) { clearInterval(yandexCheckInterval); yandexCheckInterval = null; } return; } // 尝试重新激活Yandex翻译 reactivateYandexTranslator(); } else { // 重置尝试次数 yandexTranslationAttempts = 0; } } // 检查Yandex翻译是否激活 function isYandexTranslatorActive() { if (!widgetContainer) return false; // 检查是否在页面上看到翻译相关的元素 // Yandex翻译会在页面上添加特定的类或属性 const yandexElements = document.querySelectorAll('[data-translate], .translated-ltr, .translated-rtl, .goog-te-banner'); // 检查页面文本是否包含翻译 const pageText = document.body ? document.body.textContent || '' : ''; const hasTranslatedContent = pageText.includes('翻译') || pageText.length > 1000; // 假设有内容就是翻译了 return yandexElements.length > 0 || hasTranslatedContent; } // 重新激活Yandex翻译器 function reactivateYandexTranslator() { console.log('重新激活Yandex翻译器'); // 如果Yandex小部件不存在,重新创建 if (!widgetContainer || !document.body.contains(widgetContainer)) { console.log('Yandex小部件不存在,重新创建'); removeYandexWidget(); createYandexWidget(); return; } // 尝试点击翻译按钮(如果存在) const translateButtons = document.querySelectorAll('[data-lang]'); if (translateButtons.length > 0) { console.log('找到翻译按钮,尝试点击'); for (const button of translateButtons) { if (button.textContent.includes('翻译') || button.getAttribute('data-lang') === 'zh') { button.click(); showStatusMessage('重新激活Yandex翻译', 2000); return; } } } // 如果页面上有Yandex翻译框架,尝试触发翻译 const yandexIframe = document.querySelector('iframe[src*="translate.yandex"]'); if (yandexIframe) { console.log('找到Yandex翻译iframe'); // 重新加载iframe yandexIframe.src = yandexIframe.src; showStatusMessage('重新加载Yandex翻译', 2000); } } // 初始化微软翻译器 async function initMicrosoftTranslator() { if (widgetContainer) { removeYandexWidget(); } // 停止Yandex检查 if (yandexCheckInterval) { clearInterval(yandexCheckInterval); yandexCheckInterval = null; } await setupMicrosoftTranslator(); if (widgetEnabled || isWhitelistedDomain) { console.log('微软翻译器就绪,立即开始翻译'); // 显示翻译状态提示(根据设置) if (showTranslationIndicator) { showTranslatingIndicator('正在翻译页面...'); } // 立即开始翻译,减少延迟 if (isPageLoaded) { setTimeout(() => { translateWholePage(); }, 50); } else { // 如果页面还没加载完成,等待一小会儿再开始 autoTranslateTimeout = setTimeout(() => { translateWholePage(); }, CONFIG.AUTO_TRANSLATE_DELAY); } } startDOMObservation(); startContinuousTranslation(); } // 停用所有翻译器 function deactivateAllTranslators() { if (autoTranslateTimeout) { clearTimeout(autoTranslateTimeout); autoTranslateTimeout = null; } stopAllObservations(); removeYandexWidget(); deactivateMicrosoftTranslator(); // 停止Yandex检查 if (yandexCheckInterval) { clearInterval(yandexCheckInterval); yandexCheckInterval = null; } // 隐藏翻译状态提示 hideTranslatingIndicator(); yandexTranslationActive = false; yandexTranslationAttempts = 0; console.log('所有翻译器已停用'); } // 停止所有观察器 function stopAllObservations() { stopDOMObservation(); stopScrollCheck(); stopContinuousTranslation(); } // 注册菜单命令 - 修复版,确保菜单总是显示 function registerMenuCommands() { // 先清除之前的菜单命令 unregisterMenuCommands(); const engineToUse = getCurrentEngine(); const engineName = engineToUse === CONFIG.ENGINES.MICROSOFT ? '微软' : 'Yandex'; let mainMenuText = widgetEnabled ? '❌ 关闭自动翻译' : '✅ 开启自动翻译'; mainMenuText += ` (${engineName})`; if (isRussianContent) mainMenuText += ' 🇷🇺'; if (isUsingSiteSpecificEngine) mainMenuText += ' 🌐'; try { mainMenuCommandId = GM_registerMenuCommand(mainMenuText, toggleWidget, 't'); console.log('主菜单命令注册成功:', mainMenuText); } catch (e) { console.error('主菜单命令注册失败:', e); } const engineText = engineToUse === CONFIG.ENGINES.MICROSOFT ? '🔵 切换到Yandex翻译' : '🔴 切换到微软翻译'; try { engineMenuCommandId = GM_registerMenuCommand(engineText, toggleTranslationEngine, 'e'); console.log('引擎菜单命令注册成功:', engineText); } catch (e) { console.error('引擎菜单命令注册失败:', e); } const whitelistText = isWhitelistedDomain ? `⭐ 从白名单中移除: ${currentDomain}` : `☆ 添加到白名单: ${currentDomain}`; try { whitelistMenuCommandId = GM_registerMenuCommand(whitelistText, toggleWhitelist, 'a'); console.log('白名单菜单命令注册成功:', whitelistText); } catch (e) { console.error('白名单菜单命令注册失败:', e); } try { settingsMenuCommandId = GM_registerMenuCommand('⚙️ 翻译设置', showSettings, 's'); console.log('设置菜单命令注册成功'); } catch (e) { console.error('设置菜单命令注册失败:', e); } console.log('菜单命令注册完成'); } // 注销菜单命令 function unregisterMenuCommands() { try { if (mainMenuCommandId !== null) { GM_unregisterMenuCommand(mainMenuCommandId); mainMenuCommandId = null; } if (whitelistMenuCommandId !== null) { GM_unregisterMenuCommand(whitelistMenuCommandId); whitelistMenuCommandId = null; } if (engineMenuCommandId !== null) { GM_unregisterMenuCommand(engineMenuCommandId); engineMenuCommandId = null; } if (settingsMenuCommandId !== null) { GM_unregisterMenuCommand(settingsMenuCommandId); settingsMenuCommandId = null; } } catch (e) { console.error('注销菜单命令失败:', e); } } // 显示设置窗口 function showSettings() { isModalOpen = true; // 获取当前模态框的滚动位置(如果存在) const existingContent = document.querySelector('.translation-settings-content'); let scrollTopToRestore = 0; if (existingContent) { scrollTopToRestore = existingContent.scrollTop; // 移除现有的模态框 const existingModal = existingContent.closest('.translation-settings-modal'); if (existingModal) { existingModal.remove(); } } const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 2147483647; display: flex; justify-content: center; align-items: center; font-family: Arial, sans-serif; `; modal.setAttribute('data-no-translate', 'true'); modal.setAttribute('translate', 'no'); modal.setAttribute('data-protect-translation', 'true'); modal.className = 'no-translate yandex-no-translate translation-settings-modal yandex-script-element'; const modalContent = document.createElement('div'); modalContent.style.cssText = ` background-color: white; padding: 20px; border-radius: 10px; max-width: 480px; width: 90%; max-height: 85vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); font-size: 13px; `; modalContent.setAttribute('data-no-translate', 'true'); modalContent.setAttribute('translate', 'no'); modalContent.setAttribute('data-protect-translation', 'true'); modalContent.className = 'no-translate yandex-no-translate translation-settings-content yandex-script-element'; const title = document.createElement('h3'); title.textContent = '翻译设置'; title.style.cssText = ` margin-top: 0; margin-bottom: 15px; color: #333; text-align: center; font-size: 16px; `; title.setAttribute('data-no-translate', 'true'); title.setAttribute('translate', 'no'); title.setAttribute('data-protect-translation', 'true'); title.className = 'no-translate'; modalContent.appendChild(title); // 当前状态显示 const statusInfo = document.createElement('div'); statusInfo.style.cssText = ` margin-bottom: 15px; padding: 10px; background-color: #f0f7ff; border-radius: 6px; border-left: 3px solid #0066cc; font-size: 12px; `; statusInfo.setAttribute('data-no-translate', 'true'); statusInfo.setAttribute('translate', 'no'); statusInfo.setAttribute('data-protect-translation', 'true'); statusInfo.className = 'no-translate'; const engineToUse = getCurrentEngine(); const engineName = engineToUse === CONFIG.ENGINES.MICROSOFT ? '微软翻译' : 'Yandex翻译'; let statusHTML = `
当前状态
${engineName}`; if (isUsingSiteSpecificEngine) { statusHTML += ` 网站专用`; } statusHTML += `
`; if (isRussianSite) { statusHTML += `
${isRussianContent ? '检测到俄语内容 🇷🇺' : '俄语域名'}
`; } statusHTML += `
默认引擎: ${currentEngine === CONFIG.ENGINES.MICROSOFT ? '微软翻译' : 'Yandex翻译'}
`; if (isRussianContent && engineToUse === CONFIG.ENGINES.MICROSOFT) { statusHTML += `
⚠️ 建议使用Yandex翻译
`; } if (engineToUse === CONFIG.ENGINES.YANDEX) { statusHTML += `
✓ Yandex翻译将持续工作
`; } statusInfo.innerHTML = statusHTML; modalContent.appendChild(statusInfo); // 新增:翻译提示开关设置 const indicatorSection = document.createElement('div'); indicatorSection.className = 'compact-setting'; indicatorSection.setAttribute('data-no-translate', 'true'); indicatorSection.setAttribute('translate', 'no'); indicatorSection.setAttribute('data-protect-translation', 'true'); indicatorSection.className += ' no-translate'; const indicatorTitle = document.createElement('div'); indicatorTitle.className = 'compact-setting-title'; indicatorTitle.textContent = '翻译状态提示'; indicatorTitle.setAttribute('data-no-translate', 'true'); indicatorTitle.setAttribute('translate', 'no'); indicatorTitle.setAttribute('data-protect-translation', 'true'); indicatorSection.appendChild(indicatorTitle); const indicatorDesc = document.createElement('div'); indicatorDesc.className = 'compact-setting-desc'; indicatorDesc.textContent = '控制是否显示"正在翻译页面..."等翻译状态提示'; indicatorDesc.setAttribute('data-no-translate', 'true'); indicatorDesc.setAttribute('translate', 'no'); indicatorDesc.setAttribute('data-protect-translation', 'true'); indicatorSection.appendChild(indicatorDesc); const indicatorSwitch = document.createElement('div'); indicatorSwitch.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 8px; background-color: #f9f9f9; border-radius: 6px; `; const indicatorLabel = document.createElement('div'); indicatorLabel.style.cssText = ` flex: 1; `; const indicatorLabelTitle = document.createElement('div'); indicatorLabelTitle.className = 'setting-title'; indicatorLabelTitle.textContent = '显示翻译状态提示'; indicatorLabelTitle.setAttribute('data-no-translate', 'true'); indicatorLabelTitle.setAttribute('translate', 'no'); indicatorLabelTitle.setAttribute('data-protect-translation', 'true'); const indicatorLabelDesc = document.createElement('div'); indicatorLabelDesc.className = 'setting-description'; indicatorLabelDesc.textContent = '开启后会在翻译时显示"正在翻译页面..."和"翻译完成"等提示'; indicatorLabelDesc.setAttribute('data-no-translate', 'true'); indicatorLabelDesc.setAttribute('translate', 'no'); indicatorLabelDesc.setAttribute('data-protect-translation', 'true'); indicatorLabel.appendChild(indicatorLabelTitle); indicatorLabel.appendChild(indicatorLabelDesc); const switchContainer = document.createElement('label'); switchContainer.className = 'switch'; const switchInput = document.createElement('input'); switchInput.type = 'checkbox'; switchInput.checked = showTranslationIndicator; switchInput.setAttribute('data-no-translate', 'true'); switchInput.setAttribute('translate', 'no'); switchInput.setAttribute('data-protect-translation', 'true'); const switchSlider = document.createElement('span'); switchSlider.className = 'slider'; switchSlider.setAttribute('data-no-translate', 'true'); switchSlider.setAttribute('translate', 'no'); switchSlider.setAttribute('data-protect-translation', 'true'); switchContainer.appendChild(switchInput); switchContainer.appendChild(switchSlider); switchInput.addEventListener('change', function() { showTranslationIndicator = this.checked; GM_setValue(CONFIG.showTranslationIndicatorKey, showTranslationIndicator); // 如果关闭了提示,立即隐藏当前提示 if (!showTranslationIndicator) { hideTranslatingIndicator(); } showStatusMessage(`翻译状态提示 ${showTranslationIndicator ? '已开启' : '已关闭'}`, 2000); }); indicatorSwitch.appendChild(indicatorLabel); indicatorSwitch.appendChild(switchContainer); indicatorSection.appendChild(indicatorSwitch); modalContent.appendChild(indicatorSection); // 网站专用引擎设置 const siteEngineSection = document.createElement('div'); siteEngineSection.className = 'compact-setting'; siteEngineSection.setAttribute('data-no-translate', 'true'); siteEngineSection.setAttribute('translate', 'no'); siteEngineSection.setAttribute('data-protect-translation', 'true'); siteEngineSection.className += ' no-translate'; const siteEngineTitle = document.createElement('div'); siteEngineTitle.className = 'compact-setting-title'; siteEngineTitle.textContent = '网站专用引擎'; siteEngineTitle.setAttribute('data-no-translate', 'true'); siteEngineTitle.setAttribute('translate', 'no'); siteEngineTitle.setAttribute('data-protect-translation', 'true'); siteEngineSection.appendChild(siteEngineTitle); const siteEngineDesc = document.createElement('div'); siteEngineDesc.className = 'compact-setting-desc'; siteEngineDesc.textContent = '为当前网站设置专用翻译引擎,该设置会覆盖默认引擎'; siteEngineDesc.setAttribute('data-no-translate', 'true'); siteEngineDesc.setAttribute('translate', 'no'); siteEngineDesc.setAttribute('data-protect-translation', 'true'); siteEngineSection.appendChild(siteEngineDesc); // 当前网站状态 const currentDomain = getCurrentDomain(); const isSiteHasEngine = isUsingSiteSpecificEngine; const currentSiteInfo = document.createElement('div'); currentSiteInfo.style.cssText = ` margin-bottom: 8px; padding: 6px; background-color: ${isSiteHasEngine ? '#e8f5e9' : '#f5f5f5'}; border-radius: 4px; font-size: 12px; `; currentSiteInfo.setAttribute('data-no-translate', 'true'); currentSiteInfo.setAttribute('translate', 'no'); currentSiteInfo.setAttribute('data-protect-translation', 'true'); currentSiteInfo.className = 'no-translate'; currentSiteInfo.innerHTML = `
${currentDomain}
专用引擎: ${isSiteHasEngine ? (siteSpecificEngineName === CONFIG.ENGINES.MICROSOFT ? '微软翻译' : 'Yandex翻译') : '无'}
`; siteEngineSection.appendChild(currentSiteInfo); // 引擎选择按钮 const engineSelection = document.createElement('div'); engineSelection.style.cssText = ` display: flex; gap: 6px; margin-bottom: 8px; `; // 微软引擎按钮 const microsoftBtn = document.createElement('button'); microsoftBtn.className = 'engine-btn'; if (isSiteHasEngine && siteSpecificEngineName === CONFIG.ENGINES.MICROSOFT) { microsoftBtn.classList.add('active'); } microsoftBtn.innerHTML = ` 🔵 微软 `; microsoftBtn.setAttribute('data-no-translate', 'true'); microsoftBtn.setAttribute('translate', 'no'); microsoftBtn.setAttribute('data-protect-translation', 'true'); microsoftBtn.className += ' no-translate yandex-script-element'; microsoftBtn.addEventListener('click', function() { setSiteSpecificEngine(CONFIG.ENGINES.MICROSOFT); showStatusMessage('已设置当前网站专用引擎: 微软翻译', 2000); showSettings(); // 直接重新打开,不关闭 }); // Yandex引擎按钮 - 增强防护 const yandexBtn = document.createElement('button'); yandexBtn.className = 'engine-btn'; if (isSiteHasEngine && siteSpecificEngineName === CONFIG.ENGINES.YANDEX) { yandexBtn.classList.add('active'); } // 使用span元素包裹"Yandex"文本,并添加防护 yandexBtn.innerHTML = ` 🔴 Yandex `; // 添加所有防护属性 yandexBtn.setAttribute('data-no-translate', 'true'); yandexBtn.setAttribute('translate', 'no'); yandexBtn.setAttribute('data-protect-translation', 'true'); yandexBtn.className += ' no-translate yandex-script-element'; yandexBtn.addEventListener('click', function() { setSiteSpecificEngine(CONFIG.ENGINES.YANDEX); showStatusMessage('已设置当前网站专用引擎: Yandex翻译', 2000); showSettings(); // 直接重新打开,不关闭 }); // 清除按钮 const clearBtn = document.createElement('button'); clearBtn.textContent = '清除专用引擎'; clearBtn.style.cssText = ` padding: 5px 8px; background-color: ${isSiteHasEngine ? '#ff4757' : '#ccc'}; color: white; border: none; border-radius: 4px; cursor: ${isSiteHasEngine ? 'pointer' : 'not-allowed'}; font-size: 11px; transition: background-color 0.2s; width: 100%; `; clearBtn.setAttribute('data-no-translate', 'true'); clearBtn.setAttribute('translate', 'no'); clearBtn.setAttribute('data-protect-translation', 'true'); clearBtn.className += ' no-translate yandex-script-element'; if (isSiteHasEngine) { clearBtn.addEventListener('click', function() { clearSiteSpecificEngine(); showStatusMessage('已清除当前网站专用引擎', 2000); showSettings(); // 直接重新打开,不关闭 }); } engineSelection.appendChild(microsoftBtn); engineSelection.appendChild(yandexBtn); siteEngineSection.appendChild(engineSelection); siteEngineSection.appendChild(clearBtn); modalContent.appendChild(siteEngineSection); // 目标语言设置 const languageSection = document.createElement('div'); languageSection.style.cssText = ` margin-bottom: 15px; `; languageSection.setAttribute('data-no-translate', 'true'); languageSection.setAttribute('translate', 'no'); languageSection.setAttribute('data-protect-translation', 'true'); languageSection.className = 'no-translate'; const languageLabel = document.createElement('div'); languageLabel.textContent = '目标语言:'; languageLabel.style.cssText = ` font-weight: bold; color: #555; margin-bottom: 8px; font-size: 13px; `; languageLabel.setAttribute('data-no-translate', 'true'); languageLabel.setAttribute('translate', 'no'); languageLabel.setAttribute('data-protect-translation', 'true'); languageSection.appendChild(languageLabel); const languageGrid = document.createElement('div'); languageGrid.style.cssText = ` display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 6px; `; languageGrid.setAttribute('data-no-translate', 'true'); languageGrid.setAttribute('translate', 'no'); languageGrid.setAttribute('data-protect-translation', 'true'); languageGrid.className = 'no-translate'; Object.values(CONFIG.LANGUAGES).forEach(lang => { const languageBtn = document.createElement('button'); languageBtn.style.cssText = ` padding: 8px; border: ${lang.code === targetLanguage ? '2px solid #0066cc' : '1px solid #ddd'}; border-radius: 5px; background: ${lang.code === targetLanguage ? '#f0f7ff' : 'white'}; cursor: pointer; font-size: 12px; display: flex; flex-direction: column; align-items: center; gap: 2px; transition: all 0.2s; `; languageBtn.setAttribute('data-no-translate', 'true'); languageBtn.setAttribute('translate', 'no'); languageBtn.setAttribute('data-protect-translation', 'true'); languageBtn.className = 'no-translate yandex-script-element'; languageBtn.innerHTML = ` ${lang.icon} ${lang.name} `; languageBtn.addEventListener('click', function() { if (lang.code !== targetLanguage) { targetLanguage = lang.code; GM_setValue(CONFIG.translationLanguageKey, targetLanguage); // 清除翻译缓存 translationCache.clear(); // 重新打开设置窗口 showSettings(); // 立即重新翻译 const engineToUse = getCurrentEngine(); if (engineToUse === CONFIG.ENGINES.MICROSOFT && microsoftTranslatorActive && (widgetEnabled || isWhitelistedDomain)) { showStatusMessage(`目标语言: ${lang.name},立即重新翻译...`, 2000); scheduleImmediateTranslation(); } else { showStatusMessage(`目标语言: ${lang.name}`, 2000); } } }); languageBtn.addEventListener('mouseenter', function() { if (lang.code !== targetLanguage) { this.style.borderColor = '#999'; this.style.backgroundColor = '#f9f9f9'; } }); languageBtn.addEventListener('mouseleave', function() { if (lang.code !== targetLanguage) { this.style.borderColor = '#ddd'; this.style.backgroundColor = 'white'; } }); languageGrid.appendChild(languageBtn); }); languageSection.appendChild(languageGrid); modalContent.appendChild(languageSection); // 翻译文字颜色设置 - 增强版 const colorSection = document.createElement('div'); colorSection.style.cssText = ` margin-bottom: 20px; padding: 12px; background-color: #f8f9fa; border-radius: 8px; border-left: 3px solid ${translationTextColor}; `; colorSection.setAttribute('data-no-translate', 'true'); colorSection.setAttribute('translate', 'no'); colorSection.setAttribute('data-protect-translation', 'true'); colorSection.className = 'no-translate'; const colorLabel = document.createElement('div'); colorLabel.textContent = '翻译文字颜色:'; colorLabel.style.cssText = ` font-weight: bold; color: #555; margin-bottom: 10px; font-size: 14px; `; colorLabel.setAttribute('data-no-translate', 'true'); colorLabel.setAttribute('translate', 'no'); colorLabel.setAttribute('data-protect-translation', 'true'); colorSection.appendChild(colorLabel); // 当前颜色预览 const colorPreview = document.createElement('div'); colorPreview.style.cssText = ` display: flex; align-items: center; margin-bottom: 10px; padding: 8px; background-color: white; border-radius: 6px; border: 1px solid #ddd; `; const previewLabel = document.createElement('span'); previewLabel.textContent = '当前颜色预览: '; previewLabel.style.cssText = ` margin-right: 10px; font-size: 12px; color: #666; `; const previewColor = document.createElement('span'); previewColor.textContent = '翻译文字示例'; previewColor.style.cssText = ` color: ${translationTextColor}; font-weight: bold; padding: 4px 8px; background-color: ${translationBgColor}; border-radius: 4px; border: 1px solid ${translationTextColor}20; `; colorPreview.appendChild(previewLabel); colorPreview.appendChild(previewColor); colorSection.appendChild(colorPreview); const colorGrid = document.createElement('div'); colorGrid.style.cssText = ` display: grid; grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); gap: 6px; margin-bottom: 10px; `; colorGrid.setAttribute('data-no-translate', 'true'); colorGrid.setAttribute('translate', 'no'); colorGrid.setAttribute('data-protect-translation', 'true'); colorGrid.className = 'no-translate'; CONFIG.COLOR_PRESETS.forEach(preset => { const colorBtn = document.createElement('button'); colorBtn.style.cssText = ` padding: 8px; border: ${translationTextColor === preset.value ? '2px solid #333' : '1px solid #ddd'}; border-radius: 5px; background: ${preset.value}; color: white; cursor: pointer; font-size: 11px; text-shadow: 0 1px 1px rgba(0,0,0,0.3); height: 35px; position: relative; overflow: hidden; transition: all 0.2s; `; colorBtn.setAttribute('data-no-translate', 'true'); colorBtn.setAttribute('translate', 'no'); colorBtn.setAttribute('data-protect-translation', 'true'); colorBtn.className = 'no-translate yandex-script-element'; const colorName = document.createElement('div'); colorName.textContent = preset.name; colorName.style.cssText = ` font-weight: bold; font-size: 10px; `; colorBtn.appendChild(colorName); if (translationTextColor === preset.value) { const checkmark = document.createElement('div'); checkmark.textContent = '✓'; checkmark.style.cssText = ` position: absolute; top: 1px; right: 1px; font-size: 10px; font-weight: bold; `; colorBtn.appendChild(checkmark); } colorBtn.addEventListener('click', function() { translationTextColor = preset.value; translationBgColor = preset.bg; GM_setValue(CONFIG.translationColorKey, translationTextColor); GM_setValue(CONFIG.translationBgColorKey, translationBgColor); updateColorStyles(); // 立即更新现有翻译元素的颜色 updateExistingTranslationColors(); showStatusMessage(`已应用颜色: ${preset.name}`, 2000); // 重新打开设置窗口,保持滚动位置 showSettings(); }); colorGrid.appendChild(colorBtn); }); colorSection.appendChild(colorGrid); // 自定义颜色输入 const customColorContainer = document.createElement('div'); customColorContainer.style.cssText = ` display: flex; gap: 8px; margin-top: 10px; align-items: center; `; const colorInput = document.createElement('input'); colorInput.type = 'color'; colorInput.value = translationTextColor; colorInput.style.cssText = ` width: 40px; height: 40px; padding: 0; border: none; cursor: pointer; border-radius: 4px; `; colorInput.setAttribute('data-no-translate', 'true'); colorInput.setAttribute('translate', 'no'); colorInput.setAttribute('data-protect-translation', 'true'); colorInput.className = 'no-translate'; const colorInputText = document.createElement('input'); colorInputText.type = 'text'; colorInputText.value = translationTextColor; colorInputText.placeholder = '#RRGGBB'; colorInputText.style.cssText = ` flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; `; colorInputText.setAttribute('data-no-translate', 'true'); colorInputText.setAttribute('translate', 'no'); colorInputText.setAttribute('data-protect-translation', 'true'); colorInputText.className = 'no-translate yandex-script-element'; const applyColorBtn = document.createElement('button'); applyColorBtn.textContent = '应用'; applyColorBtn.style.cssText = ` padding: 8px 12px; background-color: #0066cc; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: bold; `; applyColorBtn.setAttribute('data-no-translate', 'true'); applyColorBtn.setAttribute('translate', 'no'); applyColorBtn.setAttribute('data-protect-translation', 'true'); applyColorBtn.className = 'no-translate yandex-script-element'; applyColorBtn.addEventListener('click', function() { const colorValue = colorInputText.value.trim(); if (/^#[0-9A-Fa-f]{6}$/.test(colorValue) || /^#[0-9A-Fa-f]{3}$/.test(colorValue)) { translationTextColor = colorValue; translationBgColor = colorValue.replace('#', 'rgba(') + ',0.1)'; GM_setValue(CONFIG.translationColorKey, translationTextColor); GM_setValue(CONFIG.translationBgColorKey, translationBgColor); updateColorStyles(); // 立即更新现有翻译元素的颜色 updateExistingTranslationColors(); showStatusMessage('自定义颜色已应用', 2000); // 重新打开设置窗口,保持滚动位置 showSettings(); } else { showStatusMessage('颜色格式错误,请使用#RRGGBB格式', 2000); } }); colorInput.addEventListener('input', function() { colorInputText.value = this.value; }); colorInputText.addEventListener('input', function() { const colorValue = this.value.trim(); if (/^#[0-9A-Fa-f]{6}$/.test(colorValue) || /^#[0-9A-Fa-f]{3}$/.test(colorValue)) { colorInput.value = colorValue; } }); customColorContainer.appendChild(colorInput); customColorContainer.appendChild(colorInputText); customColorContainer.appendChild(applyColorBtn); colorSection.appendChild(customColorContainer); modalContent.appendChild(colorSection); // 显示模式设置 const modeSection = document.createElement('div'); modeSection.style.cssText = ` margin-bottom: 15px; `; modeSection.setAttribute('data-no-translate', 'true'); modeSection.setAttribute('translate', 'no'); modeSection.setAttribute('data-protect-translation', 'true'); modeSection.className = 'no-translate'; const modeLabel = document.createElement('div'); modeLabel.textContent = '显示模式:'; modeLabel.style.cssText = ` font-weight: bold; color: #555; margin-bottom: 8px; font-size: 13px; `; modeLabel.setAttribute('data-no-translate', 'true'); modeLabel.setAttribute('translate', 'no'); modeLabel.setAttribute('data-protect-translation', 'true'); modeSection.appendChild(modeLabel); const modeGrid = document.createElement('div'); modeGrid.style.cssText = ` display: grid; grid-template-columns: 1fr 1fr; gap: 8px; `; modeGrid.setAttribute('data-no-translate', 'true'); modeGrid.setAttribute('translate', 'no'); modeGrid.setAttribute('data-protect-translation', 'true'); modeGrid.className = 'no-translate'; // 替换模式按钮 const replaceModeBtn = document.createElement('button'); replaceModeBtn.style.cssText = ` padding: 8px; border: ${translationMode === CONFIG.MODES.REPLACE ? '2px solid #0066cc' : '1px solid #ddd'}; border-radius: 5px; background: ${translationMode === CONFIG.MODES.REPLACE ? '#f0f7ff' : 'white'}; cursor: pointer; font-size: 12px; display: flex; flex-direction: column; align-items: center; gap: 3px; transition: all 0.2s; `; replaceModeBtn.setAttribute('data-no-translate', 'true'); replaceModeBtn.setAttribute('translate', 'no'); replaceModeBtn.setAttribute('data-protect-translation', 'true'); replaceModeBtn.className = 'no-translate yandex-script-element'; replaceModeBtn.innerHTML = ` 🔁 替换原文 `; replaceModeBtn.addEventListener('click', function() { if (translationMode !== CONFIG.MODES.REPLACE) { translationMode = CONFIG.MODES.REPLACE; GM_setValue(CONFIG.translationModeKey, translationMode); showSettings(); const engineToUse = getCurrentEngine(); if (engineToUse === CONFIG.ENGINES.MICROSOFT && microsoftTranslatorActive && (widgetEnabled || isWhitelistedDomain)) { showStatusMessage('替换模式,立即重新翻译...', 2000); scheduleImmediateTranslation(); } else { showStatusMessage('替换模式', 2000); } } }); // 双语模式按钮 const dualModeBtn = document.createElement('button'); dualModeBtn.style.cssText = ` padding: 8px; border: ${translationMode === CONFIG.MODES.DUAL ? '2px solid #00b894' : '1px solid #ddd'}; border-radius: 5px; background: ${translationMode === CONFIG.MODES.DUAL ? '#f0fff4' : 'white'}; cursor: pointer; font-size: 12px; display: flex; flex-direction: column; align-items: center; gap: 3px; transition: all 0.2s; `; dualModeBtn.setAttribute('data-no-translate', 'true'); dualModeBtn.setAttribute('translate', 'no'); dualModeBtn.setAttribute('data-protect-translation', 'true'); dualModeBtn.className = 'no-translate yandex-script-element'; dualModeBtn.innerHTML = ` ↩️ 双语显示 `; dualModeBtn.addEventListener('click', function() { if (translationMode !== CONFIG.MODES.DUAL) { translationMode = CONFIG.MODES.DUAL; GM_setValue(CONFIG.translationModeKey, translationMode); showSettings(); const engineToUse = getCurrentEngine(); if (engineToUse === CONFIG.ENGINES.MICROSOFT && microsoftTranslatorActive && (widgetEnabled || isWhitelistedDomain)) { showStatusMessage('双语模式,立即重新翻译...', 2000); scheduleImmediateTranslation(); } else { showStatusMessage('双语模式', 2000); } } }); modeGrid.appendChild(replaceModeBtn); modeGrid.appendChild(dualModeBtn); modeSection.appendChild(modeGrid); modalContent.appendChild(modeSection); // 白名单管理部分 const whitelistSection = document.createElement('div'); whitelistSection.style.cssText = ` margin-bottom: 15px; border-top: 1px solid #eee; padding-top: 15px; `; whitelistSection.setAttribute('data-no-translate', 'true'); whitelistSection.setAttribute('translate', 'no'); whitelistSection.setAttribute('data-protect-translation', 'true'); whitelistSection.className = 'no-translate'; const whitelistHeader = document.createElement('div'); whitelistHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; `; const whitelistTitle = document.createElement('div'); whitelistTitle.textContent = '白名单管理'; whitelistTitle.style.cssText = ` font-weight: bold; color: #555; font-size: 13px; `; whitelistTitle.setAttribute('data-no-translate', 'true'); whitelistTitle.setAttribute('translate', 'no'); whitelistTitle.setAttribute('data-protect-translation', 'true'); whitelistTitle.className = 'no-translate'; const whitelist = getWhitelist(); const isInWhitelist = whitelist.includes(currentDomain); const toggleWhitelistBtn = document.createElement('button'); toggleWhitelistBtn.textContent = isInWhitelist ? `⭐ 移除` : `☆ 添加`; toggleWhitelistBtn.style.cssText = ` padding: 4px 8px; background-color: ${isInWhitelist ? '#ffa502' : '#2ed573'}; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; `; toggleWhitelistBtn.setAttribute('data-no-translate', 'true'); toggleWhitelistBtn.setAttribute('translate', 'no'); toggleWhitelistBtn.setAttribute('data-protect-translation', 'true'); toggleWhitelistBtn.className = 'no-translate yandex-script-element'; toggleWhitelistBtn.addEventListener('click', function() { toggleWhitelist(); showSettings(); }); whitelistHeader.appendChild(whitelistTitle); whitelistHeader.appendChild(toggleWhitelistBtn); whitelistSection.appendChild(whitelistHeader); if (whitelist.length > 0) { const whitelistList = document.createElement('div'); whitelistList.style.cssText = ` max-height: 100px; overflow-y: auto; margin-bottom: 10px; border: 1px solid #eee; border-radius: 4px; padding: 5px; background-color: #f9f9f9; `; whitelistList.setAttribute('data-no-translate', 'true'); whitelistList.setAttribute('translate', 'no'); whitelistList.setAttribute('data-protect-translation', 'true'); whitelistList.className = 'no-translate'; whitelist.forEach((domain, index) => { const domainItem = document.createElement('div'); domainItem.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 5px 6px; background-color: ${index % 2 === 0 ? 'white' : '#f5f5f5'}; border-radius: 3px; margin-bottom: 3px; font-size: 11px; `; domainItem.setAttribute('data-no-translate', 'true'); domainItem.setAttribute('translate', 'no'); domainItem.setAttribute('data-protect-translation', 'true'); domainItem.className = 'no-translate yandex-script-element'; const domainText = document.createElement('span'); domainText.textContent = domain; domainText.style.cssText = ` flex-grow: 1; word-break: break-all; `; domainText.setAttribute('data-no-translate', 'true'); domainText.setAttribute('translate', 'no'); domainText.setAttribute('data-protect-translation', 'true'); const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.className = 'compact-btn'; deleteBtn.style.cssText = ` font-size: 10px; padding: 3px 6px; margin-left: 5px; flex-shrink: 0; `; deleteBtn.setAttribute('data-no-translate', 'true'); deleteBtn.setAttribute('translate', 'no'); deleteBtn.setAttribute('data-protect-translation', 'true'); deleteBtn.className += ' no-translate yandex-script-element'; deleteBtn.addEventListener('click', function() { if (confirm(`确定要从白名单中删除 "${domain}" 吗?`)) { let updatedWhitelist = getWhitelist(); updatedWhitelist = updatedWhitelist.filter(d => d !== domain); saveWhitelist(updatedWhitelist); showStatusMessage(`已从白名单中删除: ${domain}`, 2000); showSettings(); } }); domainItem.appendChild(domainText); domainItem.appendChild(deleteBtn); whitelistList.appendChild(domainItem); }); whitelistSection.appendChild(whitelistList); const clearAllBtn = document.createElement('button'); clearAllBtn.textContent = '清除所有白名单'; clearAllBtn.className = 'compact-btn'; clearAllBtn.style.cssText = ` width: 100%; padding: 6px; font-size: 11px; `; clearAllBtn.setAttribute('data-no-translate', 'true'); clearAllBtn.setAttribute('translate', 'no'); clearAllBtn.setAttribute('data-protect-translation', 'true'); clearAllBtn.className += ' no-translate yandex-script-element'; clearAllBtn.addEventListener('click', function() { if (confirm(`确定要清除所有白名单域名吗?`)) { cachedWhitelist = []; GM_deleteValue(CONFIG.whitelistKey); showStatusMessage('已清除所有白名单', 2000); setTimeout(function() { checkWhitelist(); showSettings(); }, 100); } }); whitelistSection.appendChild(clearAllBtn); } else { const emptyMessage = document.createElement('div'); emptyMessage.textContent = '白名单为空'; emptyMessage.style.cssText = ` text-align: center; color: #999; padding: 15px; font-style: italic; font-size: 12px; `; emptyMessage.setAttribute('data-no-translate', 'true'); emptyMessage.setAttribute('translate', 'no'); emptyMessage.setAttribute('data-protect-translation', 'true'); emptyMessage.className = 'no-translate'; whitelistSection.appendChild(emptyMessage); } modalContent.appendChild(whitelistSection); // 关闭按钮 const closeBtn = document.createElement('button'); closeBtn.textContent = '关闭设置'; closeBtn.style.cssText = ` background-color: #747d8c; color: white; border: none; padding: 10px 15px; border-radius: 6px; cursor: pointer; width: 100%; font-size: 13px; font-weight: 600; transition: background-color 0.2s; `; closeBtn.setAttribute('data-no-translate', 'true'); closeBtn.setAttribute('translate', 'no'); closeBtn.setAttribute('data-protect-translation', 'true'); closeBtn.className = 'no-translate yandex-script-element'; closeBtn.addEventListener('click', function() { document.body.removeChild(modal); isModalOpen = false; }); closeBtn.addEventListener('mouseenter', function() { this.style.backgroundColor = '#636e72'; }); closeBtn.addEventListener('mouseleave', function() { this.style.backgroundColor = '#747d8c'; }); modalContent.appendChild(closeBtn); modal.appendChild(modalContent); modal.addEventListener('click', function(e) { if (e.target === modal) { document.body.removeChild(modal); isModalOpen = false; } }); const escHandler = function(e) { if (e.key === 'Escape') { document.body.removeChild(modal); document.removeEventListener('keydown', escHandler); isModalOpen = false; } }; document.addEventListener('keydown', escHandler); document.body.appendChild(modal); // 恢复滚动位置 setTimeout(() => { modalContent.scrollTop = scrollTopToRestore; }, 0); } // 更新现有翻译元素的颜色 function updateExistingTranslationColors() { console.log('更新现有翻译元素的颜色:', translationTextColor); // 更新替换模式下的翻译元素 document.querySelectorAll('.microsoft-translated.replace-mode').forEach(element => { element.style.color = translationTextColor + ' !important'; element.style.setProperty('color', translationTextColor, 'important'); // 更新悬停样式 element.addEventListener('mouseenter', function() { this.style.backgroundColor = translationBgColor + ' !important'; }); element.addEventListener('mouseleave', function() { this.style.backgroundColor = 'transparent'; }); }); // 更新双语模式下的翻译元素 document.querySelectorAll('.microsoft-translated.dual-mode .translated-text').forEach(element => { element.style.color = translationTextColor + ' !important'; element.style.setProperty('color', translationTextColor, 'important'); }); console.log('已更新现有翻译元素的颜色'); } // =============== Yandex翻译器相关函数 =============== // 创建Yandex小部件 function createYandexWidget() { if (widgetContainer) return; if (!document.body) { setTimeout(createYandexWidget, 100); return; } widgetContainer = document.createElement("div"); widgetContainer.id = CONFIG.widgetId; widgetContainer.className = "yandex-translate-widget"; widgetContainer.setAttribute("translate", "no"); widgetContainer.setAttribute("data-no-translate", "true"); widgetContainer.setAttribute("data-protect-translation", "true"); widgetContainer.classList.add("no-translate"); widgetContainer.classList.add("yandex-script-element"); let widgetParams = `widgetId=${CONFIG.widgetId}&pageLang=auto&widgetLang=zh&widgetTheme=minimal&autoMode=true`; document.body.appendChild(widgetContainer); loadYandexScript(widgetParams); } // 加载Yandex脚本 function loadYandexScript(widgetParams) { if (yandexScript || window.YandexTranslateWidgetLoaded) return; yandexScript = document.createElement("script"); yandexScript.src = `https://translate.yandex.net/website-widget/v1/widget.js?${widgetParams}`; yandexScript.async = true; yandexScript.defer = true; yandexScript.onload = function() { window.YandexTranslateWidgetLoaded = true; console.log('Yandex自动翻译脚本加载成功'); // 等待一段时间后检查翻译状态 setTimeout(() => { if (!isYandexTranslatorActive()) { console.log('Yandex翻译未自动激活,尝试手动激活'); reactivateYandexTranslator(); } }, 3000); }; yandexScript.onerror = function() { console.error('Yandex脚本加载失败'); }; document.body.appendChild(yandexScript); } // 移除Yandex小部件 function removeYandexWidget() { if (widgetContainer && widgetContainer.parentNode) { widgetContainer.parentNode.removeChild(widgetContainer); widgetContainer = null; } if (yandexScript && yandexScript.parentNode) { yandexScript.parentNode.removeChild(yandexScript); yandexScript = null; } window.YandexTranslateWidgetLoaded = false; yandexTranslationActive = false; } // =============== 微软翻译器相关函数 =============== // 获取微软翻译认证token function getMicrosoftAuthToken() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: "https://edge.microsoft.com/translate/auth", onload: function(res) { if (res.status === 200) { resolve(res.responseText); } else { reject(new Error('获取微软认证token失败')); } }, onerror: function(res) { reject(new Error('网络错误')); } }); }); } // 微软翻译函数(支持批量) function translateMicrosoftBatch(texts, fromLang, toLang) { return new Promise((resolve, reject) => { if (!microsoftAuthToken) { reject(new Error('未获取到微软认证token')); return; } if (!texts || texts.length === 0) { reject(new Error('翻译文本为空')); return; } const fromParam = fromLang ? `&from=${fromLang}` : ''; const translateData = texts.map(text => ({ "Text": text })); GM_xmlhttpRequest({ method: "POST", url: `https://api-edge.cognitive.microsofttranslator.com/translate?to=${toLang}${fromParam}&api-version=3.0&includeSentenceLength=true`, headers: { "Authorization": `Bearer ${microsoftAuthToken}`, "Content-Type": "application/json", }, data: JSON.stringify(translateData), onload: function(res) { if (res.status === 200) { try { const result = JSON.parse(res.responseText); const translations = result.map(item => item && item.translations && item.translations[0] ? item.translations[0].text : null ); resolve(translations); } catch (e) { reject(new Error('解析微软响应失败')); } } else { reject(new Error(`微软翻译失败: ${res.status}`)); } }, onerror: function(res) { reject(new Error('微软网络请求失败')); } }); }); } // 设置微软翻译器 async function setupMicrosoftTranslator() { if (microsoftTranslatorActive) return; try { microsoftAuthToken = await getMicrosoftAuthToken(); console.log('微软翻译认证成功'); microsoftTranslatorActive = true; } catch (error) { console.error('微软翻译初始化失败:', error); } } // 翻译整个页面 - 修复版:统一提示管理 async function translateWholePage() { if (pageTranslating) { console.log('翻译正在进行中...'); return; } if (!microsoftTranslatorActive || !microsoftAuthToken) { console.log('微软翻译未初始化,跳过翻译'); return; } try { pageTranslating = true; // 显示翻译状态指示器(根据设置) if (showTranslationIndicator) { showTranslatingIndicator('正在翻译页面...'); } let textNodes; if (isRussianContent && currentRussianSiteConfig) { console.log('使用俄语网站优化翻译...'); textNodes = collectTextNodesForRussianSite(); } else { textNodes = collectTextNodes(); } if (textNodes.length === 0) { console.log('无可翻译文本'); pageTranslating = false; // 如果没有文本可翻译,显示"翻译完成"然后隐藏 if (showTranslationIndicator) { showTranslatingIndicator('翻译完成'); setTimeout(() => { hideTranslatingIndicator(); }, CONFIG.TRANSLATING_INDICATOR_DURATION); } return; } console.log(`找到 ${textNodes.length} 个文本节点需要翻译`); const batches = createTextBatches(textNodes); const totalBatches = batches.length; let translatedCount = 0; for (let i = 0; i < batches.length; i++) { if (!pageTranslating) break; const batch = batches[i]; try { const textsToTranslate = []; const nodeIndices = []; batch.nodes.forEach((nodeInfo, index) => { const cacheKey = `${nodeInfo.text}|${targetLanguage}|${translationMode}`; if (translationCache.has(cacheKey)) { const translation = translationCache.get(cacheKey); applyTranslation(nodeInfo, translation); translatedCount++; } else { textsToTranslate.push(nodeInfo.text); nodeIndices.push(index); } }); if (textsToTranslate.length > 0) { const fromLang = isRussianContent && currentRussianSiteConfig && currentRussianSiteConfig.forceRussianSource ? 'ru' : null; const translations = await translateMicrosoftBatch(textsToTranslate, fromLang, targetLanguage); translations.forEach((translation, transIndex) => { const nodeIndex = nodeIndices[transIndex]; const nodeInfo = batch.nodes[nodeIndex]; if (translation && translation !== nodeInfo.text) { applyTranslation(nodeInfo, translation); const cacheKey = `${nodeInfo.text}|${targetLanguage}|${translationMode}`; translationCache.set(cacheKey, translation); translatedCount++; } }); } if (i < batches.length - 1) { await new Promise(resolve => setTimeout(resolve, 20)); } } catch (error) { console.error(`第 ${i + 1} 批翻译失败:`, error); if (translationRetryCount < CONFIG.MAX_RETRY_COUNT) { translationRetryCount++; console.log(`翻译失败,第${translationRetryCount}次重试...`); await new Promise(resolve => setTimeout(resolve, 200)); continue; } } } pageTranslating = false; lastTranslationTime = Date.now(); translationRetryCount = 0; console.log(`微软翻译完成: 已翻译 ${translatedCount} 个文本`); // 修复:翻译完成后显示"翻译完成",然后延迟隐藏 if (showTranslationIndicator) { showTranslatingIndicator('翻译完成'); setTimeout(() => { hideTranslatingIndicator(); }, CONFIG.TRANSLATING_INDICATOR_DURATION); } setTimeout(() => { checkForUntranslatedText(); }, 500); } catch (error) { console.error('页面翻译失败:', error); pageTranslating = false; // 修复:翻译失败时显示"翻译失败",然后延迟隐藏 if (showTranslationIndicator) { showTranslatingIndicator('翻译失败'); setTimeout(() => { hideTranslatingIndicator(); }, CONFIG.TRANSLATING_INDICATOR_DURATION); } } } // 显示翻译状态指示器 - 统一管理整个脚本的提示 function showTranslatingIndicator(message = '正在翻译页面...') { // 如果提示已关闭,不显示 if (!showTranslationIndicator) return; // 清除之前的隐藏定时器 if (indicatorHideTimeout) { clearTimeout(indicatorHideTimeout); indicatorHideTimeout = null; } // 如果已有状态提示,更新内容 if (currentTranslatingIndicator && currentTranslatingIndicator.parentNode) { const spinner = currentTranslatingIndicator.querySelector('.spinner'); const text = currentTranslatingIndicator.querySelector('span:last-child'); if (text) { text.textContent = message; } // 根据消息类型调整样式 if (message === '正在翻译页面...') { // 显示spinner if (!spinner) { const newSpinner = document.createElement('div'); newSpinner.className = 'spinner'; currentTranslatingIndicator.insertBefore(newSpinner, text); } else { spinner.style.display = 'block'; } currentTranslatingIndicator.style.background = 'rgba(0, 0, 0, 0.7)'; currentTranslatingIndicator.style.color = '#00ffcc'; currentTranslatingIndicator.style.borderColor = 'rgba(0, 255, 204, 0.3)'; } else if (message === '翻译完成') { // 隐藏spinner,改变颜色为绿色 if (spinner) { spinner.style.display = 'none'; } currentTranslatingIndicator.style.background = 'rgba(0, 184, 148, 0.7)'; currentTranslatingIndicator.style.color = '#ffffff'; currentTranslatingIndicator.style.borderColor = 'rgba(0, 184, 148, 0.3)'; } else if (message === '翻译失败') { // 隐藏spinner,改变颜色为红色 if (spinner) { spinner.style.display = 'none'; } currentTranslatingIndicator.style.background = 'rgba(255, 71, 87, 0.7)'; currentTranslatingIndicator.style.color = '#ffffff'; currentTranslatingIndicator.style.borderColor = 'rgba(255, 71, 87, 0.3)'; } else if (message === 'Yandex翻译已启动') { // Yandex翻译状态 if (spinner) { spinner.style.display = 'none'; } currentTranslatingIndicator.style.background = 'rgba(255, 61, 0, 0.7)'; currentTranslatingIndicator.style.color = '#ffffff'; currentTranslatingIndicator.style.borderColor = 'rgba(255, 61, 0, 0.3)'; } // 确保指示器可见 currentTranslatingIndicator.style.opacity = '0.9'; currentIndicatorText = message; return; } // 创建新的状态指示器 currentTranslatingIndicator = document.createElement('div'); currentTranslatingIndicator.className = 'translating-indicator'; currentTranslatingIndicator.setAttribute('translate', 'no'); currentTranslatingIndicator.setAttribute('data-no-translate', 'true'); currentTranslatingIndicator.setAttribute('data-protect-translation', 'true'); currentTranslatingIndicator.classList.add('no-translate'); currentTranslatingIndicator.classList.add('yandex-script-element'); // 根据消息类型添加spinner if (message === '正在翻译页面...') { const spinner = document.createElement('div'); spinner.className = 'spinner'; currentTranslatingIndicator.appendChild(spinner); } const text = document.createElement('span'); text.textContent = message; text.setAttribute('data-no-translate', 'true'); text.setAttribute('translate', 'no'); text.setAttribute('data-protect-translation', 'true'); currentTranslatingIndicator.appendChild(text); // 根据消息类型设置样式 if (message === '正在翻译页面...') { currentTranslatingIndicator.style.background = 'rgba(0, 0, 0, 0.7)'; currentTranslatingIndicator.style.color = '#00ffcc'; currentTranslatingIndicator.style.borderColor = 'rgba(0, 255, 204, 0.3)'; } else if (message === '翻译完成') { currentTranslatingIndicator.style.background = 'rgba(0, 184, 148, 0.7)'; currentTranslatingIndicator.style.color = '#ffffff'; currentTranslatingIndicator.style.borderColor = 'rgba(0, 184, 148, 0.3)'; } else if (message === '翻译失败') { currentTranslatingIndicator.style.background = 'rgba(255, 71, 87, 0.7)'; currentTranslatingIndicator.style.color = '#ffffff'; currentTranslatingIndicator.style.borderColor = 'rgba(255, 71, 87, 0.3)'; } else if (message === 'Yandex翻译已启动') { currentTranslatingIndicator.style.background = 'rgba(255, 61, 0, 0.7)'; currentTranslatingIndicator.style.color = '#ffffff'; currentTranslatingIndicator.style.borderColor = 'rgba(255, 61, 0, 0.3)'; } document.body.appendChild(currentTranslatingIndicator); isTranslatingIndicatorVisible = true; currentIndicatorText = message; console.log('翻译状态提示已显示:', message); } // 隐藏翻译状态指示器 function hideTranslatingIndicator() { // 清除之前的隐藏定时器 if (indicatorHideTimeout) { clearTimeout(indicatorHideTimeout); indicatorHideTimeout = null; } if (currentTranslatingIndicator && currentTranslatingIndicator.parentNode) { // 添加淡出效果 currentTranslatingIndicator.style.opacity = '0'; // 等待动画完成后移除元素 setTimeout(() => { if (currentTranslatingIndicator && currentTranslatingIndicator.parentNode) { currentTranslatingIndicator.parentNode.removeChild(currentTranslatingIndicator); currentTranslatingIndicator = null; isTranslatingIndicatorVisible = false; currentIndicatorText = ''; } }, CONFIG.TRANSLATING_INDICATOR_FADE_OUT); } else { isTranslatingIndicatorVisible = false; currentIndicatorText = ''; } } // 智能文本收集 - 不收集数字和单位 function collectTextNodes() { const textNodes = []; const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { const parent = node.parentElement; if (!parent) return NodeFilter.FILTER_REJECT; // 排除脚本菜单和脚本相关元素 const excludedSelectors = [ 'SCRIPT', 'STYLE', 'NOSCRIPT', '.microsoft-translated', '.translation-popup', '.translating-indicator', '.yandex-translate-widget', '.translation-settings-modal', '[data-no-translate]', '.no-translate', '.yandex-no-translate', '.yandex-script-element', '[data-protect-translation="true"]', '.tm-script-menu', '#tm-script-menu', '.vm-script-menu', '#vm-script-menu', '.gm-script-menu', '#gm-script-menu', '[class*="userscript"]', '[id*="userscript"]', '[class*="tampermonkey"]', '[id*="tampermonkey"]', '[class*="violentmonkey"]', '[id*="violentmonkey"]', '[class*="greasemonkey"]', '[id*="greasemonkey"]', '.engine-btn', '.site-engine-badge', '.yandex-protected-text' ]; for (const selector of excludedSelectors) { if (parent.matches(selector) || parent.closest(selector)) { return NodeFilter.FILTER_REJECT; } } // 检查属性和类名 if (parent.hasAttribute('translate') && parent.getAttribute('translate') === 'no') { return NodeFilter.FILTER_REJECT; } if (parent.classList && ( parent.classList.contains('no-translate') || parent.classList.contains('yandex-no-translate') || parent.classList.contains('yandex-script-element'))) { return NodeFilter.FILTER_REJECT; } const text = node.textContent.trim(); if (!text || text.length < 2) return NodeFilter.FILTER_REJECT; // 不翻译纯数字 if (/^\d+$/.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译数字+单位(如22M, 100GB等) if (/^\d+\.?\d*\s*[KMGT]?[B]?$/i.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译百分比 if (/^\d+\.?\d*\s*%$/.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译纯符号 if (/^[\d\s\W]+$/.test(text)) return NodeFilter.FILTER_REJECT; if (parent.classList.contains('microsoft-translated')) return NodeFilter.FILTER_REJECT; // 只收集有意义的文本(包含字母) if (!/[a-zA-Z\u0400-\u04FF\u4e00-\u9fff]/.test(text)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } } ); let node; while (node = walker.nextNode()) { const text = node.textContent.trim(); if (text && text.length > 0) { textNodes.push({ node: node, text: text, parent: node.parentElement, originalText: node.textContent }); } } return textNodes; } // 收集文本节点(俄语网站优化版)- 修复排除脚本菜单 function collectTextNodesForRussianSite() { const textNodes = []; const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { const parent = node.parentElement; if (!parent) return NodeFilter.FILTER_REJECT; // 排除脚本菜单和脚本相关元素 const excludedSelectors = [ 'SCRIPT', 'STYLE', 'NOSCRIPT', '.microsoft-translated', '.translation-popup', '.translating-indicator', '.yandex-translate-widget', '.translation-settings-modal', '[data-no-translate]', '.no-translate', '.yandex-no-translate', '.yandex-script-element', '[data-protect-translation="true"]', '.tm-script-menu', '#tm-script-menu', '.vm-script-menu', '#vm-script-menu', '.gm-script-menu', '#gm-script-menu', '[class*="userscript"]', '[id*="userscript"]', '[class*="tampermonkey"]', '[id*="tampermonkey"]', '[class*="violentmonkey"]', '[id*="violentmonkey"]', '[class*="greasemonkey"]', '[id*="greasemonkey"]', '.engine-btn', '.site-engine-badge', '.yandex-protected-text' ]; for (const selector of excludedSelectors) { if (parent.matches(selector) || parent.closest(selector)) { return NodeFilter.FILTER_REJECT; } } // 检查属性和类名 if (parent.hasAttribute('translate') && parent.getAttribute('translate') === 'no') { return NodeFilter.FILTER_REJECT; } if (parent.classList && ( parent.classList.contains('no-translate') || parent.classList.contains('yandex-no-translate') || parent.classList.contains('yandex-script-element'))) { return NodeFilter.FILTER_REJECT; } // 排除当前俄语网站配置中的选择器 if (currentRussianSiteConfig && currentRussianSiteConfig.excludeSelectors) { for (const selector of currentRussianSiteConfig.excludeSelectors) { if (parent.matches(selector) || parent.closest(selector)) { return NodeFilter.FILTER_REJECT; } } } const text = node.textContent.trim(); if (!text || text.length < 1) return NodeFilter.FILTER_REJECT; // 不翻译纯数字 if (/^\d+$/.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译数字+单位(如22M, 100GB等) if (/^\d+\.?\d*\s*[KMGT]?[B]?$/i.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译百分比 if (/^\d+\.?\d*\s*%$/.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译纯符号 if (/^[\d\s\W]+$/.test(text)) return NodeFilter.FILTER_REJECT; if (parent.classList.contains('microsoft-translated')) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } } ); let node; while (node = walker.nextNode()) { const text = node.textContent.trim(); if (text && text.length > 0) { textNodes.push({ node: node, text: text, parent: node.parentElement, originalText: node.textContent }); } } return textNodes; } // 创建文本批次 function createTextBatches(textNodes) { const batches = []; let currentBatch = { nodes: [], totalLength: 0 }; for (const nodeInfo of textNodes) { const textLength = nodeInfo.text.length; if (textLength > CONFIG.MAX_TEXT_LENGTH) { if (currentBatch.nodes.length > 0) { batches.push(currentBatch); currentBatch = { nodes: [], totalLength: 0 }; } const truncatedText = nodeInfo.text.substring(0, CONFIG.MAX_TEXT_LENGTH); batches.push({ nodes: [{...nodeInfo, text: truncatedText}], totalLength: truncatedText.length }); continue; } if (currentBatch.totalLength + textLength > CONFIG.MAX_TEXT_LENGTH || currentBatch.nodes.length >= CONFIG.BATCH_SIZE) { batches.push(currentBatch); currentBatch = { nodes: [], totalLength: 0 }; } currentBatch.nodes.push(nodeInfo); currentBatch.totalLength += textLength; } if (currentBatch.nodes.length > 0) { batches.push(currentBatch); } return batches; } // 应用翻译结果 - 修复文字颜色功能 function applyTranslation(nodeInfo, translation) { try { const originalNode = nodeInfo.node; const parent = nodeInfo.parent; const originalText = nodeInfo.originalText || originalNode.textContent; const elementId = generateElementId(); originalTextMap.set(elementId, originalText); const translatedElement = document.createElement('span'); translatedElement.className = `microsoft-translated ${translationMode}-mode`; translatedElement.dataset.originalId = elementId; translatedElement.setAttribute('translate', 'no'); translatedElement.setAttribute('data-no-translate', 'true'); translatedElement.setAttribute('data-protect-translation', 'true'); translatedElement.classList.add('no-translate'); translatedElement.classList.add('yandex-script-element'); if (translationMode === CONFIG.MODES.REPLACE) { translatedElement.textContent = translation; translatedElement.title = `原文: ${originalText}\n点击恢复原文`; // 直接设置颜色和内联样式 translatedElement.style.cssText += `color: ${translationTextColor} !important;`; translatedElement.addEventListener('click', function(e) { e.stopPropagation(); const originalId = this.dataset.originalId; const original = originalTextMap.get(originalId); if (original) { const textNode = document.createTextNode(original); parent.replaceChild(textNode, this); } }); // 添加悬停效果 translatedElement.addEventListener('mouseenter', function() { this.style.cssText += `background-color: ${translationBgColor} !important;`; }); translatedElement.addEventListener('mouseleave', function() { this.style.cssText = `color: ${translationTextColor} !important;`; }); } else { const originalSpan = document.createElement('span'); originalSpan.className = 'original-text'; originalSpan.textContent = originalText; originalSpan.setAttribute('data-no-translate', 'true'); originalSpan.setAttribute('translate', 'no'); originalSpan.setAttribute('data-protect-translation', 'true'); originalSpan.classList.add('no-translate'); originalSpan.classList.add('yandex-script-element'); const translatedSpan = document.createElement('span'); translatedSpan.className = 'translated-text'; translatedSpan.textContent = translation; // 直接设置颜色和内联样式 translatedSpan.style.cssText += `color: ${translationTextColor} !important;`; translatedSpan.setAttribute('data-no-translate', 'true'); translatedSpan.setAttribute('translate', 'no'); translatedSpan.setAttribute('data-protect-translation', 'true'); translatedSpan.classList.add('no-translate'); translatedSpan.classList.add('yandex-script-element'); translatedElement.appendChild(originalSpan); translatedElement.appendChild(translatedSpan); } parent.replaceChild(translatedElement, originalNode); } catch (error) { console.error('应用翻译失败:', error); } } // 生成元素ID function generateElementId() { return 'translated_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } // 检查是否有未翻译的文本 function checkForUntranslatedText() { if (!microsoftTranslatorActive || pageTranslating) return; const untranslatedNodes = collectUntranslatedNodes(); if (untranslatedNodes.length > 0) { console.log(`发现 ${untranslatedNodes.length} 个未翻译文本,重新翻译...`); if (untranslatedNodes.length > 10) { translateWholePage(); } else { translateSpecificNodes(untranslatedNodes); } } } // 收集未翻译的文本节点 function collectUntranslatedNodes() { const textNodes = []; const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { const parent = node.parentElement; if (!parent) return NodeFilter.FILTER_REJECT; // 排除脚本菜单和脚本相关元素 const excludedSelectors = [ 'SCRIPT', 'STYLE', 'NOSCRIPT', '.microsoft-translated', '.translation-popup', '.translating-indicator', '.yandex-translate-widget', '.translation-settings-modal', '[data-no-translate]', '.no-translate', '.yandex-no-translate', '.yandex-script-element', '[data-protect-translation="true"]', '.tm-script-menu', '#tm-script-menu', '.vm-script-menu', '#vm-script-menu', '.gm-script-menu', '#gm-script-menu', '[class*="userscript"]', '[id*="userscript"]', '[class*="tampermonkey"]', '[id*="tampermonkey"]', '[class*="violentmonkey"]', '[id*="violentmonkey"]', '[class*="greasemonkey"]', '[id*="greasemonkey"]', '.engine-btn', '.site-engine-badge', '.yandex-protected-text' ]; for (const selector of excludedSelectors) { if (parent.matches(selector) || parent.closest(selector)) { return NodeFilter.FILTER_REJECT; } } // 检查属性和类名 if (parent.hasAttribute('translate') && parent.getAttribute('translate') === 'no') { return NodeFilter.FILTER_REJECT; } if (parent.classList && ( parent.classList.contains('no-translate') || parent.classList.contains('yandex-no-translate') || parent.classList.contains('yandex-script-element'))) { return NodeFilter.FILTER_REJECT; } const text = node.textContent.trim(); if (!text || text.length < 2) return NodeFilter.FILTER_REJECT; // 不翻译纯数字 if (/^\d+$/.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译数字+单位(如22M, 100GB等) if (/^\d+\.?\d*\s*[KMGT]?[B]?$/i.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译百分比 if (/^\d+\.?\d*\s*%$/.test(text)) return NodeFilter.FILTER_REJECT; // 不翻译纯符号 if (/^[\d\s\W]+$/.test(text)) return NodeFilter.FILTER_REJECT; if (parent.classList.contains('microsoft-translated')) return NodeFilter.FILTER_REJECT; // 只收集有意义的文本(包含字母) if (!/[a-zA-Z\u0400-\u04FF\u4e00-\u9fff]/.test(text)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } } ); let node; while (node = walker.nextNode()) { const text = node.textContent.trim(); if (text && text.length > 0) { textNodes.push({ node: node, text: text, parent: node.parentElement, originalText: node.textContent }); } } return textNodes; } // 翻译特定的文本节点 async function translateSpecificNodes(textNodes) { if (!microsoftTranslatorActive || !microsoftAuthToken || pageTranslating) return; try { pageTranslating = true; // 显示翻译状态提示(根据设置) if (showTranslationIndicator) { showTranslatingIndicator('正在翻译页面...'); } const batches = createTextBatches(textNodes); let translatedCount = 0; for (let i = 0; i < batches.length; i++) { if (!pageTranslating) break; const batch = batches[i]; const textsToTranslate = []; const nodeIndices = []; batch.nodes.forEach((nodeInfo, index) => { const cacheKey = `${nodeInfo.text}|${targetLanguage}|${translationMode}`; if (translationCache.has(cacheKey)) { const translation = translationCache.get(cacheKey); applyTranslation(nodeInfo, translation); translatedCount++; } else { textsToTranslate.push(nodeInfo.text); nodeIndices.push(index); } }); if (textsToTranslate.length > 0) { const fromLang = isRussianContent && currentRussianSiteConfig && currentRussianSiteConfig.forceRussianSource ? 'ru' : null; const translations = await translateMicrosoftBatch(textsToTranslate, fromLang, targetLanguage); translations.forEach((translation, transIndex) => { const nodeIndex = nodeIndices[transIndex]; const nodeInfo = batch.nodes[nodeIndex]; if (translation && translation !== nodeInfo.text) { applyTranslation(nodeInfo, translation); const cacheKey = `${nodeInfo.text}|${targetLanguage}|${translationMode}`; translationCache.set(cacheKey, translation); translatedCount++; } }); } if (i < batches.length - 1) { await new Promise(resolve => setTimeout(resolve, 20)); } } pageTranslating = false; if (translatedCount > 0) { console.log(`已翻译 ${translatedCount} 个未翻译文本`); // 修复:翻译完成后显示"翻译完成",然后延迟隐藏 if (showTranslationIndicator) { showTranslatingIndicator('翻译完成'); setTimeout(() => { hideTranslatingIndicator(); }, CONFIG.TRANSLATING_INDICATOR_DURATION); } } else { // 修复:没有翻译任何内容时也显示"翻译完成" if (showTranslationIndicator) { showTranslatingIndicator('翻译完成'); setTimeout(() => { hideTranslatingIndicator(); }, CONFIG.TRANSLATING_INDICATOR_DURATION); } } } catch (error) { console.error('翻译特定节点失败:', error); pageTranslating = false; if (showTranslationIndicator) { showTranslatingIndicator('翻译失败'); setTimeout(() => { hideTranslatingIndicator(); }, CONFIG.TRANSLATING_INDICATOR_DURATION); } } } // =============== DOM变化监听相关函数 =============== // 开始观察DOM变化 function startDOMObservation() { if (isObservingDOM || !microsoftTranslatorActive) return; stopDOMObservation(); const observerConfig = { childList: true, subtree: true, characterData: true }; domObserver = new MutationObserver((mutations) => { if (pageTranslating) return; if (domChangeTimer) clearTimeout(domChangeTimer); domChangeTimer = setTimeout(() => { let hasSignificantChanges = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) { hasSignificantChanges = true; } if (mutation.type === 'characterData' && mutation.target.nodeType === Node.TEXT_NODE) { hasSignificantChanges = true; } }); if (hasSignificantChanges) { console.log('检测到DOM变化,重新翻译...'); // 重新检测俄语内容 smartEngineDetection(); setTimeout(() => { translateWholePage(); }, 300); } }, CONFIG.DOM_CHANGE_DELAY); }); try { domObserver.observe(document.body, observerConfig); isObservingDOM = true; } catch (error) { console.error('DOM观察器启动失败:', error); } } // 停止观察DOM变化 function stopDOMObservation() { if (domObserver) { domObserver.disconnect(); domObserver = null; } if (domChangeTimer) { clearTimeout(domChangeTimer); domChangeTimer = null; } isObservingDOM = false; } // 开始持续翻译监控 function startContinuousTranslation() { stopContinuousTranslation(); if (!microsoftTranslatorActive) return; console.log('开始持续翻译监控'); continuousTranslationTimer = setInterval(() => { if (!pageTranslating && microsoftTranslatorActive) { const now = Date.now(); if (now - lastTranslationTime > 20000) { console.log('长时间未翻译,检查未翻译文本...'); checkForUntranslatedText(); } } }, 8000); startScrollCheck(); } // 停止持续翻译监控 function stopContinuousTranslation() { if (continuousTranslationTimer) { clearInterval(continuousTranslationTimer); continuousTranslationTimer = null; } stopScrollCheck(); } // 开始滚动检查 function startScrollCheck() { stopScrollCheck(); if (!microsoftTranslatorActive) return; lastScrollY = window.scrollY; scrollCheckCount = 0; window.addEventListener('scroll', handleScroll); } // 停止滚动检查 function stopScrollCheck() { window.removeEventListener('scroll', handleScroll); if (scrollCheckTimer) { clearTimeout(scrollCheckTimer); scrollCheckTimer = null; } } // 处理滚动事件 function handleScroll() { const currentScrollY = window.scrollY; const scrollDelta = Math.abs(currentScrollY - lastScrollY); if (scrollDelta > 300) { scrollCheckCount++; lastScrollY = currentScrollY; if (scrollCheckTimer) clearTimeout(scrollCheckTimer); scrollCheckTimer = setTimeout(() => { if (!pageTranslating && microsoftTranslatorActive) { console.log(`检测到翻页行为,检查翻译...`); checkForUntranslatedText(); } }, CONFIG.SCROLL_CHECK_DELAY); } } // 停用微软翻译器 function deactivateMicrosoftTranslator() { stopAllObservations(); document.querySelectorAll('.microsoft-translation-popup').forEach(popup => { popup.remove(); }); document.querySelectorAll('.microsoft-translated').forEach(translatedElement => { const originalId = translatedElement.dataset.originalId; if (originalId) { const originalText = originalTextMap.get(originalId); if (originalText) { const textNode = document.createTextNode(originalText); translatedElement.parentNode.replaceChild(textNode, translatedElement); } } }); microsoftTranslatorActive = false; microsoftAuthToken = null; pageTranslating = false; originalTextMap.clear(); translationRetryCount = 0; lastTranslationTime = 0; } // 切换翻译引擎 function toggleTranslationEngine() { // 切换默认引擎 if (currentEngine === CONFIG.ENGINES.MICROSOFT) { currentEngine = CONFIG.ENGINES.YANDEX; } else { currentEngine = CONFIG.ENGINES.MICROSOFT; } GM_setValue(CONFIG.translationEngineKey, currentEngine); // 如果当前网站没有专用引擎,立即应用新引擎 if (!isUsingSiteSpecificEngine) { updateWidgetBasedOnSettings(); showStatusMessage(`切换到${currentEngine === CONFIG.ENGINES.MICROSOFT ? '微软' : 'Yandex'}翻译`, 2000); } else { // 如果有专用引擎,只更新默认引擎,不改变当前网站的翻译 showStatusMessage(`默认引擎已切换为${currentEngine === CONFIG.ENGINES.MICROSOFT ? '微软' : 'Yandex'}翻译`, 2000); } registerMenuCommands(); } // 切换主开关 function toggleWidget() { if (isWhitelistedDomain) { widgetEnabled = !widgetEnabled; GM_setValue(CONFIG.storageKey, widgetEnabled); showStatusMessage(widgetEnabled ? '自动翻译已开启' : '自动翻译已关闭', 2000); } else { widgetEnabled = !widgetEnabled; GM_setValue(CONFIG.storageKey, widgetEnabled); if (widgetEnabled) { console.log('开启翻译,立即更新设置'); updateWidgetBasedOnSettings(); const engineToUse = getCurrentEngine(); const engineName = engineToUse === CONFIG.ENGINES.MICROSOFT ? '微软' : 'Yandex'; let message = `${engineName}翻译已开启`; if (isUsingSiteSpecificEngine) { message += ' (网站专用引擎)'; } showStatusMessage(message, 2000); } else { console.log('关闭翻译,立即停用'); deactivateAllTranslators(); showStatusMessage('自动翻译已关闭', 2000); } } registerMenuCommands(); } // 设置键盘快捷键 function setupHotkey() { document.addEventListener('keydown', function(e) { if (isModalOpen) return; if (e.ctrlKey && e.shiftKey && e.key === 'Y') { e.preventDefault(); toggleWidget(); } if (e.ctrlKey && e.shiftKey && e.key === 'W') { e.preventDefault(); toggleWhitelist(); } if (e.ctrlKey && e.shiftKey && e.key === 'E') { e.preventDefault(); toggleTranslationEngine(); } if (e.ctrlKey && e.shiftKey && e.key === 'S') { e.preventDefault(); showSettings(); } if (e.ctrlKey && e.shiftKey && e.key === 'T') { e.preventDefault(); const engineToUse = getCurrentEngine(); if (engineToUse === CONFIG.ENGINES.MICROSOFT && microsoftTranslatorActive) { translateWholePage(); } } }, true); } // 显示状态提示 function showStatusMessage(message, duration = 2000) { const existingMsg = document.getElementById('yandex-status-msg'); if (existingMsg) { existingMsg.remove(); } const msgDiv = document.createElement('div'); msgDiv.id = 'yandex-status-msg'; // 处理消息中的"Yandex"文本 const protectedMessage = message.replace(/Yandex/g, 'Yandex' ); msgDiv.innerHTML = protectedMessage; // 添加所有防护属性 msgDiv.setAttribute('data-no-translate', 'true'); msgDiv.setAttribute('translate', 'no'); msgDiv.setAttribute('data-protect-translation', 'true'); msgDiv.className = 'no-translate yandex-script-element'; msgDiv.style.cssText = ` position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.9); color: white; padding: 12px 20px; border-radius: 10px; z-index: 2147483646; font-family: Arial, sans-serif; font-size: 14px; transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; box-shadow: 0 4px 20px rgba(0,0,0,0.3); backdrop-filter: blur(5px); border: 1px solid rgba(255, 255, 255, 0.1); opacity: 1; transform: translateY(0); max-width: 300px; word-wrap: break-word; `; if (message.includes('白名单')) { msgDiv.style.background = 'rgba(255, 193, 7, 0.95)'; msgDiv.style.color = '#000'; } else if (message.includes('微软')) { msgDiv.style.background = 'rgba(0, 120, 215, 0.95)'; msgDiv.style.color = 'white'; } else if (message.includes('Yandex')) { msgDiv.style.background = 'rgba(255, 61, 0, 0.95)'; msgDiv.style.color = 'white'; } else if (message.includes('俄语内容') || message.includes('俄语网站')) { msgDiv.style.background = 'rgba(255, 61, 0, 0.95)'; msgDiv.style.color = 'white'; } else if (message.includes('专用引擎')) { msgDiv.style.background = 'rgba(108, 92, 231, 0.95)'; msgDiv.style.color = 'white'; } else if (message.includes('翻译中') || message.includes('翻译完成') || message.includes('✓')) { msgDiv.style.background = 'rgba(0, 184, 148, 0.95)'; msgDiv.style.color = 'white'; } else if (message.includes('✗') || message.includes('失败')) { msgDiv.style.background = 'rgba(255, 71, 87, 0.95)'; msgDiv.style.color = 'white'; } else if (message.includes('颜色')) { msgDiv.style.background = 'rgba(255, 159, 67, 0.95)'; msgDiv.style.color = 'white'; } document.body.appendChild(msgDiv); // 添加淡出效果 setTimeout(function() { msgDiv.classList.add('fade-out'); setTimeout(function() { if (msgDiv.parentNode) { msgDiv.parentNode.removeChild(msgDiv); } }, 500); }, duration); } // 启动脚本 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();