fix: lang detect

This commit is contained in:
Gabe
2025-09-27 21:21:56 +08:00
parent b1142b88f1
commit fffa448425
12 changed files with 238 additions and 181 deletions

View File

@@ -3,10 +3,8 @@ import { fetchData } from "../libs/fetch";
import { import {
URL_CACHE_TRAN, URL_CACHE_TRAN,
KV_SALT_SYNC, KV_SALT_SYNC,
OPT_LANGS_BAIDU, OPT_LANGS_TO_SPEC,
OPT_LANGS_TENCENT, OPT_LANGS_SPEC_DEFAULT,
OPT_LANGS_SPECIAL,
OPT_LANGS_MICROSOFT,
API_SPE_TYPES, API_SPE_TYPES,
DEFAULT_API_SETTING, DEFAULT_API_SETTING,
} from "../config"; } from "../config";
@@ -41,6 +39,13 @@ export const apiSyncData = async (url, key, data) =>
*/ */
export const apiFetch = (url) => fetchData(url); export const apiFetch = (url) => fetchData(url);
/**
* Microsoft token
* @returns
*/
export const apiMsAuth = async () =>
fetchData("https://edge.microsoft.com/translate/auth");
/** /**
* Google语言识别 * Google语言识别
* @param {*} text * @param {*} text
@@ -78,7 +83,7 @@ export const apiGoogleLangdetect = async (text) => {
* @returns * @returns
*/ */
export const apiMicrosoftLangdetect = async (text) => { export const apiMicrosoftLangdetect = async (text) => {
const [token] = await msAuth(); const token = await msAuth();
const input = const input =
"https://api-edge.cognitive.microsofttranslator.com/detect?api-version=3.0"; "https://api-edge.cognitive.microsofttranslator.com/detect?api-version=3.0";
const init = { const init = {
@@ -93,9 +98,9 @@ export const apiMicrosoftLangdetect = async (text) => {
useCache: true, useCache: true,
}); });
if (res[0].language) { if (res?.[0]?.language) {
await putHttpCachePolyfill(input, init, res); await putHttpCachePolyfill(input, init, res);
return OPT_LANGS_MICROSOFT.get(res[0].language) ?? res[0].language; return res[0].language;
} }
return ""; return "";
@@ -119,9 +124,9 @@ export const apiBaiduLangdetect = async (text) => {
}; };
const res = await fetchData(input, init, { useCache: true }); const res = await fetchData(input, init, { useCache: true });
if (res.error === 0) { if (res?.error === 0) {
await putHttpCachePolyfill(input, init, res); await putHttpCachePolyfill(input, init, res);
return OPT_LANGS_BAIDU.get(res.lan) ?? res.lan; return res.lan;
} }
return ""; return "";
@@ -145,7 +150,7 @@ export const apiBaiduSuggest = async (text) => {
}; };
const res = await fetchData(input, init, { useCache: true }); const res = await fetchData(input, init, { useCache: true });
if (res.errno === 0) { if (res?.errno === 0) {
await putHttpCachePolyfill(input, init, res); await putHttpCachePolyfill(input, init, res);
return res.data; return res.data;
} }
@@ -175,21 +180,26 @@ export const apiTencentLangdetect = async (text) => {
const body = JSON.stringify({ const body = JSON.stringify({
header: { header: {
fn: "text_analysis", fn: "text_analysis",
client_key:
"browser-chrome-110.0.0-Mac OS-df4bd4c5-a65d-44b2-a40f-42f34f3535f2-1677486696487",
}, },
text, text,
}); });
const init = { const init = {
headers: { headers: {
"Content-type": "application/json", "Content-type": "application/json",
"user-agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
referer: "https://transmart.qq.com/zh-CN/index",
}, },
method: "POST", method: "POST",
body, body,
}; };
const res = await fetchData(input, init, { useCache: true }); const res = await fetchData(input, init, { useCache: true });
if (res.language) { if (res?.language) {
await putHttpCachePolyfill(input, init, res); await putHttpCachePolyfill(input, init, res);
return OPT_LANGS_TENCENT.get(res.language) ?? res.language; return res.language;
} }
return ""; return "";
@@ -202,7 +212,7 @@ export const apiTencentLangdetect = async (text) => {
*/ */
export const apiTranslate = async ({ export const apiTranslate = async ({
text, text,
fromLang, fromLang = "auto",
toLang, toLang,
apiSetting = DEFAULT_API_SETTING, apiSetting = DEFAULT_API_SETTING,
docInfo = {}, docInfo = {},
@@ -214,8 +224,8 @@ export const apiTranslate = async ({
} }
const { apiType, apiSlug, useBatchFetch } = apiSetting; const { apiType, apiSlug, useBatchFetch } = apiSetting;
const langMap = OPT_LANGS_SPECIAL[apiType]; const langMap = OPT_LANGS_TO_SPEC[apiType] || OPT_LANGS_SPEC_DEFAULT;
const from = langMap.get(fromLang) ?? langMap.get("auto"); const from = langMap.get(fromLang);
const to = langMap.get(toLang); const to = langMap.get(toLang);
if (!to) { if (!to) {
kissLog(`target lang: ${toLang} not support`); kissLog(`target lang: ${toLang} not support`);
@@ -283,8 +293,7 @@ export const apiTranslate = async ({
} }
} }
// const isSame = srLang && (to.includes(srLang) || srLang.includes(to)); const isSame = fromLang !== "auto" && srLang === to;
const isSame = srLang && srLang.slice(0, 2) === to.slice(0, 2);
// 插入缓存 // 插入缓存
if (useCache && trText) { if (useCache && trText) {

View File

@@ -686,7 +686,7 @@ export const parseTransRes = async (
case OPT_TRANS_MICROSOFT: case OPT_TRANS_MICROSOFT:
return res?.map((item) => [ return res?.map((item) => [
item.translations.map((item) => item.text).join(" "), item.translations.map((item) => item.text).join(" "),
item.detectedLanguage.language, item.detectedLanguage?.language,
]); ]);
case OPT_TRANS_DEEPL: case OPT_TRANS_DEEPL:
return res?.translations?.map((item) => [ return res?.translations?.map((item) => [
@@ -811,7 +811,7 @@ export const handleTranslate = async ({
let token = ""; let token = "";
if (apiType === OPT_TRANS_MICROSOFT) { if (apiType === OPT_TRANS_MICROSOFT) {
[token] = await msAuth(); token = await msAuth();
} }
const [input, init, userMsg] = await genTransReq({ const [input, init, userMsg] = await genTransReq({

View File

@@ -23,10 +23,9 @@ import {
import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage"; import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
import { trySyncSettingAndRules } from "./libs/sync"; import { trySyncSettingAndRules } from "./libs/sync";
import { fetchHandle } from "./libs/fetch"; import { fetchHandle } from "./libs/fetch";
import { getHttpCache, putHttpCache } from "./libs/cache"; import { tryClearCaches, getHttpCache, putHttpCache } from "./libs/cache";
import { sendTabMsg } from "./libs/msg"; import { sendTabMsg } from "./libs/msg";
import { trySyncAllSubRules } from "./libs/subRules"; import { trySyncAllSubRules } from "./libs/subRules";
import { tryClearCaches } from "./libs";
import { saveRule } from "./libs/rules"; import { saveRule } from "./libs/rules";
import { getCurTabId } from "./libs/msg"; import { getCurTabId } from "./libs/msg";
import { injectInlineJs, injectInternalCss } from "./libs/injector"; import { injectInlineJs, injectInternalCss } from "./libs/injector";

View File

@@ -180,48 +180,61 @@ export const OPT_LANGS_TO = [
["uk", "Ukrainian - Українська"], ["uk", "Ukrainian - Українська"],
["vi", "Vietnamese - Tiếng Việt"], ["vi", "Vietnamese - Tiếng Việt"],
]; ];
export const OPT_LANGS_LIST = OPT_LANGS_TO.map(([lang]) => lang);
export const OPT_LANGS_FROM = [["auto", "Auto-detect"], ...OPT_LANGS_TO]; export const OPT_LANGS_FROM = [["auto", "Auto-detect"], ...OPT_LANGS_TO];
export const OPT_LANGS_SPECIAL = { export const OPT_LANGS_MAP = new Map(OPT_LANGS_TO);
[OPT_TRANS_GOOGLE]: new Map(OPT_LANGS_FROM.map(([key]) => [key, key])),
[OPT_TRANS_GOOGLE_2]: new Map(OPT_LANGS_FROM.map(([key]) => [key, key])), // CODE->名称
export const OPT_LANGS_SPEC_NAME = new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
);
export const OPT_LANGS_SPEC_DEFAULT = new Map(
OPT_LANGS_FROM.map(([key]) => [key, key])
);
export const OPT_LANGS_SPEC_DEFAULT_UC = new Map(
OPT_LANGS_FROM.map(([key]) => [key, key.toUpperCase()])
);
export const OPT_LANGS_TO_SPEC = {
[OPT_TRANS_GOOGLE]: OPT_LANGS_SPEC_DEFAULT,
[OPT_TRANS_GOOGLE_2]: OPT_LANGS_SPEC_DEFAULT,
[OPT_TRANS_MICROSOFT]: new Map([ [OPT_TRANS_MICROSOFT]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]), ...OPT_LANGS_SPEC_DEFAULT,
["auto", ""], ["auto", ""],
["zh-CN", "zh-Hans"], ["zh-CN", "zh-Hans"],
["zh-TW", "zh-Hant"], ["zh-TW", "zh-Hant"],
]), ]),
[OPT_TRANS_DEEPL]: new Map([ [OPT_TRANS_DEEPL]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key.toUpperCase()]), ...OPT_LANGS_SPEC_DEFAULT_UC,
["auto", ""], ["auto", ""],
["zh-CN", "ZH"], ["zh-CN", "ZH"],
["zh-TW", "ZH"], ["zh-TW", "ZH"],
]), ]),
[OPT_TRANS_DEEPLFREE]: new Map([ [OPT_TRANS_DEEPLFREE]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key.toUpperCase()]), ...OPT_LANGS_SPEC_DEFAULT_UC,
["auto", "auto"], ["auto", "auto"],
["zh-CN", "ZH"], ["zh-CN", "ZH"],
["zh-TW", "ZH"], ["zh-TW", "ZH"],
]), ]),
[OPT_TRANS_DEEPLX]: new Map([ [OPT_TRANS_DEEPLX]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key.toUpperCase()]), ...OPT_LANGS_SPEC_DEFAULT_UC,
["auto", "auto"], ["auto", "auto"],
["zh-CN", "ZH"], ["zh-CN", "ZH"],
["zh-TW", "ZH"], ["zh-TW", "ZH"],
]), ]),
[OPT_TRANS_NIUTRANS]: new Map([ [OPT_TRANS_NIUTRANS]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]), ...OPT_LANGS_SPEC_DEFAULT,
["auto", "auto"], ["auto", "auto"],
["zh-CN", "zh"], ["zh-CN", "zh"],
["zh-TW", "cht"], ["zh-TW", "cht"],
]), ]),
[OPT_TRANS_VOLCENGINE]: new Map([ [OPT_TRANS_VOLCENGINE]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]), ...OPT_LANGS_SPEC_DEFAULT,
["auto", "auto"], ["auto", "auto"],
["zh-CN", "zh"], ["zh-CN", "zh"],
["zh-TW", "zh-Hant"], ["zh-TW", "zh-Hant"],
]), ]),
[OPT_TRANS_BAIDU]: new Map([ [OPT_TRANS_BAIDU]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]), ...OPT_LANGS_SPEC_DEFAULT,
["zh-CN", "zh"], ["zh-CN", "zh"],
["zh-TW", "cht"], ["zh-TW", "cht"],
["ar", "ara"], ["ar", "ara"],
@@ -269,62 +282,34 @@ export const OPT_LANGS_SPECIAL = {
["id", "id"], ["id", "id"],
["vi", "vi"], ["vi", "vi"],
]), ]),
[OPT_TRANS_OPENAI]: new Map( [OPT_TRANS_OPENAI]: OPT_LANGS_SPEC_DEFAULT,
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]]) [OPT_TRANS_GEMINI]: OPT_LANGS_SPEC_DEFAULT,
), [OPT_TRANS_GEMINI_2]: OPT_LANGS_SPEC_DEFAULT,
[OPT_TRANS_GEMINI]: new Map( [OPT_TRANS_CLAUDE]: OPT_LANGS_SPEC_DEFAULT,
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]]) [OPT_TRANS_OLLAMA]: OPT_LANGS_SPEC_DEFAULT,
), [OPT_TRANS_OPENROUTER]: OPT_LANGS_SPEC_DEFAULT,
[OPT_TRANS_GEMINI_2]: new Map( [OPT_TRANS_CLOUDFLAREAI]: OPT_LANGS_SPEC_DEFAULT,
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]]) [OPT_TRANS_CUSTOMIZE]: OPT_LANGS_SPEC_DEFAULT,
),
[OPT_TRANS_CLAUDE]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_OLLAMA]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_OPENROUTER]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_CLOUDFLAREAI]: new Map([
["auto", ""],
["zh-CN", "chinese"],
["zh-TW", "chinese"],
["en", "english"],
["ar", "arabic"],
["de", "german"],
["ru", "russian"],
["fr", "french"],
["pt", "portuguese"],
["ja", "japanese"],
["es", "spanish"],
["hi", "hindi"],
]),
[OPT_TRANS_CUSTOMIZE]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]),
]),
}; };
export const OPT_LANGS_LIST = OPT_LANGS_TO.map(([lang]) => lang);
export const OPT_LANGS_MICROSOFT = new Map( const specToCode = (m) =>
Array.from(OPT_LANGS_SPECIAL[OPT_TRANS_MICROSOFT].entries()).map(([k, v]) => [ new Map(
v, Array.from(m.entries()).map(([k, v]) => {
k, if (v === "") {
]) return ["auto", "auto"];
); }
export const OPT_LANGS_BAIDU = new Map( if (v === "zh" || v === "ZH") {
Array.from(OPT_LANGS_SPECIAL[OPT_TRANS_BAIDU].entries()).map(([k, v]) => [ return [v, "zh-CN"];
v, }
k, return [v, k];
]) })
); );
export const OPT_LANGS_TENCENT = new Map(
Array.from(OPT_LANGS_SPECIAL[OPT_TRANS_TENCENT].entries()).map(([k, v]) => [ // 名称->CODE
v, export const OPT_LANGS_TO_CODE = {};
k, Object.entries(OPT_LANGS_TO_SPEC).forEach(([t, m]) => {
]) OPT_LANGS_TO_CODE[t] = specToCode(m);
); });
OPT_LANGS_TENCENT.set("zh", "zh-CN");
const defaultSystemPrompt = `Act as a translation API. Output a single raw JSON object only. No extra text or fences. const defaultSystemPrompt = `Act as a translation API. Output a single raw JSON object only. No extra text or fences.

