Codedex题目翻译器
- 内容介绍
- 文章标签
- 相关推荐
// ==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!

