From abcf2baad66391f9ca5e937ee32e6fb0403f45e5 Mon Sep 17 00:00:00 2001 From: Gabe Date: Sat, 15 Nov 2025 14:25:05 +0800 Subject: [PATCH] feat: supports plain text translation --- src/common.js | 2 +- src/config/i18n.js | 7 +++++++ src/libs/translator.js | 28 ++++++++++++++++++++++++---- src/views/Popup/PopupCont.js | 20 +++++++++++++++++++- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/common.js b/src/common.js index d54a242..fb9887c 100644 --- a/src/common.js +++ b/src/common.js @@ -121,7 +121,7 @@ export async function run(isUserscript = false) { // if (document?.documentElement?.tagName?.toUpperCase() !== "HTML") { // return; // } - if (!document?.contentType?.includes("html")) { + if (!document?.contentType?.includes("text")) { return; } diff --git a/src/config/i18n.js b/src/config/i18n.js index bf0fefa..3d5f18c 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -2584,6 +2584,13 @@ export const I18N = { ja: `原言語と目標言語が同じ場合、字幕は処理されません`, ko: `원본 언어와 대상 언어가 동일한 경우, 자막은 처리되지 않습니다`, }, + plain_text_translate: { + zh: `纯文本翻译`, + en: `Plain text translation`, + zh_TW: `純文字翻譯`, + ja: `プレーンテキスト翻訳`, + ko: `순수 텍스트 번역`, + }, }; export const newI18n = (lang) => (key) => I18N[key]?.[lang] || ""; diff --git a/src/libs/translator.js b/src/libs/translator.js index 78e387d..f7320af 100644 --- a/src/libs/translator.js +++ b/src/libs/translator.js @@ -274,8 +274,7 @@ export class Translator { data, datalist, embed, head, iframe, input, noscript, map, object, option, param, picture, progress, select, script, style, track, textarea, template, - video, wbr, .notranslate, [contenteditable='true'], [translate='no'], - ${Translator.KISS_IGNORE_SELECTOR}`; + video, wbr, .notranslate, [contenteditable='true'], [translate='no']`; #setting; // 设置选项 #rule; // 规则 @@ -322,6 +321,10 @@ export class Translator { // 忽略元素 get #ignoreSelector() { + if (this.#rule.isPlainText) { + return Translator.KISS_IGNORE_SELECTOR; + } + if (this.#rule.autoScan === "false") { return `${Translator.KISS_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`; } @@ -353,7 +356,7 @@ export class Translator { constructor({ rule = {}, setting = {}, favWords = [] }) { this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting }; - this.#rule = { ...Translator.DEFAULT_RULE, ...rule }; + this.#rule = { ...Translator.DEFAULT_RULE, ...rule, isPlainText: false }; this.#favWords = favWords; this.#apisMap = new Map( this.#setting.transApis.map((api) => [api.apiSlug, api]) @@ -413,6 +416,19 @@ export class Translator { // 注入JS/CSS this.#initInjector(); + // 纯文本预处理 + if (this.#rule.isPlainText) { + document + .querySelectorAll("pre") + .forEach( + (pre) => + (pre.innerHTML = pre.innerHTML?.replace( + /(?:\r\n|\r|\n)/g, + "
" + )) + ); + } + // 查找根节点并扫描 document .querySelectorAll(this.#rule.rootsSelector || "body") @@ -1786,7 +1802,11 @@ export class Translator { this.#rule[key] !== newRule[key] ) { this.#rule[key] = newRule[key]; - if (key === "autoScan" || key === "hasShadowroot") { + if ( + key === "autoScan" || + key === "hasShadowroot" || + key === "isPlainText" + ) { needsRescan = true; } else { hasChanged = true; diff --git a/src/views/Popup/PopupCont.js b/src/views/Popup/PopupCont.js index 310dbb3..84ca143 100644 --- a/src/views/Popup/PopupCont.js +++ b/src/views/Popup/PopupCont.js @@ -112,7 +112,10 @@ export default function PopupCont({ const handleChange = async (e) => { try { - const { name, value } = e.target; + let { name, value, checked } = e.target; + if (name === "isPlainText") { + value = checked; + } setRule((pre) => ({ ...pre, [name]: value })); if (!processActions) { @@ -204,6 +207,7 @@ export default function PopupCont({ transOnly, hasRichText, hasShadowroot, + isPlainText = false, } = rule; return ( @@ -322,6 +326,20 @@ export default function PopupCont({ label={i18n("input_translate")} /> + + + } + label={i18n("plain_text_translate")} + /> +