Codedex题目翻译器

2026-04-29 09:262阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

// ==UserScript== // @name Codedex 自动翻译(Chrome Translator API / Google 降级) // @namespace https://github.com/yourname/codedex-translator // @version 5.0.0 // @description 检测到 .challenge-container 加载完毕后自动翻译,无需按钮 // @author You // @match https://www.codedex.io/* // @grant GM_xmlhttpRequest // @connect translate.googleapis.com // @run-at document-idle // ==/UserScript== (async function () { 'use strict'; const CONFIG = { sourceLang: 'en', targetLang: 'zh', targetLangGoogle: 'zh-CN', }; const SELECTORS = [ '.challenge-container h2', '.challenge-container h3', '.challenge-container p', '.challenge-container li', '.challenge-container .clone p', ]; // ─── Chrome Translator API ─────────────────────────────────────────────────── let chromeTranslator = null; async function initChromeTranslator() { if ('Translator' in self) { try { const avail = await Translator.availability({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (avail === 'no') return false; chromeTranslator = await Translator.create({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); console.log('[Codedex Translator] Chrome Translator 就绪:', avail); return true; } catch (e) { console.warn('[Codedex Translator] Chrome Translator 失败:', e); return false; } } // 旧版兼容 Chrome 138-140 if (window.ai?.translator) { try { const canDo = await window.ai.translator.canTranslate({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (canDo === 'no') return false; chromeTranslator = await window.ai.translator.createTranslator({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (chromeTranslator?.ready) await chromeTranslator.ready; return true; } catch (e) { console.warn('[Codedex Translator] window.ai.translator 失败:', e); return false; } } return false; } // ─── Chrome LanguageDetector ───────────────────────────────────────────────── let langDetector = null; async function initLangDetector() { if ('LanguageDetector' in self) { try { const avail = await LanguageDetector.availability(); if (avail === 'no') return; langDetector = await LanguageDetector.create(); console.log('[Codedex Translator] LanguageDetector 就绪:', avail); } catch (e) { console.warn('[Codedex Translator] LanguageDetector 初始化失败:', e); } } } // 取容器内一段代表性文本做语言检测,返回 true 表示需要翻译 async function shouldTranslate(container) { if (!langDetector) return true; // 没有检测器就默认翻译 const sample = container.innerText.trim().slice(0, 200); if (!sample) return false; try { const results = await langDetector.detect(sample); const top = results?.[0]?.detectedLanguage; console.log('[Codedex Translator] 检测语言:', top); return top === 'en'; // 只翻译英文内容 } catch { return true; } } // ─── Google Translate 降级 ─────────────────────────────────────────────────── function translateWithGoogle(text) { return new Promise((resolve) => { if (!text.trim()) return resolve(text); const url = `https://translate.googleapis.com/translate_a/single` + `?client=gtx&sl=${CONFIG.sourceLang}&tl=${CONFIG.targetLangGoogle}` + `&dt=t&q=${encodeURIComponent(text)}`; GM_xmlhttpRequest({ method: 'GET', url, onload(res) { try { const data = JSON.parse(res.responseText); resolve(data[0].filter(Boolean).map((s) => s[0]).join('') || text); } catch { resolve(text); } }, onerror() { resolve(text); }, ontimeout() { resolve(text); }, timeout: 8000, }); }); } // ─── 统一翻译 ───────────────────────────────────────────────────────────────── let mode = 'none'; async function translateText(text) { if (!text.trim()) return text; if (mode === 'chrome') { try { const res = await chromeTranslator.translate(text); if (res) return res; } catch (e) { console.warn('[Codedex Translator] Chrome 翻译单条失败,降级:', e); } } return translateWithGoogle(text); } // ─── DOM 翻译 ───────────────────────────────────────────────────────────────── const DONE = 'data-cdx-done'; const ORIG = 'data-cdx-orig'; function collectNodes() { const nodes = []; SELECTORS.forEach((sel) => document.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) nodes.push(el); }) ); return nodes; } // 收集叶子文本节点,跳过代码块 const SKIP_TAGS = new Set(['CODE', 'PRE', 'KBD', 'VAR', 'SAMP']); function collectLeafTextNodes(el) { const result = []; function walk(node) { if (node.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(node.tagName)) return; if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.trim()) result.push(node); return; } node.childNodes.forEach(walk); } walk(el); return result; } async function translateContainer(container) { const els = []; SELECTORS.forEach((sel) => container.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) els.push(el); }) ); SELECTORS.forEach((sel) => { if (container.matches?.(sel) && !container.hasAttribute(DONE) && container.innerText.trim()) els.push(container); }); if (!els.length) return; for (const el of els) { el.setAttribute(DONE, '1'); const leafNodes = collectLeafTextNodes(el); for (const textNode of leafNodes) { const orig = textNode.textContent.trim(); if (!orig) continue; const result = await translateText(orig); if (result && result !== orig) { textNode.textContent = textNode.textContent.replace(orig, result); } } } } // ─── 等待 .challenge-container 出现/内容变化后翻译 ────────────────────────── let translateTimer = null; async function maybeTranslate(container) { if (await shouldTranslate(container)) { await translateContainer(container); } } function scheduleTranslate(container) { clearTimeout(translateTimer); translateTimer = setTimeout(() => maybeTranslate(container), 300); } function waitAndTranslate() { const existing = document.querySelector('.challenge-container'); if (existing) maybeTranslate(existing); // 监听 .challenge-container 的新增 和 内部文本内容变化 const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { // 新节点插入:检查是否是或包含 challenge-container for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; if (node.classList?.contains('challenge-container')) { scheduleTranslate(node); return; } const inner = node.querySelector?.('.challenge-container'); if (inner) { scheduleTranslate(inner); return; } } // 文本内容变化:如果变化发生在 challenge-container 内部 if (mutation.type === 'characterData' || mutation.type === 'childList') { const container = mutation.target.closest?.('.challenge-container') ?? (mutation.target.nodeType === Node.TEXT_NODE ? mutation.target.parentElement?.closest('.challenge-container') : null); if (container) { scheduleTranslate(container); return; } } } }); observer.observe(document.body, { childList: true, subtree: true, characterData: true, }); } // ─── 启动 ───────────────────────────────────────────────────────────────────── const ok = await initChromeTranslator(); mode = ok ? 'chrome' : 'google'; await initLangDetector(); console.log('[Codedex Translator] 模式:', mode); waitAndTranslate(); })();

