feat: Support for multiple translation services in tranbox

This commit is contained in:
Gabe
2025-10-02 11:33:33 +08:00
parent 039566ded5
commit 389f0b6f82
9 changed files with 41 additions and 36 deletions

View File

@@ -56,6 +56,13 @@ export const OPT_ALL_TYPES = [
OPT_TRANS_CUSTOMIZE, OPT_TRANS_CUSTOMIZE,
]; ];
export const OPT_LANGDETECTOR_ALL = [
OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
];
// 翻译引擎特殊集合 // 翻译引擎特殊集合
export const API_SPE_TYPES = { export const API_SPE_TYPES = {
// 内置翻译 // 内置翻译
@@ -114,15 +121,9 @@ export const API_SPE_TYPES = {
OPT_TRANS_OPENROUTER, OPT_TRANS_OPENROUTER,
OPT_TRANS_CUSTOMIZE, OPT_TRANS_CUSTOMIZE,
]), ]),
detector: new Set(OPT_LANGDETECTOR_ALL),
}; };
export const OPT_LANGDETECTOR_ALL = [
OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
];
export const BUILTIN_STONES = [ export const BUILTIN_STONES = [
"formal", // 正式风格 "formal", // 正式风格
"casual", // 口语风格 "casual", // 口语风格

View File

@@ -344,6 +344,11 @@ export const I18N = {
en: `Translate Service`, en: `Translate Service`,
zh_TW: `翻譯服務`, zh_TW: `翻譯服務`,
}, },
translate_service_multiple: {
zh: `翻译服务 (支持多选)`,
en: `Translation service (multiple supported)`,
zh_TW: `翻譯服務 (支援多選)`,
},
translate_timing: { translate_timing: {
zh: `翻译时机`, zh: `翻译时机`,
en: `Translate Timing`, en: `Translate Timing`,

View File

@@ -74,7 +74,7 @@ export const OPT_TRANBOX_TRIGGER_ALL = [
export const DEFAULT_TRANBOX_SHORTCUT = ["AltLeft", "KeyS"]; export const DEFAULT_TRANBOX_SHORTCUT = ["AltLeft", "KeyS"];
export const DEFAULT_TRANBOX_SETTING = { export const DEFAULT_TRANBOX_SETTING = {
transOpen: true, // 是否启用划词翻译 transOpen: true, // 是否启用划词翻译
apiSlug: OPT_TRANS_MICROSOFT, apiSlugs: [OPT_TRANS_MICROSOFT],
fromLang: "auto", fromLang: "auto",
toLang: "zh-CN", toLang: "zh-CN",
toLang2: "en", toLang2: "en",
@@ -127,7 +127,7 @@ export const DEFAULT_SETTING = {
injectRules: true, // 是否注入订阅规则 injectRules: true, // 是否注入订阅规则
fabClickAction: 0, // 悬浮按钮点击行为 fabClickAction: 0, // 悬浮按钮点击行为
// injectWebfix: true, // 是否注入修复补丁(作废) // injectWebfix: true, // 是否注入修复补丁(作废)
detectRemote: false, // 是否使用远程语言检测 从rule移回 // detectRemote: false, // 是否使用远程语言检测 从rule移回
// contextMenus: true, // 是否添加右键菜单(作废) // contextMenus: true, // 是否添加右键菜单(作废)
contextMenuType: 1, // 右键菜单类型(0不显示1简单菜单2多级菜单) contextMenuType: 1, // 右键菜单类型(0不显示1简单菜单2多级菜单)
// transTag: DEFAULT_TRANS_TAG, // 译文元素标签(移至rule作废) // transTag: DEFAULT_TRANS_TAG, // 译文元素标签(移至rule作废)
@@ -146,7 +146,7 @@ export const DEFAULT_SETTING = {
// disableLangs: [], // 不翻译的语言(移至rule作废) // disableLangs: [], // 不翻译的语言(移至rule作废)
skipLangs: [], // 不翻译的语言从rule移回 skipLangs: [], // 不翻译的语言从rule移回
transInterval: 100, // 翻译等待时间 transInterval: 100, // 翻译等待时间
langDetector: OPT_TRANS_MICROSOFT, // 远程语言识别服务 langDetector: "-", // 远程语言识别服务
mouseHoverSetting: DEFAULT_MOUSE_HOVER_SETTING, // 鼠标悬停翻译 mouseHoverSetting: DEFAULT_MOUSE_HOVER_SETTING, // 鼠标悬停翻译
preInit: true, // 是否预加载脚本 preInit: true, // 是否预加载脚本
transAllnow: false, // 是否立即全部翻译 transAllnow: false, // 是否立即全部翻译

View File

@@ -5,6 +5,7 @@ import {
OPT_TRANS_TENCENT, OPT_TRANS_TENCENT,
OPT_LANGS_TO_CODE, OPT_LANGS_TO_CODE,
OPT_LANGS_MAP, OPT_LANGS_MAP,
API_SPE_TYPES,
} from "../config"; } from "../config";
import { browser } from "./browser"; import { browser } from "./browser";
import { import {
@@ -27,15 +28,11 @@ const langdetectFns = {
* @param {*} text * @param {*} text
* @returns * @returns
*/ */
export const tryDetectLang = async ( export const tryDetectLang = async (text, langDetector = "-") => {
text,
detectRemote = true,
langDetector = OPT_TRANS_MICROSOFT
) => {
let deLang = ""; let deLang = "";
// 远程识别 // 远程识别
if (detectRemote && langDetector) { if (API_SPE_TYPES.detector.has(langDetector)) {
try { try {
const lang = await langdetectFns[langDetector](text); const lang = await langdetectFns[langDetector](text);
if (lang) { if (lang) {

View File

@@ -849,14 +849,9 @@ export class Translator {
// 提前进行语言检测 // 提前进行语言检测
let deLang = ""; let deLang = "";
const { fromLang = "auto", toLang } = this.#rule; const { fromLang = "auto", toLang } = this.#rule;
const { detectRemote, skipLangs = [] } = this.#setting; const { langDetector, skipLangs = [] } = this.#setting;
if (fromLang === "auto") { if (fromLang === "auto") {
const { langDetector } = this.#setting; deLang = await tryDetectLang(node.textContent, langDetector);
deLang = await tryDetectLang(
node.textContent,
detectRemote,
langDetector
);
if ( if (
deLang && deLang &&
(toLang.slice(0, 2) === deLang.slice(0, 2) || (toLang.slice(0, 2) === deLang.slice(0, 2) ||

View File

@@ -13,7 +13,6 @@ import {
UI_LANGS, UI_LANGS,
TRANS_NEWLINE_LENGTH, TRANS_NEWLINE_LENGTH,
CACHE_NAME, CACHE_NAME,
OPT_TRANS_MICROSOFT,
OPT_LANGDETECTOR_ALL, OPT_LANGDETECTOR_ALL,
OPT_SHORTCUT_TRANSLATE, OPT_SHORTCUT_TRANSLATE,
OPT_SHORTCUT_STYLE, OPT_SHORTCUT_STYLE,
@@ -118,10 +117,10 @@ export default function Settings() {
blacklist = DEFAULT_BLACKLIST.join(",\n"), blacklist = DEFAULT_BLACKLIST.join(",\n"),
csplist = DEFAULT_CSPLIST.join(",\n"), csplist = DEFAULT_CSPLIST.join(",\n"),
transInterval = 100, transInterval = 100,
langDetector = OPT_TRANS_MICROSOFT, langDetector = "-",
preInit = true, preInit = true,
skipLangs = [], skipLangs = [],
detectRemote = true, // detectRemote = true,
transAllnow = false, transAllnow = false,
} = setting; } = setting;
const { isHide = false, fabClickAction = 0 } = fab || {}; const { isHide = false, fabClickAction = 0 } = fab || {};
@@ -315,7 +314,7 @@ export default function Settings() {
<MenuItem value={true}>{i18n("mk_pageopen")}</MenuItem> <MenuItem value={true}>{i18n("mk_pageopen")}</MenuItem>
</TextField> </TextField>
</Grid> </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
size="small" size="small"
@@ -328,7 +327,7 @@ export default function Settings() {
<MenuItem value={true}>{i18n("enable")}</MenuItem> <MenuItem value={true}>{i18n("enable")}</MenuItem>
<MenuItem value={false}>{i18n("disable")}</MenuItem> <MenuItem value={false}>{i18n("disable")}</MenuItem>
</TextField> </TextField>
</Grid> </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
@@ -336,9 +335,10 @@ export default function Settings() {
size="small" size="small"
name="langDetector" name="langDetector"
value={langDetector} value={langDetector}
label={i18n("detect_lang_service")} label={i18n("detect_lang_remote")}
onChange={handleChange} onChange={handleChange}
> >
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
{OPT_LANGDETECTOR_ALL.map((item) => ( {OPT_LANGDETECTOR_ALL.map((item) => (
<MenuItem value={item} key={item}> <MenuItem value={item} key={item}>
{item} {item}

View File

@@ -51,7 +51,7 @@ export default function Tranbox() {
const { const {
transOpen, transOpen,
apiSlug, apiSlugs,
fromLang, fromLang,
toLang, toLang,
toLang2 = "en", toLang2 = "en",
@@ -88,15 +88,18 @@ export default function Tranbox() {
<Box> <Box>
<Grid container spacing={2} columns={12}> <Grid container spacing={2} columns={12}>
<Grid item xs={12} sm={12} md={6} lg={3}> <Grid item xs={12} sm={12} md={12} lg={6}>
<TextField <TextField
select select
fullWidth fullWidth
size="small" size="small"
name="apiSlug" name="apiSlugs"
value={apiSlug} value={apiSlugs}
label={i18n("translate_service")} label={i18n("translate_service_multiple")}
onChange={handleChange} onChange={handleChange}
SelectProps={{
multiple: true,
}}
> >
{enabledApis.map((api) => ( {enabledApis.map((api) => (
<MenuItem key={api.apiSlug} value={api.apiSlug}> <MenuItem key={api.apiSlug} value={api.apiSlug}>

View File

@@ -287,7 +287,7 @@ function TranForm({
/> />
)} )}
{enDict !== "-" && ( {enDict !== "-" && isValidWord(text) && (
<> <>
<DictCont text={text} /> <DictCont text={text} />
<SugCont text={text} /> <SugCont text={text} />

View File

@@ -18,7 +18,7 @@ export default function TranCont({
toLang, toLang,
toLang2 = "en", toLang2 = "en",
transApis, transApis,
simpleStyle, simpleStyle = false,
langDetector, langDetector,
}) { }) {
const i18n = useI18n(); const i18n = useI18n();
@@ -27,6 +27,10 @@ export default function TranCont({
const [error, setError] = useState(""); const [error, setError] = useState("");
useEffect(() => { useEffect(() => {
if (!text?.trim()) {
return;
}
(async () => { (async () => {
try { try {
setLoading(true); setLoading(true);