View File

@@ -8,7 +8,7 @@ import { useCallback } from "react";
* @returns * @returns
*/ */
export function useRules() { export function useRules() {
const { data: list, save } = useStorage( const { data: list = [], save } = useStorage(
STOKEY_RULES, STOKEY_RULES,
DEFAULT_RULES, DEFAULT_RULES,
KV_RULES_KEY KV_RULES_KEY

View File

@@ -1,6 +1,6 @@
import { getMsauth, setMsauth } from "./storage"; import { getMsauth, setMsauth } from "./storage";
import { fetchData } from "./fetch";
import { kissLog } from "./log"; import { kissLog } from "./log";
import { apiMsAuth } from "../apis";
const parseMSToken = (token) => { const parseMSToken = (token) => {
try { try {
@@ -16,28 +16,55 @@ const parseMSToken = (token) => {
* @returns * @returns
*/ */
const _msAuth = () => { const _msAuth = () => {
let { token, exp } = {}; let tokenPromise = null;
const EXPIRATION_MS = 1000;
const fetchNewToken = async () => {
try {
const now = Date.now();
// 1. 查询storage缓存
const storageToken = await getMsauth();
if (storageToken) {
const storageExp = parseMSToken(storageToken);
const storageExpiresAt = storageExp * 1000;
if (storageExpiresAt > now + EXPIRATION_MS) {
return { token: storageToken, expiresAt: storageExpiresAt };
}
}
// 2. 缓存没有或失效,查询接口
const apiToken = await apiMsAuth();
if (!apiToken) {
throw new Error("Failed to fetch ms token");
}
const apiExp = parseMSToken(apiToken);
const apiExpiresAt = apiExp * 1000;
await setMsauth(apiToken);
return { token: apiToken, expiresAt: apiExpiresAt };
} catch (error) {
kissLog("get msauth failed", error);
throw error;
}
};
return async () => { return async () => {
// 查询内存缓存 // 检查是否有缓存的 Promise
const now = Date.now(); if (tokenPromise) {
if (token && exp * 1000 > now + 1000) { try {
return [token, exp]; const cachedResult = await tokenPromise;
if (cachedResult.expiresAt > Date.now() + EXPIRATION_MS) {
return cachedResult.token;
}
} catch (error) {
//
}
} }
// 查询storage缓存 tokenPromise = fetchNewToken();
const res = await getMsauth(); const result = await tokenPromise;
token = res?.token; return result.token;
exp = res?.exp;
if (token && exp * 1000 > now + 1000) {
return [token, exp];
}
// 缓存没有或失效,查询接口
token = await fetchData("https://edge.microsoft.com/translate/auth");
exp = parseMSToken(token);
await setMsauth({ token, exp });
return [token, exp];
}; };
}; };

View File

@@ -10,6 +10,17 @@ import { isBg } from "./browser";
import { sendBgMsg } from "./msg"; import { sendBgMsg } from "./msg";
import { blobToBase64 } from "./utils"; import { blobToBase64 } from "./utils";
/**
* 清除缓存数据
*/
export const tryClearCaches = async () => {
try {
caches.delete(CACHE_NAME);
} catch (err) {
kissLog("clean caches", err);
}
};
/** /**
* 构造缓存 request * 构造缓存 request
* @param {*} input * @param {*} input

View File

@@ -1,9 +1,10 @@
import { import {
CACHE_NAME,
OPT_TRANS_GOOGLE, OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT, OPT_TRANS_MICROSOFT,
OPT_TRANS_BAIDU, OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT, OPT_TRANS_TENCENT,
OPT_LANGS_TO_CODE,
OPT_LANGS_MAP,
} from "../config"; } from "../config";
import { browser } from "./browser"; import { browser } from "./browser";
import { import {
@@ -14,52 +15,51 @@ import {
} from "../apis"; } from "../apis";
import { kissLog } from "./log"; import { kissLog } from "./log";
const langdetectMap = { const langdetectFns = {
[OPT_TRANS_GOOGLE]: apiGoogleLangdetect, [OPT_TRANS_GOOGLE]: apiGoogleLangdetect,
[OPT_TRANS_MICROSOFT]: apiMicrosoftLangdetect, [OPT_TRANS_MICROSOFT]: apiMicrosoftLangdetect,
[OPT_TRANS_BAIDU]: apiBaiduLangdetect, [OPT_TRANS_BAIDU]: apiBaiduLangdetect,
[OPT_TRANS_TENCENT]: apiTencentLangdetect, [OPT_TRANS_TENCENT]: apiTencentLangdetect,
}; };
/**
* 清除缓存数据
*/
export const tryClearCaches = async () => {
try {
caches.delete(CACHE_NAME);
} catch (err) {
kissLog("clean caches", err);
}
};
/** /**
* 语言识别 * 语言识别
* @param {*} q * @param {*} text
* @returns * @returns
*/ */
export const tryDetectLang = async ( export const tryDetectLang = async (
q, text,
useRemote = false, useRemote = "false",
langDetector = OPT_TRANS_MICROSOFT langDetector = OPT_TRANS_MICROSOFT
) => { ) => {
let lang = ""; let deLang = "";
if (useRemote) { // 远程识别
if (useRemote === "true" && langDetector) {
try { try {
lang = await langdetectMap[langDetector](q); const lang = await langdetectFns[langDetector](text);
if (lang) {
deLang = OPT_LANGS_TO_CODE[langDetector].get(lang) || "";
}
} catch (err) { } catch (err) {
kissLog("detect lang remote", err); kissLog("detect lang remote", err);
} }
} }
if (!lang) { // 本地识别
if (!deLang) {
try { try {
const res = await browser?.i18n?.detectLanguage(q); const res = await browser?.i18n?.detectLanguage(text);
lang = res?.languages?.[0]?.language; const lang = res?.languages?.[0]?.language;
if (OPT_LANGS_MAP.has(lang)) {
deLang = lang;
} else if (lang.startsWith("zh")) {
deLang = "zh-CN";
}
} catch (err) { } catch (err) {
kissLog("detect lang local", err); kissLog("detect lang local", err);
} }
} }
return lang; return deLang;
}; };

View File

@@ -24,7 +24,7 @@ import { clearAllBatchQueue } from "./batchQueue";
import { genTextClass } from "./style"; import { genTextClass } from "./style";
import { loadingSvg } from "./svg"; import { loadingSvg } from "./svg";
import { shortcutRegister } from "./shortcut"; import { shortcutRegister } from "./shortcut";
import { tryDetectLang } from "."; import { tryDetectLang } from "./detect";
/** /**
* @class Translator * @class Translator
@@ -436,6 +436,7 @@ export class Translator {
} }
} }
// todo: 利用AI总结
#getDocDescription() { #getDocDescription() {
try { try {
const meta = document.querySelector('meta[name="description"]'); const meta = document.querySelector('meta[name="description"]');
@@ -738,20 +739,30 @@ export class Translator {
} }
// 提前进行语言检测 // 提前进行语言检测
const { detectRemote, toLang, skipLangs = [] } = this.#rule; let deLang = "";
const {
detectRemote,
fromLang = "auto",
toLang,
skipLangs = [],
} = this.#rule;
if (fromLang === "auto") {
const { langDetector } = this.#setting; const { langDetector } = this.#setting;
const deLang = await tryDetectLang( deLang = await tryDetectLang(
node.textContent, node.textContent,
detectRemote, detectRemote,
langDetector langDetector
); );
// console.log("deLang", deLang, toLang);
if ( if (
deLang && deLang &&
(toLang.slice(0, 2) === deLang.slice(0, 2) || skipLangs.includes(deLang)) (toLang.slice(0, 2) === deLang.slice(0, 2) ||
skipLangs.includes(deLang))
) { ) {
// 保留处理状态,不做删除
// this.#processedNodes.delete(node);
return; return;
} }
}
let nodeGroup = []; let nodeGroup = [];
[...node.childNodes].forEach((child) => { [...node.childNodes].forEach((child) => {
@@ -762,13 +773,13 @@ export class Translator {
if (!shouldBreak && shouldGroup) { if (!shouldBreak && shouldGroup) {
nodeGroup.push(child); nodeGroup.push(child);
} else if (shouldBreak && nodeGroup.length) { } else if (shouldBreak && nodeGroup.length) {
this.#translateNodeGroup(nodeGroup, node); this.#translateNodeGroup(nodeGroup, node, deLang);
nodeGroup = []; nodeGroup = [];
} }
}); });
if (nodeGroup.length) { if (nodeGroup.length) {
this.#translateNodeGroup(nodeGroup, node); this.#translateNodeGroup(nodeGroup, node, deLang);
} }
} }
@@ -834,7 +845,7 @@ export class Translator {
} }
// 翻译内联节点 // 翻译内联节点
async #translateNodeGroup(nodes, hostNode) { async #translateNodeGroup(nodes, hostNode, deLang) {
const { const {
transTag, transTag,
textStyle, textStyle,
@@ -901,8 +912,10 @@ export class Translator {
// return; // return;
// } // }
const [translatedText, isSameLang] = const [translatedText, isSameLang] = await this.#translateFetch(
await this.#translateFetch(processedString); processedString,
deLang
);
// console.log("translatedText", translatedText); // console.log("translatedText", translatedText);
if (isSameLang || this.#runId !== currentRunId) { if (isSameLang || this.#runId !== currentRunId) {
wrapper.remove(); wrapper.remove();
@@ -1060,7 +1073,7 @@ export class Translator {
} }
// 发起翻译请求 // 发起翻译请求
#translateFetch(text) { #translateFetch(text, deLang = "") {
const { apiSlug, fromLang, toLang } = this.#rule; const { apiSlug, fromLang, toLang } = this.#rule;
const apiSetting = const apiSetting =
this.#setting.transApis.find((api) => api.apiSlug === apiSlug) || this.#setting.transApis.find((api) => api.apiSlug === apiSlug) ||
@@ -1068,7 +1081,7 @@ export class Translator {
return apiTranslate({ return apiTranslate({
text, text,
fromLang, fromLang: deLang || fromLang,
toLang, toLang,
apiSetting, apiSetting,
docInfo: this.#docInfo, docInfo: this.#docInfo,
@@ -1295,17 +1308,23 @@ export class Translator {
this.#init(); this.#init();
} }
// 翻译页面标题
if (this.#rule.transTitle === "true") { if (this.#rule.transTitle === "true") {
this.#translateTitle();
}
}
// 翻译页面标题
async #translateTitle() {
const title = document.title; const title = document.title;
this.#docInfo.title = title; this.#docInfo.title = title;
this.#translateFetch(title) if (!title) return;
.then(([trText]) => {
document.title = trText || title; try {
}) const deLang = await tryDetectLang(title);
.catch((err) => { const [translatedTitle] = await this.#translateFetch(title, deLang);
document.title = translatedTitle || title;
} catch (err) {
kissLog("tanslate title", err); kissLog("tanslate title", err);
});
} }
} }

View File

@@ -63,24 +63,30 @@ import { kissLog } from "../../libs/log";
import { useApiList } from "../../hooks/Api"; import { useApiList } from "../../hooks/Api";
import ShowMoreButton from "./ShowMoreButton"; import ShowMoreButton from "./ShowMoreButton";
const calculateInitialValues = (rule) => {
const base = rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE;
return { ...base, ...(rule || {}) };
};
function RuleFields({ rule, rules, setShow, setKeyword }) { function RuleFields({ rule, rules, setShow, setKeyword }) {
const initFormValues = useMemo(
() => ({
...(rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE),
...(rule || {}),
}),
[rule]
);
const editMode = useMemo(() => !!rule, [rule]); const editMode = useMemo(() => !!rule, [rule]);
const i18n = useI18n(); const i18n = useI18n();
const [disabled, setDisabled] = useState(editMode); const [disabled, setDisabled] = useState(editMode);
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const [formValues, setFormValues] = useState(initFormValues); const [initialFormValues, setInitialFormValues] = useState(() =>
calculateInitialValues(rule)
);
const [formValues, setFormValues] = useState(initialFormValues);
const [showMore, setShowMore] = useState(!rules); const [showMore, setShowMore] = useState(!rules);
const [isModified, setIsModified] = useState(false);
const { enabledApis } = useApiList(); const { enabledApis } = useApiList();
useEffect(() => {
const newInitialValues = calculateInitialValues(rule);
setInitialFormValues(newInitialValues);
setFormValues(newInitialValues);
}, [rule]);
const { const {
pattern, pattern,
selector, selector,
@@ -116,12 +122,9 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
// transRemoveHook = "", // transRemoveHook = "",
} = formValues; } = formValues;
useEffect(() => { const isModified = useMemo(() => {
if (!initFormValues) return; return JSON.stringify(initialFormValues) !== JSON.stringify(formValues);
const hasChanged = }, [initialFormValues, formValues]);
JSON.stringify(initFormValues) !== JSON.stringify(formValues);
setIsModified(hasChanged);
}, [initFormValues, formValues]);
const hasSamePattern = (str) => { const hasSamePattern = (str) => {
for (const item of rules.list) { for (const item of rules.list) {
@@ -163,7 +166,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
setShow(false); setShow(false);
} }
setErrors({}); setErrors({});
setFormValues(initFormValues); setFormValues(initialFormValues);
}; };
const handleRestore = (e) => { const handleRestore = (e) => {
@@ -199,7 +202,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
// 添加 // 添加
rules.add(formValues); rules.add(formValues);
setShow(false); setShow(false);
setFormValues(initFormValues); setFormValues(initialFormValues);
} }
}; };
@@ -1205,11 +1208,15 @@ function SubRules({ subRules }) {
} }
function GlobalRule({ rules }) { function GlobalRule({ rules }) {
if (!rules.list) { const globalRule = useMemo(
() => rules.list[rules.list.length - 1],
[rules.list]
);
if (!globalRule) {
return; return;
} }
const globalRule = rules.list[rules.list.length - 1];
return ( return (
<Stack spacing={3}> <Stack spacing={3}>
<RuleAccordion <RuleAccordion

View File

@@ -26,7 +26,7 @@ import {
} from "../../config"; } from "../../config";
import { sendIframeMsg } from "../../libs/iframe"; import { sendIframeMsg } from "../../libs/iframe";
import { saveRule } from "../../libs/rules"; import { saveRule } from "../../libs/rules";
import { tryClearCaches } from "../../libs"; import { tryClearCaches } from "../../libs/cache";
import { kissLog } from "../../libs/log"; import { kissLog } from "../../libs/log";
import { parseUrlPattern } from "../../libs/utils"; import { parseUrlPattern } from "../../libs/utils";

View File

@@ -9,7 +9,7 @@ import { apiTranslate } from "../../apis";
import CopyBtn from "./CopyBtn"; import CopyBtn from "./CopyBtn";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Alert from "@mui/material/Alert"; import Alert from "@mui/material/Alert";
import { tryDetectLang } from "../../libs"; import { tryDetectLang } from "../../libs/detect";
export default function TranCont({ export default function TranCont({
text, text,
@@ -35,7 +35,7 @@ export default function TranCont({
let to = toLang; let to = toLang;
if (fromLang === "auto" && toLang !== toLang2 && toLang2 !== "none") { if (fromLang === "auto" && toLang !== toLang2 && toLang2 !== "none") {
const detectLang = await tryDetectLang(text, true, langDetector); const detectLang = await tryDetectLang(text, "true", langDetector);
if (detectLang === toLang) { if (detectLang === toLang) {
to = toLang2; to = toLang2;
} }