效果:
image2088×1166 205 KB

优先使用Chrome Translate API(Chrome 本地翻译)服务
其次才是Google API,所以支持Translator API的情况下速度更快

Enjoy it!

网友解答:
--【壹】--:

// ==UserScript== // @name Codedex 自动翻译(Chrome Translator API / Google 降级) // @namespace https://github.com/yourname/codedex-translator // @version 5.0.0 // @description 检测到 .challenge-container 加载完毕后自动翻译,无需按钮 // @author You // @match https://www.codedex.io/* // @grant GM_xmlhttpRequest // @connect translate.googleapis.com // @run-at document-idle // ==/UserScript== (async function () { 'use strict'; const CONFIG = { sourceLang: 'en', targetLang: 'zh', targetLangGoogle: 'zh-CN', }; const SELECTORS = [ '.challenge-container h2', '.challenge-container h3', '.challenge-container p', '.challenge-container li', '.challenge-container .clone p', ]; // ─── Chrome Translator API ─────────────────────────────────────────────────── let chromeTranslator = null; async function initChromeTranslator() { if ('Translator' in self) { try { const avail = await Translator.availability({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (avail === 'no') return false; chromeTranslator = await Translator.create({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); console.log('[Codedex Translator] Chrome Translator 就绪:', avail); return true; } catch (e) { console.warn('[Codedex Translator] Chrome Translator 失败:', e); return false; } } // 旧版兼容 Chrome 138-140 if (window.ai?.translator) { try { const canDo = await window.ai.translator.canTranslate({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (canDo === 'no') return false; chromeTranslator = await window.ai.translator.createTranslator({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (chromeTranslator?.ready) await chromeTranslator.ready; return true; } catch (e) { console.warn('[Codedex Translator] window.ai.translator 失败:', e); return false; } } return false; } // ─── Chrome LanguageDetector ───────────────────────────────────────────────── let langDetector = null; async function initLangDetector() { if ('LanguageDetector' in self) { try { const avail = await LanguageDetector.availability(); if (avail === 'no') return; langDetector = await LanguageDetector.create(); console.log('[Codedex Translator] LanguageDetector 就绪:', avail); } catch (e) { console.warn('[Codedex Translator] LanguageDetector 初始化失败:', e); } } } // 取容器内一段代表性文本做语言检测,返回 true 表示需要翻译 async function shouldTranslate(container) { if (!langDetector) return true; // 没有检测器就默认翻译 const sample = container.innerText.trim().slice(0, 200); if (!sample) return false; try { const results = await langDetector.detect(sample); const top = results?.[0]?.detectedLanguage; console.log('[Codedex Translator] 检测语言:', top); return top === 'en'; // 只翻译英文内容 } catch { return true; } } // ─── Google Translate 降级 ─────────────────────────────────────────────────── function translateWithGoogle(text) { return new Promise((resolve) => { if (!text.trim()) return resolve(text); const url = `https://translate.googleapis.com/translate_a/single` + `?client=gtx&sl=${CONFIG.sourceLang}&tl=${CONFIG.targetLangGoogle}` + `&dt=t&q=${encodeURIComponent(text)}`; GM_xmlhttpRequest({ method: 'GET', url, onload(res) { try { const data = JSON.parse(res.responseText); resolve(data[0].filter(Boolean).map((s) => s[0]).join('') || text); } catch { resolve(text); } }, onerror() { resolve(text); }, ontimeout() { resolve(text); }, timeout: 8000, }); }); } // ─── 统一翻译 ───────────────────────────────────────────────────────────────── let mode = 'none'; async function translateText(text) { if (!text.trim()) return text; if (mode === 'chrome') { try { const res = await chromeTranslator.translate(text); if (res) return res; } catch (e) { console.warn('[Codedex Translator] Chrome 翻译单条失败,降级:', e); } } return translateWithGoogle(text); } // ─── DOM 翻译 ───────────────────────────────────────────────────────────────── const DONE = 'data-cdx-done'; const ORIG = 'data-cdx-orig'; function collectNodes() { const nodes = []; SELECTORS.forEach((sel) => document.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) nodes.push(el); }) ); return nodes; } // 收集叶子文本节点,跳过代码块 const SKIP_TAGS = new Set(['CODE', 'PRE', 'KBD', 'VAR', 'SAMP']); function collectLeafTextNodes(el) { const result = []; function walk(node) { if (node.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(node.tagName)) return; if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.trim()) result.push(node); return; } node.childNodes.forEach(walk); } walk(el); return result; } async function translateContainer(container) { const els = []; SELECTORS.forEach((sel) => container.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) els.push(el); }) ); SELECTORS.forEach((sel) => { if (container.matches?.(sel) && !container.hasAttribute(DONE) && container.innerText.trim()) els.push(container); }); if (!els.length) return; for (const el of els) { el.setAttribute(DONE, '1'); const leafNodes = collectLeafTextNodes(el); for (const textNode of leafNodes) { const orig = textNode.textContent.trim(); if (!orig) continue; const result = await translateText(orig); if (result && result !== orig) { textNode.textContent = textNode.textContent.replace(orig, result); } } } } // ─── 等待 .challenge-container 出现/内容变化后翻译 ────────────────────────── let translateTimer = null; async function maybeTranslate(container) { if (await shouldTranslate(container)) { await translateContainer(container); } } function scheduleTranslate(container) { clearTimeout(translateTimer); translateTimer = setTimeout(() => maybeTranslate(container), 300); } function waitAndTranslate() { const existing = document.querySelector('.challenge-container'); if (existing) maybeTranslate(existing); // 监听 .challenge-container 的新增 和 内部文本内容变化 const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { // 新节点插入:检查是否是或包含 challenge-container for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; if (node.classList?.contains('challenge-container')) { scheduleTranslate(node); return; } const inner = node.querySelector?.('.challenge-container'); if (inner) { scheduleTranslate(inner); return; } } // 文本内容变化:如果变化发生在 challenge-container 内部 if (mutation.type === 'characterData' || mutation.type === 'childList') { const container = mutation.target.closest?.('.challenge-container') ?? (mutation.target.nodeType === Node.TEXT_NODE ? mutation.target.parentElement?.closest('.challenge-container') : null); if (container) { scheduleTranslate(container); return; } } } }); observer.observe(document.body, { childList: true, subtree: true, characterData: true, }); } // ─── 启动 ───────────────────────────────────────────────────────────────────── const ok = await initChromeTranslator(); mode = ok ? 'chrome' : 'google'; await initLangDetector(); console.log('[Codedex Translator] 模式:', mode); waitAndTranslate(); })();

