feat: supports plain text translation
This commit is contained in:
@@ -121,7 +121,7 @@ export async function run(isUserscript = false) {
|
|||||||
// if (document?.documentElement?.tagName?.toUpperCase() !== "HTML") {
|
// if (document?.documentElement?.tagName?.toUpperCase() !== "HTML") {
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
if (!document?.contentType?.includes("html")) {
|
if (!document?.contentType?.includes("text")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2584,6 +2584,13 @@ export const I18N = {
|
|||||||
ja: `原言語と目標言語が同じ場合、字幕は処理されません`,
|
ja: `原言語と目標言語が同じ場合、字幕は処理されません`,
|
||||||
ko: `원본 언어와 대상 언어가 동일한 경우, 자막은 처리되지 않습니다`,
|
ko: `원본 언어와 대상 언어가 동일한 경우, 자막은 처리되지 않습니다`,
|
||||||
},
|
},
|
||||||
|
plain_text_translate: {
|
||||||
|
zh: `纯文本翻译`,
|
||||||
|
en: `Plain text translation`,
|
||||||
|
zh_TW: `純文字翻譯`,
|
||||||
|
ja: `プレーンテキスト翻訳`,
|
||||||
|
ko: `순수 텍스트 번역`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newI18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
export const newI18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
||||||
|
|||||||
@@ -274,8 +274,7 @@ export class Translator {
|
|||||||
data, datalist, embed, head, iframe, input, noscript, map,
|
data, datalist, embed, head, iframe, input, noscript, map,
|
||||||
object, option, param, picture, progress,
|
object, option, param, picture, progress,
|
||||||
select, script, style, track, textarea, template,
|
select, script, style, track, textarea, template,
|
||||||
video, wbr, .notranslate, [contenteditable='true'], [translate='no'],
|
video, wbr, .notranslate, [contenteditable='true'], [translate='no']`;
|
||||||
${Translator.KISS_IGNORE_SELECTOR}`;
|
|
||||||
|
|
||||||
#setting; // 设置选项
|
#setting; // 设置选项
|
||||||
#rule; // 规则
|
#rule; // 规则
|
||||||
@@ -322,6 +321,10 @@ export class Translator {
|
|||||||
|
|
||||||
// 忽略元素
|
// 忽略元素
|
||||||
get #ignoreSelector() {
|
get #ignoreSelector() {
|
||||||
|
if (this.#rule.isPlainText) {
|
||||||
|
return Translator.KISS_IGNORE_SELECTOR;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.#rule.autoScan === "false") {
|
if (this.#rule.autoScan === "false") {
|
||||||
return `${Translator.KISS_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
|
return `${Translator.KISS_IGNORE_SELECTOR}, ${this.#rule.ignoreSelector}`;
|
||||||
}
|
}
|
||||||
@@ -353,7 +356,7 @@ export class Translator {
|
|||||||
|
|
||||||
constructor({ rule = {}, setting = {}, favWords = [] }) {
|
constructor({ rule = {}, setting = {}, favWords = [] }) {
|
||||||
this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting };
|
this.#setting = { ...Translator.DEFAULT_OPTIONS, ...setting };
|
||||||
this.#rule = { ...Translator.DEFAULT_RULE, ...rule };
|
this.#rule = { ...Translator.DEFAULT_RULE, ...rule, isPlainText: false };
|
||||||
this.#favWords = favWords;
|
this.#favWords = favWords;
|
||||||
this.#apisMap = new Map(
|
this.#apisMap = new Map(
|
||||||
this.#setting.transApis.map((api) => [api.apiSlug, api])
|
this.#setting.transApis.map((api) => [api.apiSlug, api])
|
||||||
@@ -413,6 +416,19 @@ export class Translator {
|
|||||||
// 注入JS/CSS
|
// 注入JS/CSS
|
||||||
this.#initInjector();
|
this.#initInjector();
|
||||||
|
|
||||||
|
// 纯文本预处理
|
||||||
|
if (this.#rule.isPlainText) {
|
||||||
|
document
|
||||||
|
.querySelectorAll("pre")
|
||||||
|
.forEach(
|
||||||
|
(pre) =>
|
||||||
|
(pre.innerHTML = pre.innerHTML?.replace(
|
||||||
|
/(?:\r\n|\r|\n)/g,
|
||||||
|
"<br />"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 查找根节点并扫描
|
// 查找根节点并扫描
|
||||||
document
|
document
|
||||||
.querySelectorAll(this.#rule.rootsSelector || "body")
|
.querySelectorAll(this.#rule.rootsSelector || "body")
|
||||||
@@ -1786,7 +1802,11 @@ export class Translator {
|
|||||||
this.#rule[key] !== newRule[key]
|
this.#rule[key] !== newRule[key]
|
||||||
) {
|
) {
|
||||||
this.#rule[key] = newRule[key];
|
this.#rule[key] = newRule[key];
|
||||||
if (key === "autoScan" || key === "hasShadowroot") {
|
if (
|
||||||
|
key === "autoScan" ||
|
||||||
|
key === "hasShadowroot" ||
|
||||||
|
key === "isPlainText"
|
||||||
|
) {
|
||||||
needsRescan = true;
|
needsRescan = true;
|
||||||
} else {
|
} else {
|
||||||
hasChanged = true;
|
hasChanged = true;
|
||||||
|
|||||||
@@ -112,7 +112,10 @@ export default function PopupCont({
|
|||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleChange = async (e) => {
|
||||||
try {
|
try {
|
||||||
const { name, value } = e.target;
|
let { name, value, checked } = e.target;
|
||||||
|
if (name === "isPlainText") {
|
||||||
|
value = checked;
|
||||||
|
}
|
||||||
setRule((pre) => ({ ...pre, [name]: value }));
|
setRule((pre) => ({ ...pre, [name]: value }));
|
||||||
|
|
||||||
if (!processActions) {
|
if (!processActions) {
|
||||||
@@ -204,6 +207,7 @@ export default function PopupCont({
|
|||||||
transOnly,
|
transOnly,
|
||||||
hasRichText,
|
hasRichText,
|
||||||
hasShadowroot,
|
hasShadowroot,
|
||||||
|
isPlainText = false,
|
||||||
} = rule;
|
} = rule;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -322,6 +326,20 @@ export default function PopupCont({
|
|||||||
label={i18n("input_translate")}
|
label={i18n("input_translate")}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
name="isPlainText"
|
||||||
|
value={!isPlainText}
|
||||||
|
checked={isPlainText}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n("plain_text_translate")}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
|||||||
Reference in New Issue
Block a user