// ==UserScript== // @name 网页英语难词自动划词翻译 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 自动检测网页中的英语难词,鼠标悬浮显示翻译 // @author xile bb844785535@gmail.com // @match *://*/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect api.fanyi.baidu.com // @connect raw.githubusercontent.com // @downloadURL https://update.greasyfork.icu/scripts/526533/%E7%BD%91%E9%A1%B5%E8%8B%B1%E8%AF%AD%E9%9A%BE%E8%AF%8D%E8%87%AA%E5%8A%A8%E5%88%92%E8%AF%8D%E7%BF%BB%E8%AF%91.user.js // @updateURL https://update.greasyfork.icu/scripts/526533/%E7%BD%91%E9%A1%B5%E8%8B%B1%E8%AF%AD%E9%9A%BE%E8%AF%8D%E8%87%AA%E5%8A%A8%E5%88%92%E8%AF%8D%E7%BF%BB%E8%AF%91.meta.js // ==/UserScript== (function() { 'use strict'; // 修改配置项 const config = { wordFreqThreshold: 8000, // 调整为更低的难度阈值 academicWordThreshold: 0.3, // 降低学术词汇权重 minWordLength: 4, // 降低最小单词长度 enableLemmatization: true, excludeProperNouns: true, excludeAbbreviations: true, maxAbbrLength: 4, debugMode: false, excludeTags: [ 'SCRIPT', 'STYLE', 'PRE', 'CODE', 'TEXTAREA', 'INPUT', 'IFRAME', 'NOSCRIPT', 'BUTTON', 'SELECT', 'OPTION' ], excludeClasses: ['no-translate', 'button', 'btn', 'word-tooltip', 'translation-item', 'phonetic'], excludeAttributes: ['onclick', 'onmouseover', 'onmouseenter'], buttonTags: ['BUTTON', 'A', 'INPUT[type="button"]', 'INPUT[type="submit"]'], apiUrl: 'https://v2.xxapi.cn/api/englishwords?word=', fallbackApiUrl: 'https://v.api.aa1.cn/api/api-fanyi-yd/index.php', apiHeaders: { 'User-Agent': 'xiaoxiaoapi/1.0.0 (https://xxapi.cn)' }, maxRetries: 3, retryDelay: 1000, tooltipDelay: 200, tooltipHideDelay: 300, contextAwareness: true, // 启用上下文感知 dynamicThreshold: true // 启用动态难度调整 }; // 修改悬浮提示框样式 GM_addStyle(` .word-tooltip { position: fixed; background: rgba(255, 255, 255, 0.98); color: #333; padding: 12px 16px; border-radius: 6px; font-size: 14px; line-height: 1.6; z-index: 2147483647; pointer-events: none; max-width: 350px; backdrop-filter: blur(8px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.1); transition: opacity 0.15s ease; opacity: 0; } .difficult-word { position: relative !important; display: inline-block !important; cursor: help !important; padding: 0 1px !important; border: none !important; background: none !important; text-decoration: none !important; } .difficult-word::after { content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; background-color: #2196F3; width: 100% !important; transition: none !important; pointer-events: none; } .difficult-word:hover::after { background-color: #1976D2; } .word-tooltip .phonetic { color: #666; font-family: Arial, sans-serif; margin-bottom: 8px; font-size: 13px; } .word-tooltip .translation-item { margin-bottom: 4px; } .word-tooltip .pos { color: #2196F3; margin-right: 6px; font-style: italic; } .translation-inline { color: #666; margin-left: 4px; font-size: inherit; display: none; } .translation-wrapper { display: inline !important; position: relative !important; pointer-events: auto !important; } `); // 添加常用词列表 const commonWords = new Set([ // 代词 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them', 'my', 'your', 'his', 'its', 'our', 'their', 'mine', 'yours', 'hers', 'ours', 'theirs', 'this', 'that', 'these', 'those', // 常用动词 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'can', 'could', 'will', 'would', 'shall', 'should', 'may', 'might', 'must', 'need', 'dare', 'go', 'goes', 'went', 'gone', 'going', 'come', 'comes', 'came', 'coming', 'get', 'gets', 'got', 'getting', 'make', 'makes', 'made', 'making', 'know', 'knows', 'knew', 'known', 'think', 'thinks', 'thought', 'thinking', 'take', 'takes', 'took', 'taken', 'taking', 'see', 'sees', 'saw', 'seen', 'seeing', 'want', 'wants', 'wanted', 'wanting', 'give', 'gives', 'gave', 'given', 'giving', 'use', 'uses', 'used', 'using', 'find', 'finds', 'found', 'finding', 'tell', 'tells', 'told', 'telling', 'ask', 'asks', 'asked', 'asking', 'work', 'works', 'worked', 'working', 'seem', 'seems', 'seemed', 'seeming', 'feel', 'feels', 'felt', 'feeling', 'try', 'tries', 'tried', 'trying', 'leave', 'leaves', 'left', 'leaving', 'call', 'calls', 'called', 'calling', // 常用介词 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'about', 'into', 'over', 'after', 'under', 'during', 'before', 'above', 'below', 'up', 'down', 'off', 'through', // 常用连词 'and', 'but', 'or', 'nor', 'for', 'yet', 'so', 'because', 'although', 'unless', 'since', 'if', 'when', 'where', 'while', // 常用副词 'very', 'really', 'just', 'now', 'then', 'here', 'there', 'only', 'also', 'too', 'not', 'never', 'always', 'sometimes', 'often', 'again', 'ever', 'still', 'already', 'quite', 'well', // 常用形容词 'good', 'bad', 'new', 'old', 'great', 'high', 'low', 'big', 'small', 'long', 'short', 'many', 'much', 'few', 'other', 'same', 'different', 'next', 'last', 'first', 'right', 'wrong', 'true', 'false', 'important', // 数词 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'first', 'second', 'third', 'fourth', 'fifth', 'hundred', 'thousand', 'million', 'billion', // 其他高频词 'time', 'year', 'day', 'week', 'month', 'way', 'thing', 'life', 'world', 'school', 'family', 'group', 'company', 'number', 'part', 'most', 'both', 'all', 'such', 'even', 'yes', 'no', 'any', 'every', 'each', 'than', 'what', 'who', 'which', 'whose', 'why', 'how', 'when', 'where' ]); // 添加常见缩写列表 const commonAbbreviations = new Set([ 'ai', 'url', 'http', 'https', 'www', 'html', 'css', 'js', 'cpu', 'gpu', 'ram', 'rom', 'ssd', 'hdd', 'usb', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'jpeg', 'png', 'gif', 'mp3', 'mp4', 'avi', 'wifi', 'os', 'ui', 'ux', 'api', 'sdk', 'npm', 'git', 'id', 'ip', 'dns', 'sql', 'json', 'xml', 'ftp', 'ssl', 'ssh', 'utf' ]); // 添加学术词汇表 const academicWords = new Set([ 'abstract', 'analysis', 'approach', 'area', 'assessment', 'assume', 'authority', 'available', 'benefit', 'concept', 'consistent', 'constitutional', 'context', 'contract', 'create', 'data', 'definition', 'derived', 'distribution', 'economic', 'environment', 'established', 'estimate', 'evidence', 'export', 'factor', 'financial', 'formula', 'function', 'identified', 'income', 'indicate', 'individual', 'interpretation', 'involved', 'issue', 'labor', 'legal', 'legislation', 'major', 'method', 'occur', 'percent', 'period', 'policy', 'principle', 'procedure', 'process', 'required', 'research', 'response', 'role', 'section', 'sector', 'significant', 'similar', 'source', 'specific', 'structure', 'theory', 'variable', 'traditional', 'technology', 'technique', 'hypothesis', 'methodology', 'quantitative', 'synthesis', 'paradigm', 'empirical', 'theoretical', 'phenomenon', 'anthropology', 'psychology', 'sociology', 'philosophy', 'linguistics', 'cognitive', 'semantic', 'pragmatic', 'syntax', 'morphology', 'etymology' ]); // 添加技术术语表 const techTerms = new Set([ 'api', 'json', 'xml', 'html', 'css', 'javascript', 'python', 'java', 'ruby', 'php', 'sql', 'mysql', 'postgresql', 'mongodb', 'redis', 'nginx', 'apache', 'docker', 'kubernetes', 'git', 'github', 'gitlab', 'bitbucket', 'npm', 'yarn', 'webpack', 'babel', 'react', 'vue', 'angular', 'node', 'express', 'django', 'flask', 'spring', 'hibernate', 'maven', 'gradle', 'jenkins', 'travis', 'aws', 'azure', 'gcp', 'linux', 'unix', 'windows', 'macos', 'ios', 'android', 'kotlin', 'swift', 'objective-c', 'rust', 'golang', 'scala' ]); // 添加品牌名称表 const brandNames = new Set([ 'apple', 'google', 'microsoft', 'amazon', 'facebook', 'twitter', 'instagram', 'linkedin', 'youtube', 'netflix', 'spotify', 'uber', 'airbnb', 'tesla', 'samsung', 'sony', 'lg', 'hp', 'dell', 'lenovo', 'asus', 'acer', 'intel', 'amd', 'nvidia', 'qualcomm', 'cisco', 'oracle', 'ibm', 'salesforce', 'adobe', 'autodesk', 'wordpress', 'shopify', 'stripe' ]); // 创建悬浮提示框 let tooltip = document.createElement('div'); tooltip.className = 'word-tooltip'; document.body.appendChild(tooltip); let tooltipTimeout = null; let hideTooltipTimeout = null; // 修改处理鼠标悬浮事件 let hoverTimeout = null; async function handleWordHover(event) { const target = event.target; if (!target.classList.contains('difficult-word')) return; // 如果已经在翻译中,则不重复触发 if (target.dataset.translating === 'true') return; // 如果之前翻译失败,不再重复请求 if (target.dataset.translationFailed === 'true') { tooltip.innerHTML = target.textContent; tooltip.style.opacity = '1'; positionTooltip(target.getBoundingClientRect()); return; } // 清除之前的定时器 clearTimeout(hoverTimeout); clearTimeout(tooltipTimeout); // 添加延迟,防止快速划过时触发翻译 hoverTimeout = setTimeout(async () => { const word = target.textContent; const rect = target.getBoundingClientRect(); // 立即显示加载状态 tooltip.innerHTML = '翻译中...'; tooltip.style.opacity = '1'; positionTooltip(rect); try { // 标记正在翻译 target.dataset.translating = 'true'; const translationHtml = await translationQueue.translateWithCache(word); if (translationHtml && translationHtml !== word) { tooltip.innerHTML = translationHtml; // 清除失败标记(如果存在) target.dataset.translationFailed = 'false'; } else { // 如果返回原单词,说明翻译失败 tooltip.innerHTML = word; target.dataset.translationFailed = 'true'; } } catch (error) { console.error('翻译失败:', error); tooltip.innerHTML = word; target.dataset.translationFailed = 'true'; tooltip.style.opacity = '1'; } finally { // 移除翻译中标记 target.dataset.translating = 'false'; } }, 200); // 200ms的延迟 } // 修改处理鼠标移出事件 function handleWordLeave(event) { const target = event.target; // 移除翻译中标记 if (target.dataset.translating) { target.dataset.translating = 'false'; } hideTooltip(); } // 修改hideTooltip函数 function hideTooltip() { if (tooltip) { tooltip.style.opacity = '0'; } } // 添加滚动事件监听 window.addEventListener('scroll', hideTooltip, { passive: true }); // 添加窗口大小改变事件监听 window.addEventListener('resize', hideTooltip, { passive: true }); // 获取元素的背景色 function getBackgroundColor(element) { let bg = window.getComputedStyle(element).backgroundColor; let parent = element.parentElement; // 修复语法错误:添加缺少的右括号 while (parent && (bg === 'transparent' || bg === 'rgba(0, 0, 0, 0)')) { bg = window.getComputedStyle(parent).backgroundColor; parent = parent.parentElement; } // 如果找不到背景色,返回默认白色 if (bg === 'transparent' || bg === 'rgba(0, 0, 0, 0)') { return 'rgb(255, 255, 255)'; } return bg; } // 调整背景色透明度和亮度 function adjustBackgroundColor(bgColor) { // 解析RGB值 const rgb = bgColor.match(/\d+/g).map(Number); // 计算亮度 const brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; // 根据亮度调整文字颜色 tooltip.style.color = brightness > 125 ? '#333' : '#fff'; // 返回半透明的背景色 return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.95)`; } // 定位提示框 function positionTooltip(rect) { const tooltipRect = tooltip.getBoundingClientRect(); let left = rect.left; let top = rect.bottom + 5; // 确保提示框不超出视窗 if (left + tooltipRect.width > window.innerWidth) { left = window.innerWidth - tooltipRect.width - 5; } if (top + tooltipRect.height > window.innerHeight) { top = rect.top - tooltipRect.height - 5; } tooltip.style.left = `${left}px`; tooltip.style.top = `${top}px`; } // 扩展不规则动词变化表 const irregularVerbs = { 'am': 'be', 'is': 'be', 'are': 'be', 'was': 'be', 'were': 'be', 'been': 'be', 'has': 'have', 'have': 'have', 'had': 'have', 'does': 'do', 'did': 'do', 'goes': 'go', 'went': 'go', 'gone': 'go', 'makes': 'make', 'made': 'make', 'came': 'come', 'come': 'come', 'took': 'take', 'taken': 'take', 'saw': 'see', 'seen': 'see', 'knew': 'know', 'known': 'know', 'grew': 'grow', 'grown': 'grow', 'wrote': 'write', 'written': 'write', 'drove': 'drive', 'driven': 'drive', 'spoke': 'speak', 'spoken': 'speak', 'broke': 'break', 'broken': 'break', 'chose': 'choose', 'chosen': 'choose', 'wore': 'wear', 'worn': 'wear', 'drew': 'draw', 'drawn': 'draw', 'flew': 'fly', 'flown': 'fly', 'threw': 'throw', 'thrown': 'throw', 'began': 'begin', 'begun': 'begin', 'swam': 'swim', 'swum': 'swim', 'sang': 'sing', 'sung': 'sing', 'rang': 'ring', 'rung': 'ring', 'drank': 'drink', 'drunk': 'drink', 'sank': 'sink', 'sunk': 'sink', 'sprang': 'spring', 'sprung': 'spring', 'bore': 'bear', 'borne': 'bear', 'shook': 'shake', 'shaken': 'shake', 'stole': 'steal', 'stolen': 'steal', 'rose': 'rise', 'risen': 'rise', 'fell': 'fall', 'fallen': 'fall', 'froze': 'freeze', 'frozen': 'freeze' }; // 改进的词形还原规则 const lemmatizationRules = [ // 复数规则 [/([^aeiou])ies$/, '$1y'], // cities → city [/(ss|sh|ch|x|z)es$/, '$1'], // boxes → box [/([^s])s$/, '$1'], // dogs → dog // 动词规则 [/([^aeiou])ied$/, '$1y'], // studied → study [/([aeiou][^aeiou])ed$/, '$1'], // saved → save [/(.[^aeiou])ed$/, '$1'], // jumped → jump [/([^e])ing$/, '$1'], // running → run [/ying$/, 'ie'], // lying → lie // 形容词规则 [/([^aeiou])est$/, '$1'], // biggest → big [/([aeiou][^aeiou])er$/, '$1'], // safer → safe ]; // 词形还原函数 function lemmatize(word) { let baseForm = word.toLowerCase(); // 1. 检查不规则动词 if (irregularVerbs[baseForm]) { return irregularVerbs[baseForm]; } // 2. 应用词形还原规则 for (const [pattern, replacement] of lemmatizationRules) { if (pattern.test(baseForm)) { const lemmatized = baseForm.replace(pattern, replacement); // 如果还原后的词在词频表中存在,则采用 if (wordFrequencyList && wordFrequencyList.has(lemmatized)) { return lemmatized; } } } return baseForm; } let wordFrequencyList = null; let processedNodes = new WeakMap(); let isProcessing = false; let isInitialized = false; // 改进单词提取正则 const wordRegex = /(?= this.limit) { this.cache.delete(this.cache.keys().next().value); } this.cache.set(key, value); } has(key) { return this.cache.has(key); } } // 创建翻译缓存实例 const translationCache = new LRUCache(1000); // 创建翻译队列处理器 class TranslationQueue { constructor() { this.queue = []; this.processing = false; this.cache = new Map(); this.processingWords = new Set(); this.loadCacheFromStorage(); } // 从本地存储加载缓存 loadCacheFromStorage() { try { const storedCache = localStorage.getItem('translation_cache'); if (storedCache) { this.cache = new Map(JSON.parse(storedCache)); } } catch (error) { console.error('加载翻译缓存失败:', error); this.cache = new Map(); } } // 保存缓存到本地存储 saveCacheToStorage() { try { localStorage.setItem('translation_cache', JSON.stringify(Array.from(this.cache.entries())) ); } catch (error) { console.error('保存翻译缓存失败:', error); if (error.name === 'QuotaExceededError') { this.pruneCache(); } } } // 清理缓存 pruneCache() { const entries = Array.from(this.cache.entries()); if (entries.length > 10000) { // 限制缓存大小 this.cache = new Map(entries.slice(-10000)); this.saveCacheToStorage(); } } // 获取翻译(优先从缓存获取) async translateWithCache(word) { const normalizedWord = word.toLowerCase(); // 检查缓存 if (this.cache.has(normalizedWord)) { return this.cache.get(normalizedWord); } try { const translationHtml = await fetchTranslation(word); if (translationHtml && translationHtml !== word) { this.cache.set(normalizedWord, translationHtml); this.saveCacheToStorage(); return translationHtml; } } catch (error) { console.error(`翻译失败: ${word}`, error); throw error; // 抛出错误以便上层处理 } return word; } } const translationQueue = new TranslationQueue(); // 加载词频表 async function loadWordFrequencyList() { try { const response = await fetch('https://raw.githubusercontent.com/mahavivo/vocabulary/master/vocabulary/COCA60000.txt'); const text = await response.text(); wordFrequencyList = new Map( text.split(/\s+/) .map((word, index) => [word.toLowerCase(), index + 1]) ); if (config.debugMode) { console.log('词频表加载完成:', wordFrequencyList.size, '个单词'); } } catch (error) { console.error('词频表加载失败:', error); } } // 获取词频得分(0-1) function getFrequencyScore(word) { const baseWord = lemmatize(word); const frequency = wordFrequencyList.get(baseWord) || 20000; return Math.min(1, frequency / 20000); // 频率越高得分越低 } // 获取学术词汇得分 function getAcademicScore(word) { const lowerWord = word.toLowerCase(); if (academicWords.has(lowerWord)) return 1; // 检查学术词缀 const academicSuffixes = [ 'ology', 'tion', 'sion', 'ism', 'ity', 'ment', 'ance', 'ence', 'able', 'ible', 'ative', 'itive', 'ical', 'istic', 'ious', 'eous', 'uous' ]; for (const suffix of academicSuffixes) { if (lowerWord.endsWith(suffix)) return 0.7; } return 0; } // 获取结构复杂度得分 function getStructuralComplexity(word) { let score = 0; // 长度分数 if (word.length >= 8) score += 0.3; else if (word.length >= 6) score += 0.2; // 音节数分数 const syllables = countSyllables(word); if (syllables >= 3) score += 0.2; // 连续辅音 if (/[^aeiouy]{3,}/i.test(word)) score += 0.2; // 特殊字符 if (/['-]/.test(word)) score += 0.1; // 复杂前缀 if (/^(anti|counter|inter|intra|multi|over|under|super|trans)/.test(word)) { score += 0.2; } return Math.min(score, 1); } // 计算音节数 function countSyllables(word) { word = word.toLowerCase(); word = word.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, ''); word = word.replace(/^y/, ''); return word.match(/[aeiouy]{1,2}/g)?.length || 1; } // 改进的生词判断函数 function isDifficultWord(word, context = '') { if (!word || !wordFrequencyList) return false; // 清理单词 word = word.trim(); const originalWord = word; word = word.toLowerCase().replace(/[^a-z'-]/g, ''); // 基本过滤 if (word.length < config.minWordLength) return false; if (commonWords.has(word)) return false; if (techTerms.has(word)) return false; if (brandNames.has(word)) return false; // 排除缩写 if (config.excludeAbbreviations) { if (commonAbbreviations.has(word)) return false; if (word.length <= config.maxAbbrLength && (originalWord === originalWord.toUpperCase() || /\d/.test(originalWord))) { return false; } } // 排除专有名词 if (config.excludeProperNouns && /^[A-Z]/.test(originalWord) && !isStartOfSentence(originalWord)) { return false; } // 多维度评分 const frequencyScore = getFrequencyScore(word); const academicScore = getAcademicScore(word); const complexityScore = getStructuralComplexity(word); // 根据上下文调整权重 let weights = { freq: 0.7, academic: 0.2, complex: 0.1 }; // 调整权重,更偏向频率评分 if (isInTechnicalContext(context)) { weights = { freq: 0.6, academic: 0.3, complex: 0.1 }; } else if (isInAcademicContext(context)) { weights = { freq: 0.5, academic: 0.4, complex: 0.1 }; } const difficultyScore = frequencyScore * weights.freq + academicScore * weights.academic + complexityScore * weights.complex; return difficultyScore > 0.6; // 降低难度阈值 } // 判断是否在技术上下文中 function isInTechnicalContext(text) { const techKeywords = [ 'code', 'programming', 'software', 'developer', 'algorithm', 'database', 'framework', 'api', 'function', 'method', 'class', 'object' ]; return techKeywords.some(kw => text.toLowerCase().includes(kw)); } // 判断是否在学术上下文中 function isInAcademicContext(text) { const academicKeywords = [ 'research', 'study', 'analysis', 'theory', 'experiment', 'hypothesis', 'methodology', 'conclusion', 'literature', 'journal' ]; return academicKeywords.some(kw => text.toLowerCase().includes(kw)); } // 延迟函数 const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // 优化翻译函数 async function fetchTranslation(word, retryCount = 0) { try { // 首先检查缓存 const cachedTranslation = translationCache.get(word.toLowerCase()); if (cachedTranslation) return cachedTranslation; // 尝试主API const response = await fetch(`${config.apiUrl}${encodeURIComponent(word)}`, { headers: config.apiHeaders }); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); // 如果主API无法找到单词,尝试备用API if (data.code === -2 || !data.data) { return await fetchFallbackTranslation(word); } const result = { phonetic: data.data.usphone || '', translations: [] }; // 收集所有词性和释义 const posMap = new Map(); // 处理 translations 数组 if (data.data.translations) { data.data.translations.forEach(trans => { if (!posMap.has(trans.pos)) { posMap.set(trans.pos, new Set()); } posMap.get(trans.pos).add(trans.tran_cn); }); } // 处理 relWords 数组 if (data.data.relWords) { data.data.relWords.forEach(relWord => { if (!posMap.has(relWord.Pos)) { posMap.set(relWord.Pos, new Set()); } relWord.Hwds.forEach(hwd => { if (hwd.tran) { hwd.tran.split(';').forEach(tran => { posMap.get(relWord.Pos).add(tran.trim()); }); } }); }); } // 如果没有任何翻译,尝试备用API if (posMap.size === 0) { return await fetchFallbackTranslation(word); } // 转换为最终的translations数组 for (const [pos, trans] of posMap.entries()) { result.translations.push({ pos: pos, tran_cn: Array.from(trans).join(';') }); } // 格式化翻译结果为HTML let translationHtml = ''; if (result.phonetic) { translationHtml += `