feat: support translate all now

This commit is contained in:
Gabe
2025-10-01 21:18:53 +08:00
parent 3c5ffc045f
commit c993c15c92
8 changed files with 56 additions and 38 deletions

View File

@@ -201,7 +201,7 @@ export async function run(isUserscript = false) {
// 翻译网页 // 翻译网页
const rule = await matchRule(href, setting); const rule = await matchRule(href, setting);
const translator = new Translator(rule, setting); const translator = new Translator(rule, setting, isUserscript);
// 适配iframe // 适配iframe
if (isIframe) { if (isIframe) {

View File

@@ -355,9 +355,9 @@ export const I18N = {
zh_TW: `滾動載入翻譯(建議)`, zh_TW: `滾動載入翻譯(建議)`,
}, },
mk_pageopen: { mk_pageopen: {
zh: `页面打开全部翻译`, zh: `立即全部翻译`,
en: `Page Open`, en: `Translate all now`,
zh_TW: `頁面開啟全部翻譯`, zh_TW: `立即全部翻譯`,
}, },
mk_mouseover: { mk_mouseover: {
zh: `鼠标悬停翻译`, zh: `鼠标悬停翻译`,

View File

@@ -73,7 +73,7 @@ background: linear-gradient(
export const DEFAULT_SELECTOR = export const DEFAULT_SELECTOR =
"h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend"; "h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend";
export const DEFAULT_IGNORE_SELECTOR = export const DEFAULT_IGNORE_SELECTOR =
"aside, button, footer, form, header, pre, mark, nav"; "aside, button, footer, form, pre, mark, nav";
export const DEFAULT_KEEP_SELECTOR = `a:has(code)`; export const DEFAULT_KEEP_SELECTOR = `a:has(code)`;
export const DEFAULT_RULE = { export const DEFAULT_RULE = {
pattern: "", // 匹配网址 pattern: "", // 匹配网址

View File

@@ -149,4 +149,5 @@ export const DEFAULT_SETTING = {
langDetector: OPT_TRANS_MICROSOFT, // 远程语言识别服务 langDetector: OPT_TRANS_MICROSOFT, // 远程语言识别服务
mouseHoverSetting: DEFAULT_MOUSE_HOVER_SETTING, // 鼠标悬停翻译 mouseHoverSetting: DEFAULT_MOUSE_HOVER_SETTING, // 鼠标悬停翻译
preInit: true, // 是否预加载脚本 preInit: true, // 是否预加载脚本
transAllnow: false, // 是否立即全部翻译
}; };

View File

@@ -51,9 +51,9 @@ export const tryDetectLang = async (
try { try {
const res = await browser?.i18n?.detectLanguage(text); const res = await browser?.i18n?.detectLanguage(text);
const lang = res?.languages?.[0]?.language; const lang = res?.languages?.[0]?.language;
if (OPT_LANGS_MAP.has(lang)) { if (lang && OPT_LANGS_MAP.has(lang)) {
deLang = lang; deLang = lang;
} else if (lang.startsWith("zh")) { } else if (lang?.startsWith("zh")) {
deLang = "zh-CN"; deLang = "zh-CN";
} }
} catch (err) { } catch (err) {

View File

@@ -309,7 +309,7 @@ export class Translator {
return `${Translator.BUILTIN_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`; return `${Translator.BUILTIN_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
} }
constructor(rule = {}, setting = {}, isUserscript) { constructor(rule = {}, setting = {}, isUserscript = false) {
this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting }; this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting };
this.#rule = { ...Translator.DEFAULT_RULE, ...rule }; this.#rule = { ...Translator.DEFAULT_RULE, ...rule };
this.#apiSetting = this.#apiSetting =
@@ -767,6 +767,16 @@ export class Translator {
// 开始/重新监控节点 // 开始/重新监控节点
#startObserveNode(node) { #startObserveNode(node) {
if (
!this.#observedNodes.has(node) &&
this.#enabled &&
this.#setting.transAllnow
) {
this.#observedNodes.add(node);
this.#processNode(node);
return;
}
// 未监控 // 未监控
if (!this.#observedNodes.has(node)) { if (!this.#observedNodes.has(node)) {
this.#observedNodes.add(node); this.#observedNodes.add(node);
@@ -992,7 +1002,10 @@ export class Translator {
wrapper.appendChild(inner); wrapper.appendChild(inner);
nodes[nodes.length - 1].after(wrapper); nodes[nodes.length - 1].after(wrapper);
this.#translationNodes.set(wrapper, nodes); this.#translationNodes.set(wrapper, {
nodes,
isHide: transOnly === "true",
});
const currentRunId = this.#runId; const currentRunId = this.#runId;
try { try {
@@ -1210,8 +1223,9 @@ export class Translator {
this.#processedNodes.delete(el.parentElement); this.#processedNodes.delete(el.parentElement);
// 如果是仅显示译文模式,先恢复原文 // 如果是仅显示译文模式,先恢复原文
if (this.#rule.transOnly === "true") { const { nodes, isHide } = this.#translationNodes.get(el) || {};
this.#restoreOriginal(el); if (isHide) {
this.#restoreOriginal(el, nodes);
} }
this.#translationNodes.delete(el); this.#translationNodes.delete(el);
@@ -1219,8 +1233,7 @@ export class Translator {
} }
// 恢复原文 // 恢复原文
#restoreOriginal(el) { #restoreOriginal(el, nodes) {
const nodes = this.#translationNodes.get(el);
if (nodes) { if (nodes) {
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
nodes.forEach((n) => frag.appendChild(n)); nodes.forEach((n) => frag.appendChild(n));
@@ -1231,23 +1244,27 @@ export class Translator {
// 移除多个节点 // 移除多个节点
#removeNodes(nodes) { #removeNodes(nodes) {
const frag = document.createDocumentFragment(); if (nodes) {
nodes.forEach((n) => frag.appendChild(n)); const frag = document.createDocumentFragment();
nodes.forEach((n) => frag.appendChild(n));
}
} }
// 切换译文和双语显示 // 切换译文和双语显示
#toggleTranslationOnly(node, transOnly) { #toggleTranslationOnly(node, transOnly) {
this.#findTranslationWrappers(node).forEach((el) => { this.#findTranslationWrappers(node).forEach((el) => {
const br = el.querySelector(":scope > br"); const br = el.querySelector(":scope > br");
const { nodes } = this.#translationNodes.get(el) || {};
if (transOnly === "true") { if (transOnly === "true") {
// 双语变为仅译文 // 双语变为仅译文
if (br) br.hidden = true; if (br) br.hidden = true;
const nodes = this.#translationNodes.get(el) || [];
this.#removeNodes(nodes); this.#removeNodes(nodes);
this.#translationNodes.set(el, { nodes, isHide: true });
} else { } else {
// 仅译文变为双语 // 仅译文变为双语
this.#restoreOriginal(el);
if (br) br.hidden = false; if (br) br.hidden = false;
this.#restoreOriginal(el, nodes);
this.#translationNodes.set(el, { nodes, isHide: false });
} }
}); });
} }
@@ -1396,7 +1413,11 @@ export class Translator {
this.#runId++; this.#runId++;
if (this.#isInitialized) { if (this.#isInitialized) {
this.#reIOViewNodes(); if (this.#setting.transAllnow) {
this.rescan();
} else {
this.#reIOViewNodes();
}
} else { } else {
this.#init(); this.#init();
} }
@@ -1507,7 +1528,7 @@ export class Translator {
} }
} }
if (needsRescan) { if (needsRescan || (this.#enabled && this.#setting.transAllnow)) {
this.rescan(); this.rescan();
return; return;
} }

View File

@@ -474,25 +474,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
onChange={handleChange} onChange={handleChange}
/> />
</Grid> </Grid>
{/* <Grid item xs={12} sm={12} md={6} lg={3}>
<TextField
select
size="small"
fullWidth
name="transTiming"
value={transTiming}
label={i18n("trigger_mode")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
{OPT_TIMING_ALL.map((item) => (
<MenuItem key={item} value={item}>
{i18n(item)}
</MenuItem>
))}
</TextField>
</Grid> */}
</Grid> </Grid>
</Box> </Box>

View File

@@ -122,6 +122,7 @@ export default function Settings() {
preInit = true, preInit = true,
skipLangs = [], skipLangs = [],
detectRemote = true, detectRemote = true,
transAllnow = false,
} = setting; } = setting;
const { isHide = false, fabClickAction = 0 } = fab || {}; const { isHide = false, fabClickAction = 0 } = fab || {};
@@ -300,6 +301,20 @@ export default function Settings() {
<MenuItem value={2}>{i18n("secondary_context_menus")}</MenuItem> <MenuItem value={2}>{i18n("secondary_context_menus")}</MenuItem>
</TextField> </TextField>
</Grid> </Grid>
<Grid item xs={12} sm={12} md={6} lg={3}>
<TextField
select
size="small"
fullWidth
name="transAllnow"
value={transAllnow}
label={i18n("trigger_mode")}
onChange={handleChange}
>
<MenuItem value={false}>{i18n("mk_pagescroll")}</MenuItem>
<MenuItem value={true}>{i18n("mk_pageopen")}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={3}> <Grid item xs={12} sm={12} md={6} lg={3}>
<TextField <TextField
select select