// ==UserScript== // @name 自动翻译助手 // @name:zh-CN 自动翻译助手 // @name:zh-TW 自動翻譯助手 // @name:zh-HK 自動翻譯助手 // @name:en Auto Translation Assistant // @namespace https://github.com/eraycc // @version 2.2.2 // @description 强大的网页翻译工具,支持多语言,可移动可缩放悬浮窗,白名单管理(样式完全独立) // @author Eray // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAhUlEQVQ4je2SMQ6AIAxFeyfH4hLiYh8OoGMZ8Uok1UJ5Tdv0Jf/Sl/db0hAaC6FEUXgHxIGYgRZJqGQpRGOAMUABNkVnzI5I4Qz5PkBOtB6WQlTEnUqxhwhHAK54HUE5/5DxTSyG1njDQRIDaM3zkI5YV/hIlgyjXzGf/6xX1oB+jw8fAAAAAElFTkSuQmCC // @run-at document-start // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_xmlhttpRequest // @grant unsafeWindow // @license Apache-2.0 // @require https://unpkg.com/i18n-jsautotranslate@3.18.89/index.js // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iMTYiIGZpbGw9IiMyOTUzZTgiLz48Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIxMiIgZmlsbD0iIzQyYTVmNSIvPjxwYXRoIGQ9Ik00IDE2YzAgNi42IDUuNCAxMiAxMiAxMnMxMi01LjQgMTItMTIiIGZpbGw9Im5vbmUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PHBhdGggZD0iTTE2IDR2MjRNMjggMTZINCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSI0IiBmaWxsPSIjMjk1M2U4Ii8+PC9zdmc+ // ==/UserScript== (function() { 'use strict'; // 工具函数 function isMobile() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768; } function isDarkMode() { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } function getCurrentDomain() { return window.location.hostname; } // 配置管理器 class ConfigManager { constructor() { this.defaultConfig = { enabled: true, localLanguage: 'english', targetLanguage: 'chinese_simplified', autoTranslate: false, translateService: 'client.edge', customServiceUrls: '', panelPosition: { x: 100, y: 100 }, panelWidth: 380, panelHeight: 500, panelOpacity: 1, ignoredClasses: '', ignoredIds: '', customTerms: '', enableListener: true, enableCache: true, translateAttributes: ['title', 'alt', 'placeholder'], whitelist: [], siteSpecificServices: {}, enableSelectionTranslate: true }; this.config = this.loadConfig(); } loadConfig() { const saved = GM_getValue('translateConfig', null); if (saved) { if (!saved.panelWidth) saved.panelWidth = this.defaultConfig.panelWidth; if (!saved.panelHeight) saved.panelHeight = this.defaultConfig.panelHeight; if (!saved.localLanguage) saved.localLanguage = this.defaultConfig.localLanguage; if (saved.enableSelectionTranslate === undefined) saved.enableSelectionTranslate = this.defaultConfig.enableSelectionTranslate; return { ...this.defaultConfig, ...saved }; } return { ...this.defaultConfig }; } saveConfig() { GM_setValue('translateConfig', this.config); } get(key) { return this.config[key]; } set(key, value) { this.config[key] = value; this.saveConfig(); } reset() { GM_deleteValue('translateConfig'); this.config = { ...this.defaultConfig }; this.saveConfig(); } clearCache() { if (typeof translate !== 'undefined') { try { translate.storage.clear(); } catch (e) { console.log('清除translate.js缓存失败:', e); } } const keys = Object.keys(localStorage); let cleared = 0; keys.forEach(key => { if (key.includes('translate_') || key.includes('hash_')) { localStorage.removeItem(key); cleared++; } }); return cleared; } isInWhitelist(domain) { return this.config.whitelist.includes(domain); } addToWhitelist(domain) { if (!this.isInWhitelist(domain)) { this.config.whitelist.push(domain); this.saveConfig(); return true; } return false; } removeFromWhitelist(domain) { const index = this.config.whitelist.indexOf(domain); if (index !== -1) { this.config.whitelist.splice(index, 1); this.saveConfig(); return true; } return false; } clearWhitelist() { this.config.whitelist = []; this.saveConfig(); } toggleWhitelistDomain(domain) { if (this.isInWhitelist(domain)) { this.removeFromWhitelist(domain); return false; } else { this.addToWhitelist(domain); return true; } } shouldTranslate() { if (this.isInWhitelist(getCurrentDomain())) return true; return this.config.enabled; } getSiteService(domain) { return this.config.siteSpecificServices[domain] || null; } setSiteService(domain, service) { if (service && service !== this.config.translateService) { this.config.siteSpecificServices[domain] = service; } else { delete this.config.siteSpecificServices[domain]; } this.saveConfig(); } getEffectiveService(domain) { const siteService = this.getSiteService(domain); return siteService || this.config.translateService; } } // 翻译管理器 class TranslateManager { constructor(configManager) { this.configManager = configManager; this.initialized = false; this.listenerStarted = false; this.currentLanguage = null; this.isTranslating = false; } init() { if (this.initialized || typeof translate === 'undefined') return; try { translate.language.setLocal(this.configManager.get('localLanguage')); const domain = getCurrentDomain(); const service = this.configManager.getEffectiveService(domain); translate.service.use(service); if (service === 'translate.service') { const customUrls = this.configManager.get('customServiceUrls'); if (customUrls) { const urls = customUrls.split(',').map(url => url.trim()).filter(url => url); if (urls.length > 0) { translate.request.api.host = urls.length === 1 ? urls[0] : urls; } } } translate.selectLanguageTag.show = false; this.applyIgnoreSettings(); this.applyCustomTerms(); const translateAttributes = this.configManager.get('translateAttributes'); if (translateAttributes.length > 0) { translate.translateAttributes = translateAttributes; } translate.showOrigin = false; if (this.configManager.get('enableCache')) { translate.storage.enable(); } else { translate.storage.disable(); } this.initialized = true; console.log('翻译管理器初始化完成'); } catch (error) { console.error('翻译初始化失败:', error); } } applyIgnoreSettings() { if (typeof translate === 'undefined') return; translate.ignore.class = []; translate.ignore.id = []; const ignoredClasses = this.configManager.get('ignoredClasses'); if (ignoredClasses) { ignoredClasses.split(',').map(c => c.trim()).filter(c => c).forEach(cls => translate.ignore.class.push(cls)); } const ignoredIds = this.configManager.get('ignoredIds'); if (ignoredIds) { ignoredIds.split(',').map(id => id.trim()).filter(id => id).forEach(id => translate.ignore.id.push(id)); } } applyCustomTerms() { if (typeof translate === 'undefined') return; const customTerms = this.configManager.get('customTerms'); if (!customTerms) return; const localLang = this.configManager.get('localLanguage'); const targetLang = this.configManager.get('targetLanguage'); translate.nomenclature.append(localLang, targetLang, customTerms); } startListener() { if (!this.listenerStarted && typeof translate !== 'undefined') { try { if (this.configManager.get('enableListener')) { translate.listener.start(); this.listenerStarted = true; console.log('动态内容监听已启动'); } } catch (error) { if (!error.message?.includes('已经启动')) { console.error('启动监听失败:', error); } } } } changeLanguage(targetLang, force = false) { if (!this.initialized) this.init(); if (typeof translate === 'undefined') return; if (!force && this.currentLanguage === targetLang && this.isTranslating) return; this.isTranslating = true; this.init(); this.applyIgnoreSettings(); this.applyCustomTerms(); this.startListener(); translate.changeLanguage(targetLang); setTimeout(() => { this.isTranslating = false; this.currentLanguage = targetLang; }, 1000); } forceTranslate() { const targetLang = this.configManager.get('targetLanguage'); this.changeLanguage(targetLang, true); } refresh() { const should = this.configManager.shouldTranslate(); if (should) { this.forceTranslate(); } else { this.changeLanguage(null, true); } } startAutoTranslate() { if (!this.initialized) this.init(); this.startListener(); const should = this.configManager.shouldTranslate(); const auto = this.configManager.get('autoTranslate'); if (should && (auto || this.configManager.isInWhitelist(getCurrentDomain()))) { setTimeout(() => this.forceTranslate(), 100); } else if (!should && !auto) { setTimeout(() => this.changeLanguage(null, true), 100); } } startSelectionTranslate() { if (!this.initialized) this.init(); if (typeof translate !== 'undefined') { try { translate.language.setDefaultTo(this.configManager.get('targetLanguage')); translate.selectionTranslate.start(); } catch (error) { console.error('启动划词翻译失败:', error); } } } stopSelectionTranslate() { if (typeof translate !== 'undefined' && translate.selectionTranslate) { try { translate.selectionTranslate.stop(); } catch (error) { console.error('停止划词翻译失败:', error); } } } } // 提示管理器 class ToastManager { constructor() { this.container = null; this.init(); } init() { this.container = document.createElement('div'); this.container.id = 'translate-toast-container'; this.container.style.cssText = ` position: fixed !important; top: 20px !important; right: 20px !important; z-index: 2147483647 !important; pointer-events: none !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif !important; `; document.body.appendChild(this.container); } show(message, type = 'success', duration = 2000) { const toast = document.createElement('div'); const bgColor = type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'; toast.style.cssText = ` background: ${bgColor} !important; color: white !important; padding: 12px 20px !important; border-radius: 12px !important; margin-bottom: 10px !important; box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.02) !important; animation: slideInRight 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55) !important; pointer-events: auto !important; font-size: 14px !important; font-weight: 500 !important; backdrop-filter: blur(8px) !important; `; toast.textContent = message; this.container.appendChild(toast); setTimeout(() => { toast.style.animation = 'slideOutRight 0.3s ease !important'; setTimeout(() => { if (this.container.contains(toast)) this.container.removeChild(toast); }, 300); }, duration); } } // UI管理器 - 确保样式完全独立 class UIManager { constructor(configManager, translateManager) { this.configManager = configManager; this.translateManager = translateManager; this.panel = null; this.toast = new ToastManager(); this.isDragging = false; this.isResizing = false; this.init(); } init() { this.injectStyles(); this.createPanel(); this.bindEvents(); setTimeout(() => this.translateManager.startAutoTranslate(), 500); if (!isMobile()) setTimeout(() => this.translateManager.startSelectionTranslate(), 1500); } injectStyles() { const darkMode = isDarkMode(); // 使用更严格的样式,添加 !important 防止被覆盖 GM_addStyle(` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } #translate-panel { position: fixed !important; border-radius: 12px !important; box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.2) !important; z-index: 2147483646 !important; display: none !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif !important; overflow: hidden !important; touch-action: none !important; user-select: none !important; -webkit-user-select: none !important; transition: none !important; min-width: 280px !important; min-height: 300px !important; max-width: 90vw !important; max-height: 80vh !important; backdrop-filter: blur(20px) !important; ${darkMode ? ` background: rgba(30, 30, 35, 0.95) !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; ` : ` background: rgba(255, 255, 255, 0.95) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; `} } #translate-panel.show { display: flex !important; flex-direction: column !important; animation: fadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1) !important; } #translate-panel.dragging { transition: none !important; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3) !important; cursor: grabbing !important; } #translate-panel.resizing { transition: none !important; cursor: nw-resize !important; } .translate-panel-header { padding: 12px 16px !important; display: flex !important; justify-content: space-between !important; align-items: center !important; cursor: move !important; border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important; flex-shrink: 0 !important; ${darkMode ? ` background: rgba(0, 0, 0, 0.3) !important; border-bottom-color: rgba(255, 255, 255, 0.1) !important; ` : ` background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; `} } .translate-panel-title { font-size: 16px !important; font-weight: 600 !important; user-select: none !important; letter-spacing: -0.01em !important; display: flex !important; align-items: center !important; gap: 6px !important; color: white !important; } .translate-panel-close { width: 28px !important; height: 28px !important; border-radius: 50% !important; background: rgba(0, 0, 0, 0.1) !important; border: none !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: all 0.2s !important; font-size: 16px !important; color: white !important; } .translate-panel-close:hover { background: rgba(0, 0, 0, 0.2) !important; transform: scale(1.05) !important; } .translate-panel-close:active { transform: scale(0.95) !important; } .translate-panel-body { padding: 16px !important; overflow-y: auto !important; -webkit-overflow-scrolling: touch !important; cursor: default !important; flex: 1 !important; } .translate-panel-body::-webkit-scrollbar { width: 4px !important; } .translate-panel-body::-webkit-scrollbar-track { background: transparent !important; } .translate-panel-body::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2) !important; border-radius: 10px !important; } .translate-control-group { margin-bottom: 16px !important; } .translate-control-label { display: flex !important; align-items: center !important; gap: 6px !important; margin-bottom: 6px !important; font-size: 13px !important; font-weight: 500 !important; ${darkMode ? `color: #e5e7eb !important;` : `color: #1f2937 !important;`} } .translate-description { font-size: 11px !important; ${darkMode ? `color: #9ca3af !important;` : `color: #6b7280 !important;`} margin-top: 2px !important; font-weight: normal !important; } .translate-switch { position: relative !important; display: inline-block !important; width: 40px !important; height: 22px !important; } .translate-switch input { opacity: 0 !important; width: 0 !important; height: 0 !important; } .translate-switch-slider { position: absolute !important; cursor: pointer !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; background-color: #cbd5e1 !important; transition: .2s !important; border-radius: 34px !important; } .translate-switch-slider:before { position: absolute !important; content: "" !important; height: 16px !important; width: 16px !important; left: 3px !important; bottom: 3px !important; background-color: white !important; transition: .2s !important; border-radius: 50% !important; box-shadow: 0 1px 2px rgba(0,0,0,0.1) !important; } .translate-switch input:checked + .translate-switch-slider { background-color: #8b5cf6 !important; } .translate-switch input:checked + .translate-switch-slider:before { transform: translateX(18px) !important; } .translate-select, .translate-input { width: 100% !important; padding: 8px 12px !important; border: 1px solid #e5e7eb !important; border-radius: 8px !important; font-size: 13px !important; transition: all 0.2s !important; box-sizing: border-box !important; font-family: inherit !important; ${darkMode ? ` background: rgba(50, 50, 55, 0.8) !important; color: #e5e7eb !important; border-color: rgba(255, 255, 255, 0.1) !important; ` : ` background: rgba(255, 255, 255, 0.8) !important; color: #1f2937 !important; `} } .translate-select:focus, .translate-input:focus { outline: none !important; border-color: #8b5cf6 !important; box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.1) !important; } .translate-textarea { width: 100% !important; min-height: 80px !important; padding: 8px 12px !important; border: 1px solid #e5e7eb !important; border-radius: 8px !important; font-size: 12px !important; transition: all 0.2s !important; box-sizing: border-box !important; resize: vertical !important; font-family: monospace !important; line-height: 1.4 !important; ${darkMode ? ` background: rgba(50, 50, 55, 0.8) !important; color: #e5e7eb !important; border-color: rgba(255, 255, 255, 0.1) !important; ` : ` background: rgba(255, 255, 255, 0.8) !important; color: #1f2937 !important; `} } .translate-textarea:focus { outline: none !important; border-color: #8b5cf6 !important; box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.1) !important; } .translate-button { width: 100% !important; padding: 8px !important; background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%) !important; color: white !important; border: none !important; border-radius: 8px !important; font-size: 13px !important; font-weight: 500 !important; cursor: pointer !important; transition: all 0.2s !important; -webkit-tap-highlight-color: transparent !important; box-shadow: 0 1px 2px rgba(0,0,0,0.05) !important; } .translate-button:hover { transform: translateY(-1px) !important; box-shadow: 0 4px 10px rgba(139, 92, 246, 0.2) !important; } .translate-button:active { transform: scale(0.98) !important; } .translate-button-group { display: flex !important; gap: 10px !important; margin-bottom: 16px !important; } .translate-button-group .translate-button { flex: 1 !important; } .translate-section-title { font-size: 15px !important; font-weight: 600 !important; ${darkMode ? `color: #e5e7eb !important;` : `color: #1f2937 !important;`} margin: 20px 0 12px !important; padding-bottom: 6px !important; border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important; letter-spacing: -0.2px !important; } .translate-section-title:first-of-type { margin-top: 0 !important; } .translate-info { background: rgba(0, 0, 0, 0.03) !important; padding: 12px !important; border-radius: 8px !important; font-size: 11px !important; ${darkMode ? `color: #9ca3af !important;` : `color: #6b7280 !important;`} margin-top: 12px !important; line-height: 1.4 !important; } details { margin-top: 10px !important; } summary { cursor: pointer !important; font-size: 13px !important; font-weight: 500 !important; color: #8b5cf6 !important; padding: 6px 0 !important; user-select: none !important; } summary:hover { color: #6366f1 !important; } .advanced-content { margin-top: 12px !important; } .resize-handle { position: absolute !important; bottom: 0 !important; right: 0 !important; width: 16px !important; height: 16px !important; cursor: nw-resize !important; background: rgba(0,0,0,0.1) !important; border-radius: 0 0 12px 0 !important; z-index: 10 !important; } .resize-handle:hover { background: rgba(139, 92, 246, 0.3) !important; } .size-slider-container { margin-top: 8px !important; display: flex !important; align-items: center !important; gap: 10px !important; } .size-slider-container .translate-slider { flex: 1 !important; } `); } createPanel() { const panel = document.createElement('div'); panel.id = 'translate-panel'; const languages = this.getSupportedLanguages(); const services = [ { value: 'client.edge', name: '微软 Edge' }, { value: 'giteeai', name: 'gitee AI' }, { value: 'siliconflow', name: 'SiliconFlow AI' }, { value: 'translate.service', name: '自定义服务' } ]; const config = this.configManager.config; const currentDomain = getCurrentDomain(); const inWhitelist = this.configManager.isInWhitelist(currentDomain); const siteService = this.configManager.getSiteService(currentDomain); const currentEffectiveService = this.configManager.getEffectiveService(currentDomain); const mobile = isMobile(); panel.innerHTML = `