效果:
image2088×1166 205 KB

优先使用Chrome Translate API(Chrome 本地翻译)服务
其次才是Google API,所以支持Translator API的情况下速度更快

Enjoy it!

标签:软件开发
问题描述:

// ==UserScript== // @name Codedex 自动翻译(Chrome Translator API / Google 降级) // @namespace https://github.com/yourname/codedex-translator // @version 5.0.0 // @description 检测到 .challenge-container 加载完毕后自动翻译,无需按钮 // @author You // @match https://www.codedex.io/* // @grant GM_xmlhttpRequest // @connect translate.googleapis.com // @run-at document-idle // ==/UserScript== (async function () { 'use strict'; const CONFIG = { sourceLang: 'en', targetLang: 'zh', targetLangGoogle: 'zh-CN', }; const SELECTORS = [ '.challenge-container h2', '.challenge-container h3', '.challenge-container p', '.challenge-container li', '.challenge-container .clone p', ]; // ─── Chrome Translator API ─────────────────────────────────────────────────── let chromeTranslator = null; async function initChromeTranslator() { if ('Translator' in self) { try { const avail = await Translator.availability({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (avail === 'no') return false; chromeTranslator = await Translator.create({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); console.log('[Codedex Translator] Chrome Translator 就绪:', avail); return true; } catch (e) { console.warn('[Codedex Translator] Chrome Translator 失败:', e); return false; } } // 旧版兼容 Chrome 138-140 if (window.ai?.translator) { try { const canDo = await window.ai.translator.canTranslate({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (canDo === 'no') return false; chromeTranslator = await window.ai.translator.createTranslator({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (chromeTranslator?.ready) await chromeTranslator.ready; return true; } catch (e) { console.warn('[Codedex Translator] window.ai.translator 失败:', e); return false; } } return false; } // ─── Chrome LanguageDetector ───────────────────────────────────────────────── let langDetector = null; async function initLangDetector() { if ('LanguageDetector' in self) { try { const avail = await LanguageDetector.availability(); if (avail === 'no') return; langDetector = await LanguageDetector.create(); console.log('[Codedex Translator] LanguageDetector 就绪:', avail); } catch (e) { console.warn('[Codedex Translator] LanguageDetector 初始化失败:', e); } } } // 取容器内一段代表性文本做语言检测,返回 true 表示需要翻译 async function shouldTranslate(container) { if (!langDetector) return true; // 没有检测器就默认翻译 const sample = container.innerText.trim().slice(0, 200); if (!sample) return false; try { const results = await langDetector.detect(sample); const top = results?.[0]?.detectedLanguage; console.log('[Codedex Translator] 检测语言:', top); return top === 'en'; // 只翻译英文内容 } catch { return true; } } // ─── Google Translate 降级 ─────────────────────────────────────────────────── function translateWithGoogle(text) { return new Promise((resolve) => { if (!text.trim()) return resolve(text); const url = `https://translate.googleapis.com/translate_a/single` + `?client=gtx&sl=${CONFIG.sourceLang}&tl=${CONFIG.targetLangGoogle}` + `&dt=t&q=${encodeURIComponent(text)}`; GM_xmlhttpRequest({ method: 'GET', url, onload(res) { try { const data = JSON.parse(res.responseText); resolve(data[0].filter(Boolean).map((s) => s[0]).join('') || text); } catch { resolve(text); } }, onerror() { resolve(text); }, ontimeout() { resolve(text); }, timeout: 8000, }); }); } // ─── 统一翻译 ───────────────────────────────────────────────────────────────── let mode = 'none'; async function translateText(text) { if (!text.trim()) return text; if (mode === 'chrome') { try { const res = await chromeTranslator.translate(text); if (res) return res; } catch (e) { console.warn('[Codedex Translator] Chrome 翻译单条失败,降级:', e); } } return translateWithGoogle(text); } // ─── DOM 翻译 ───────────────────────────────────────────────────────────────── const DONE = 'data-cdx-done'; const ORIG = 'data-cdx-orig'; function collectNodes() { const nodes = []; SELECTORS.forEach((sel) => document.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) nodes.push(el); }) ); return nodes; } // 收集叶子文本节点,跳过代码块 const SKIP_TAGS = new Set(['CODE', 'PRE', 'KBD', 'VAR', 'SAMP']); function collectLeafTextNodes(el) { const result = []; function walk(node) { if (node.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(node.tagName)) return; if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.trim()) result.push(node); return; } node.childNodes.forEach(walk); } walk(el); return result; } async function translateContainer(container) { const els = []; SELECTORS.forEach((sel) => container.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) els.push(el); }) ); SELECTORS.forEach((sel) => { if (container.matches?.(sel) && !container.hasAttribute(DONE) && container.innerText.trim()) els.push(container); }); if (!els.length) return; for (const el of els) { el.setAttribute(DONE, '1'); const leafNodes = collectLeafTextNodes(el); for (const textNode of leafNodes) { const orig = textNode.textContent.trim(); if (!orig) continue; const result = await translateText(orig); if (result && result !== orig) { textNode.textContent = textNode.textContent.replace(orig, result); } } } } // ─── 等待 .challenge-container 出现/内容变化后翻译 ────────────────────────── let translateTimer = null; async function maybeTranslate(container) { if (await shouldTranslate(container)) { await translateContainer(container); } } function scheduleTranslate(container) { clearTimeout(translateTimer); translateTimer = setTimeout(() => maybeTranslate(container), 300); } function waitAndTranslate() { const existing = document.querySelector('.challenge-container'); if (existing) maybeTranslate(existing); // 监听 .challenge-container 的新增 和 内部文本内容变化 const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { // 新节点插入:检查是否是或包含 challenge-container for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; if (node.classList?.contains('challenge-container')) { scheduleTranslate(node); return; } const inner = node.querySelector?.('.challenge-container'); if (inner) { scheduleTranslate(inner); return; } } // 文本内容变化:如果变化发生在 challenge-container 内部 if (mutation.type === 'characterData' || mutation.type === 'childList') { const container = mutation.target.closest?.('.challenge-container') ?? (mutation.target.nodeType === Node.TEXT_NODE ? mutation.target.parentElement?.closest('.challenge-container') : null); if (container) { scheduleTranslate(container); return; } } } }); observer.observe(document.body, { childList: true, subtree: true, characterData: true, }); } // ─── 启动 ───────────────────────────────────────────────────────────────────── const ok = await initChromeTranslator(); mode = ok ? 'chrome' : 'google'; await initLangDetector(); console.log('[Codedex Translator] 模式:', mode); waitAndTranslate(); })();

效果:
image2088×1166 205 KB

优先使用Chrome Translate API(Chrome 本地翻译)服务
其次才是Google API,所以支持Translator API的情况下速度更快

Enjoy it!

网友解答:
--【壹】--:

// ==UserScript== // @name Codedex 自动翻译(Chrome Translator API / Google 降级) // @namespace https://github.com/yourname/codedex-translator // @version 5.0.0 // @description 检测到 .challenge-container 加载完毕后自动翻译,无需按钮 // @author You // @match https://www.codedex.io/* // @grant GM_xmlhttpRequest // @connect translate.googleapis.com // @run-at document-idle // ==/UserScript== (async function () { 'use strict'; const CONFIG = { sourceLang: 'en', targetLang: 'zh', targetLangGoogle: 'zh-CN', }; const SELECTORS = [ '.challenge-container h2', '.challenge-container h3', '.challenge-container p', '.challenge-container li', '.challenge-container .clone p', ]; // ─── Chrome Translator API ─────────────────────────────────────────────────── let chromeTranslator = null; async function initChromeTranslator() { if ('Translator' in self) { try { const avail = await Translator.availability({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (avail === 'no') return false; chromeTranslator = await Translator.create({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); console.log('[Codedex Translator] Chrome Translator 就绪:', avail); return true; } catch (e) { console.warn('[Codedex Translator] Chrome Translator 失败:', e); return false; } } // 旧版兼容 Chrome 138-140 if (window.ai?.translator) { try { const canDo = await window.ai.translator.canTranslate({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (canDo === 'no') return false; chromeTranslator = await window.ai.translator.createTranslator({ sourceLanguage: CONFIG.sourceLang, targetLanguage: CONFIG.targetLang, }); if (chromeTranslator?.ready) await chromeTranslator.ready; return true; } catch (e) { console.warn('[Codedex Translator] window.ai.translator 失败:', e); return false; } } return false; } // ─── Chrome LanguageDetector ───────────────────────────────────────────────── let langDetector = null; async function initLangDetector() { if ('LanguageDetector' in self) { try { const avail = await LanguageDetector.availability(); if (avail === 'no') return; langDetector = await LanguageDetector.create(); console.log('[Codedex Translator] LanguageDetector 就绪:', avail); } catch (e) { console.warn('[Codedex Translator] LanguageDetector 初始化失败:', e); } } } // 取容器内一段代表性文本做语言检测,返回 true 表示需要翻译 async function shouldTranslate(container) { if (!langDetector) return true; // 没有检测器就默认翻译 const sample = container.innerText.trim().slice(0, 200); if (!sample) return false; try { const results = await langDetector.detect(sample); const top = results?.[0]?.detectedLanguage; console.log('[Codedex Translator] 检测语言:', top); return top === 'en'; // 只翻译英文内容 } catch { return true; } } // ─── Google Translate 降级 ─────────────────────────────────────────────────── function translateWithGoogle(text) { return new Promise((resolve) => { if (!text.trim()) return resolve(text); const url = `https://translate.googleapis.com/translate_a/single` + `?client=gtx&sl=${CONFIG.sourceLang}&tl=${CONFIG.targetLangGoogle}` + `&dt=t&q=${encodeURIComponent(text)}`; GM_xmlhttpRequest({ method: 'GET', url, onload(res) { try { const data = JSON.parse(res.responseText); resolve(data[0].filter(Boolean).map((s) => s[0]).join('') || text); } catch { resolve(text); } }, onerror() { resolve(text); }, ontimeout() { resolve(text); }, timeout: 8000, }); }); } // ─── 统一翻译 ───────────────────────────────────────────────────────────────── let mode = 'none'; async function translateText(text) { if (!text.trim()) return text; if (mode === 'chrome') { try { const res = await chromeTranslator.translate(text); if (res) return res; } catch (e) { console.warn('[Codedex Translator] Chrome 翻译单条失败,降级:', e); } } return translateWithGoogle(text); } // ─── DOM 翻译 ───────────────────────────────────────────────────────────────── const DONE = 'data-cdx-done'; const ORIG = 'data-cdx-orig'; function collectNodes() { const nodes = []; SELECTORS.forEach((sel) => document.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) nodes.push(el); }) ); return nodes; } // 收集叶子文本节点,跳过代码块 const SKIP_TAGS = new Set(['CODE', 'PRE', 'KBD', 'VAR', 'SAMP']); function collectLeafTextNodes(el) { const result = []; function walk(node) { if (node.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(node.tagName)) return; if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.trim()) result.push(node); return; } node.childNodes.forEach(walk); } walk(el); return result; } async function translateContainer(container) { const els = []; SELECTORS.forEach((sel) => container.querySelectorAll(sel).forEach((el) => { if (!el.hasAttribute(DONE) && el.innerText.trim()) els.push(el); }) ); SELECTORS.forEach((sel) => { if (container.matches?.(sel) && !container.hasAttribute(DONE) && container.innerText.trim()) els.push(container); }); if (!els.length) return; for (const el of els) { el.setAttribute(DONE, '1'); const leafNodes = collectLeafTextNodes(el); for (const textNode of leafNodes) { const orig = textNode.textContent.trim(); if (!orig) continue; const result = await translateText(orig); if (result && result !== orig) { textNode.textContent = textNode.textContent.replace(orig, result); } } } } // ─── 等待 .challenge-container 出现/内容变化后翻译 ────────────────────────── let translateTimer = null; async function maybeTranslate(container) { if (await shouldTranslate(container)) { await translateContainer(container); } } function scheduleTranslate(container) { clearTimeout(translateTimer); translateTimer = setTimeout(() => maybeTranslate(container), 300); } function waitAndTranslate() { const existing = document.querySelector('.challenge-container'); if (existing) maybeTranslate(existing); // 监听 .challenge-container 的新增 和 内部文本内容变化 const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { // 新节点插入:检查是否是或包含 challenge-container for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; if (node.classList?.contains('challenge-container')) { scheduleTranslate(node); return; } const inner = node.querySelector?.('.challenge-container'); if (inner) { scheduleTranslate(inner); return; } } // 文本内容变化:如果变化发生在 challenge-container 内部 if (mutation.type === 'characterData' || mutation.type === 'childList') { const container = mutation.target.closest?.('.challenge-container') ?? (mutation.target.nodeType === Node.TEXT_NODE ? mutation.target.parentElement?.closest('.challenge-container') : null); if (container) { scheduleTranslate(container); return; } } } }); observer.observe(document.body, { childList: true, subtree: true, characterData: true, }); } // ─── 启动 ───────────────────────────────────────────────────────────────────── const ok = await initChromeTranslator(); mode = ok ? 'chrome' : 'google'; await initLangDetector(); console.log('[Codedex Translator] 模式:', mode); waitAndTranslate(); })();

效果:
image2088×1166 205 KB

优先使用Chrome Translate API(Chrome 本地翻译)服务
其次才是Google API,所以支持Translator API的情况下速度更快

Enjoy it!

标签:软件开发