feat: support youdao dict
This commit is contained in:
@@ -45,7 +45,7 @@
|
||||
"description": "__MSG_open_options__"
|
||||
}
|
||||
},
|
||||
"permissions": ["storage", "contextMenus", "scripting", "declarativeNetRequest"],
|
||||
"permissions": ["storage", "contextMenus", "scripting", "declarativeNetRequest", "declarativeNetRequestWithHostAccess"],
|
||||
"host_permissions": ["<all_urls>"],
|
||||
"icons": {
|
||||
"16": "images/logo16.png",
|
||||
|
||||
@@ -159,6 +159,77 @@ export const apiBaiduSuggest = async (text) => {
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 有道翻译建议
|
||||
* @param {*} text
|
||||
* @returns
|
||||
*/
|
||||
export const apiYoudaoSuggest = async (text) => {
|
||||
const params = {
|
||||
num: 5,
|
||||
ver: 3.0,
|
||||
doctype: "json",
|
||||
cache: false,
|
||||
le: "en",
|
||||
q: text,
|
||||
};
|
||||
const input = `https://dict.youdao.com/suggest?${queryString.stringify(params)}`;
|
||||
const init = {
|
||||
headers: {
|
||||
accept: "application/json, text/plain, */*",
|
||||
"accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,ja;q=0.6",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
method: "GET",
|
||||
};
|
||||
const res = await fetchData(input, init, { useCache: true });
|
||||
|
||||
if (res?.result?.code === 200) {
|
||||
await putHttpCachePolyfill(input, init, res);
|
||||
return res.data.entries;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 有道词典
|
||||
* @param {*} text
|
||||
* @returns
|
||||
*/
|
||||
export const apiYoudaoDict = async (text) => {
|
||||
const params = {
|
||||
doctype: "json",
|
||||
jsonversion: 4,
|
||||
};
|
||||
const input = `https://dict.youdao.com/jsonapi_s?${queryString.stringify(params)}`;
|
||||
const body = queryString.stringify({
|
||||
q: "search",
|
||||
le: "en",
|
||||
t: 3,
|
||||
client: "web",
|
||||
// sign: "",
|
||||
keyfrom: "webdict",
|
||||
});
|
||||
const init = {
|
||||
headers: {
|
||||
accept: "application/json, text/plain, */*",
|
||||
"accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,ja;q=0.6",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
method: "POST",
|
||||
body,
|
||||
};
|
||||
const res = await fetchData(input, init, { useCache: true });
|
||||
|
||||
if (res) {
|
||||
await putHttpCachePolyfill(input, init, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 百度语音
|
||||
* @param {*} text
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
MSG_INJECT_CSS,
|
||||
MSG_UPDATE_CSP,
|
||||
DEFAULT_CSPLIST,
|
||||
DEFAULT_ORILIST,
|
||||
CMD_TOGGLE_TRANSLATE,
|
||||
CMD_TOGGLE_STYLE,
|
||||
CMD_OPEN_OPTIONS,
|
||||
@@ -33,7 +34,9 @@ import { kissLog } from "./libs/log";
|
||||
|
||||
globalThis.ContextType = "BACKGROUND";
|
||||
|
||||
const REMOVE_HEADERS = [
|
||||
const CSP_RULE_START_ID = 1;
|
||||
const ORI_RULE_START_ID = 10000;
|
||||
const CSP_REMOVE_HEADERS = [
|
||||
`content-security-policy`,
|
||||
`content-security-policy-report-only`,
|
||||
`x-webkit-csp`,
|
||||
@@ -94,17 +97,34 @@ async function addContextMenus(contextMenuType = 1) {
|
||||
* 更新CSP策略
|
||||
* @param {*} csplist
|
||||
*/
|
||||
async function updateCspRules(csplist = DEFAULT_CSPLIST.join(",\n")) {
|
||||
async function updateCspRules({ csplist, orilist }) {
|
||||
try {
|
||||
const newRules = csplist
|
||||
const oldRules = await browser.declarativeNetRequest.getDynamicRules();
|
||||
|
||||
const rulesToAdd = [];
|
||||
const idsToRemove = [];
|
||||
|
||||
if (csplist !== undefined) {
|
||||
let processedCspList = csplist;
|
||||
if (typeof processedCspList === "string") {
|
||||
processedCspList = processedCspList
|
||||
.split(/\n|,/)
|
||||
.map((url) => url.trim())
|
||||
.filter(Boolean)
|
||||
.map((url, idx) => ({
|
||||
id: idx + 1,
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
const oldCspRuleIds = oldRules
|
||||
.filter(
|
||||
(rule) => rule.id >= CSP_RULE_START_ID && rule.id < ORI_RULE_START_ID
|
||||
)
|
||||
.map((rule) => rule.id);
|
||||
idsToRemove.push(...oldCspRuleIds);
|
||||
|
||||
const newCspRules = processedCspList.map((url, index) => ({
|
||||
id: CSP_RULE_START_ID + index,
|
||||
action: {
|
||||
type: "modifyHeaders",
|
||||
responseHeaders: REMOVE_HEADERS.map((header) => ({
|
||||
responseHeaders: CSP_REMOVE_HEADERS.map((header) => ({
|
||||
operation: "remove",
|
||||
header,
|
||||
})),
|
||||
@@ -114,12 +134,43 @@ async function updateCspRules(csplist = DEFAULT_CSPLIST.join(",\n")) {
|
||||
resourceTypes: ["main_frame", "sub_frame"],
|
||||
},
|
||||
}));
|
||||
const oldRules = await browser.declarativeNetRequest.getDynamicRules();
|
||||
const oldRuleIds = oldRules.map((rule) => rule.id);
|
||||
rulesToAdd.push(...newCspRules);
|
||||
}
|
||||
|
||||
if (orilist !== undefined) {
|
||||
let processedOriList = orilist;
|
||||
if (typeof processedOriList === "string") {
|
||||
processedOriList = processedOriList
|
||||
.split(/\n|,/)
|
||||
.map((url) => url.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
const oldOriRuleIds = oldRules
|
||||
.filter((rule) => rule.id >= ORI_RULE_START_ID)
|
||||
.map((rule) => rule.id);
|
||||
idsToRemove.push(...oldOriRuleIds);
|
||||
|
||||
const newOriRules = processedOriList.map((url, index) => ({
|
||||
id: ORI_RULE_START_ID + index,
|
||||
action: {
|
||||
type: "modifyHeaders",
|
||||
requestHeaders: [{ header: "Origin", operation: "remove" }],
|
||||
},
|
||||
condition: {
|
||||
urlFilter: url,
|
||||
resourceTypes: ["xmlhttprequest"],
|
||||
},
|
||||
}));
|
||||
rulesToAdd.push(...newOriRules);
|
||||
}
|
||||
|
||||
if (idsToRemove.length > 0 || rulesToAdd.length > 0) {
|
||||
await browser.declarativeNetRequest.updateDynamicRules({
|
||||
removeRuleIds: oldRuleIds,
|
||||
addRules: newRules,
|
||||
removeRuleIds: idsToRemove,
|
||||
addRules: rulesToAdd,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
kissLog("update csp rules", err);
|
||||
}
|
||||
@@ -149,7 +200,7 @@ browser.runtime.onInstalled.addListener(() => {
|
||||
addContextMenus();
|
||||
|
||||
// 禁用CSP
|
||||
updateCspRules();
|
||||
updateCspRules({ csplist: DEFAULT_CSPLIST, orilist: DEFAULT_ORILIST });
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -159,7 +210,7 @@ browser.runtime.onStartup.addListener(async () => {
|
||||
// 同步数据
|
||||
await trySyncSettingAndRules();
|
||||
|
||||
const { clearCache, contextMenuType, subrulesList, csplist } =
|
||||
const { clearCache, contextMenuType, subrulesList, csplist, orilist } =
|
||||
await getSettingWithDefault();
|
||||
|
||||
// 清除缓存
|
||||
@@ -177,7 +228,7 @@ browser.runtime.onStartup.addListener(async () => {
|
||||
addContextMenus(contextMenuType);
|
||||
|
||||
// 禁用CSP
|
||||
updateCspRules(csplist);
|
||||
updateCspRules({ csplist, orilist });
|
||||
|
||||
// 同步订阅规则
|
||||
trySyncAllSubRules({ subrulesList });
|
||||
|
||||
@@ -14,6 +14,14 @@ export const INPUT_PLACE_KEY = "{{key}}"; // 占位符
|
||||
export const INPUT_PLACE_MODEL = "{{model}}"; // 占位符
|
||||
|
||||
export const OPT_DICT_BAIDU = "Baidu";
|
||||
export const OPT_DICT_YOUDAO = "Youdao";
|
||||
export const OPT_DICT_ALL = [OPT_DICT_BAIDU, OPT_DICT_YOUDAO];
|
||||
export const OPT_DICT_MAP = new Set(OPT_DICT_ALL);
|
||||
|
||||
export const OPT_SUG_BAIDU = "Baidu";
|
||||
export const OPT_SUG_YOUDAO = "Youdao";
|
||||
export const OPT_SUG_ALL = [OPT_SUG_BAIDU, OPT_SUG_YOUDAO];
|
||||
export const OPT_SUG_MAP = new Set(OPT_SUG_ALL);
|
||||
|
||||
export const OPT_TRANS_GOOGLE = "Google";
|
||||
export const OPT_TRANS_GOOGLE_2 = "Google2";
|
||||
@@ -63,9 +71,6 @@ export const OPT_LANGDETECTOR_ALL = [
|
||||
OPT_TRANS_TENCENT,
|
||||
];
|
||||
|
||||
export const OPT_DICT_ALL = [OPT_TRANS_BAIDU];
|
||||
export const OPT_DICT_MAP = new Set(OPT_DICT_ALL);
|
||||
|
||||
// 翻译引擎特殊集合
|
||||
export const API_SPE_TYPES = {
|
||||
// 内置翻译
|
||||
|
||||
@@ -1148,6 +1148,11 @@ export const I18N = {
|
||||
en: `Translate Blacklist`,
|
||||
zh_TW: `停用翻譯名單`,
|
||||
},
|
||||
disabled_orilist: {
|
||||
zh: `禁用Origin名单`,
|
||||
en: `Disabled Origin List`,
|
||||
zh_TW: `停用 Origin 名單`,
|
||||
},
|
||||
disabled_csplist: {
|
||||
zh: `禁用CSP名单`,
|
||||
en: `Disabled CSP List`,
|
||||
@@ -1323,6 +1328,11 @@ export const I18N = {
|
||||
en: `English Dictionary`,
|
||||
zh_TW: `英文字典`,
|
||||
},
|
||||
english_suggest: {
|
||||
zh: `英文建议`,
|
||||
en: `English Suggest`,
|
||||
zh_TW: `英文建議`,
|
||||
},
|
||||
api_name: {
|
||||
zh: `接口名称`,
|
||||
en: `API Name`,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
OPT_DICT_BAIDU,
|
||||
OPT_SUG_BAIDU,
|
||||
DEFAULT_HTTP_TIMEOUT,
|
||||
OPT_TRANS_MICROSOFT,
|
||||
DEFAULT_API_LIST,
|
||||
@@ -28,6 +29,7 @@ export const DEFAULT_BLACKLIST = [
|
||||
"login.dingtalk.com",
|
||||
]; // 禁用翻译名单
|
||||
export const DEFAULT_CSPLIST = ["https://github.com"]; // 禁用CSP名单
|
||||
export const DEFAULT_ORILIST = ["https://dict.youdao.com"]; // 移除Origin名单
|
||||
|
||||
// 同步设置
|
||||
export const OPT_SYNCTYPE_WORKER = "KISS-Worker";
|
||||
@@ -90,6 +92,7 @@ export const DEFAULT_TRANBOX_SETTING = {
|
||||
triggerMode: OPT_TRANBOX_TRIGGER_CLICK, // 触发翻译方式
|
||||
// extStyles: "", // 附加样式
|
||||
enDict: OPT_DICT_BAIDU, // 英文词典
|
||||
enSug: OPT_SUG_BAIDU, // 英文建议
|
||||
};
|
||||
|
||||
// 订阅列表
|
||||
@@ -143,6 +146,7 @@ export const DEFAULT_SETTING = {
|
||||
touchTranslate: 2, // 触屏翻译
|
||||
blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单
|
||||
csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单
|
||||
orilist: DEFAULT_ORILIST.join(",\n"), // 禁用CSP名单
|
||||
// disableLangs: [], // 不翻译的语言(移至rule,作废)
|
||||
skipLangs: [], // 不翻译的语言(从rule移回)
|
||||
transInterval: 100, // 翻译等待时间
|
||||
|
||||
@@ -1,40 +1,152 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
|
||||
/**
|
||||
* fetch data hook
|
||||
* @returns
|
||||
*/
|
||||
export const useFetch = (url) => {
|
||||
export const useAsync = () => {
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!url) {
|
||||
const execute = useCallback(async (fn, ...args) => {
|
||||
if (!fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(`[${res.status}] ${res.statusText}`);
|
||||
}
|
||||
let data;
|
||||
if (res.headers.get("Content-Type")?.includes("json")) {
|
||||
data = await res.json();
|
||||
} else {
|
||||
data = await res.text();
|
||||
}
|
||||
setData(data);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [url]);
|
||||
setError(null);
|
||||
|
||||
return [data, loading, error];
|
||||
try {
|
||||
const res = await fn(...args);
|
||||
setData(res);
|
||||
setLoading(false);
|
||||
return res;
|
||||
} catch (err) {
|
||||
setError(err?.message || "An unknown error occurred");
|
||||
setLoading(false);
|
||||
// throw err;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setData(null);
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return { data, loading, error, execute, reset };
|
||||
};
|
||||
|
||||
export const useAsyncNow = (fn, arg) => {
|
||||
const { execute, ...asyncState } = useAsync();
|
||||
|
||||
useEffect(() => {
|
||||
if (fn) {
|
||||
execute(fn, arg);
|
||||
}
|
||||
}, [execute, fn, arg]);
|
||||
|
||||
return { ...asyncState };
|
||||
};
|
||||
|
||||
export const useFetch = () => {
|
||||
const { execute, ...asyncState } = useAsync();
|
||||
|
||||
const requester = useCallback(async (url, options) => {
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
const errorInfo = await response.text();
|
||||
throw new Error(
|
||||
`Request failed: ${response.status} ${response.statusText} - ${errorInfo}`
|
||||
);
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.headers.get("Content-Type")?.includes("json")) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
return response.text();
|
||||
}, []);
|
||||
|
||||
const get = useCallback(
|
||||
async (url, options = {}) => {
|
||||
try {
|
||||
const result = await execute(requester, url, {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
return result;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[execute, requester]
|
||||
);
|
||||
|
||||
const post = useCallback(
|
||||
async (url, body, options = {}) => {
|
||||
try {
|
||||
const result = await execute(requester, url, {
|
||||
...options,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...options.headers },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return result;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[execute, requester]
|
||||
);
|
||||
|
||||
const put = useCallback(
|
||||
async (url, body, options = {}) => {
|
||||
try {
|
||||
const result = await execute(requester, url, {
|
||||
...options,
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json", ...options.headers },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return result;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[execute, requester]
|
||||
);
|
||||
|
||||
const del = useCallback(
|
||||
async (url, options = {}) => {
|
||||
try {
|
||||
const result = await execute(requester, url, {
|
||||
...options,
|
||||
method: "DELETE",
|
||||
});
|
||||
return result;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[execute, requester]
|
||||
);
|
||||
|
||||
return {
|
||||
...asyncState,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
del,
|
||||
};
|
||||
};
|
||||
|
||||
export const useGet = (url) => {
|
||||
const { get, ...fetchState } = useFetch();
|
||||
|
||||
useEffect(() => {
|
||||
if (url) get(url);
|
||||
}, [url, get]);
|
||||
|
||||
return { ...fetchState };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useSetting } from "./Setting";
|
||||
import { I18N, URL_RAW_PREFIX } from "../config";
|
||||
import { useFetch } from "./Fetch";
|
||||
import { useGet } from "./Fetch";
|
||||
|
||||
export const getI18n = (uiLang, key, defaultText = "") => {
|
||||
return I18N?.[key]?.[uiLang] ?? defaultText;
|
||||
@@ -25,5 +25,5 @@ export const useI18nMd = (key) => {
|
||||
const i18n = useI18n();
|
||||
const fileName = i18n(key);
|
||||
const url = fileName ? `${URL_RAW_PREFIX}/${fileName}` : "";
|
||||
return useFetch(url);
|
||||
return useGet(url);
|
||||
};
|
||||
|
||||
@@ -7,14 +7,15 @@ import Paper from "@mui/material/Paper";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import { useFetch } from "./hooks/Fetch";
|
||||
import { useGet } from "./hooks/Fetch";
|
||||
import { I18N, URL_RAW_PREFIX } from "./config";
|
||||
|
||||
function App() {
|
||||
const [lang, setLang] = useState("zh");
|
||||
const [data, loading, error] = useFetch(
|
||||
const { data, loading, error } = useGet(
|
||||
`${URL_RAW_PREFIX}/${I18N?.["about_md"]?.[lang]}`
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper sx={{ padding: 2, margin: 2 }}>
|
||||
<Stack spacing={2} direction="row" justifyContent="flex-end">
|
||||
@@ -47,7 +48,7 @@ function App() {
|
||||
<CircularProgress />
|
||||
</center>
|
||||
) : (
|
||||
<ReactMarkdown children={error ? error.message : data} />
|
||||
<ReactMarkdown children={error || data} />
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useI18n, useI18nMd } from "../../hooks/I18n";
|
||||
|
||||
export default function About() {
|
||||
const i18n = useI18n();
|
||||
const [data, loading, error] = useI18nMd("about_md");
|
||||
const { data, loading, error } = useI18nMd("about_md");
|
||||
return (
|
||||
<Box>
|
||||
{loading ? (
|
||||
|
||||
@@ -21,9 +21,12 @@ import { kissLog } from "../../libs/log";
|
||||
import { apiTranslate } from "../../apis";
|
||||
import { OPT_TRANS_BAIDU, PHONIC_MAP } from "../../config";
|
||||
import { useConfirm } from "../../hooks/Confirm";
|
||||
import { useSetting } from "../../hooks/Setting";
|
||||
|
||||
function FavAccordion({ word, index }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { setting } = useSetting();
|
||||
const { enDict, enSug } = setting?.tranboxSetting || {};
|
||||
|
||||
const handleChange = (e) => {
|
||||
setExpanded((pre) => !pre);
|
||||
@@ -40,8 +43,8 @@ function FavAccordion({ word, index }) {
|
||||
<AccordionDetails>
|
||||
{expanded && (
|
||||
<Stack spacing={2}>
|
||||
<DictCont text={word} />
|
||||
<SugCont text={word} />
|
||||
<DictCont text={word} enDict={enDict} />
|
||||
<SugCont text={word} enSug={enSug} />
|
||||
</Stack>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function Playgound() {
|
||||
const { setting } = useSetting();
|
||||
const { transApis, langDetector, tranboxSetting } =
|
||||
setting || DEFAULT_SETTING;
|
||||
const { apiSlugs, fromLang, toLang, toLang2, enDict } =
|
||||
const { apiSlugs, fromLang, toLang, toLang2, enDict, enSug } =
|
||||
tranboxSetting || DEFAULT_TRANBOX_SETTING;
|
||||
return (
|
||||
<TranForm
|
||||
@@ -22,6 +22,7 @@ export default function Playgound() {
|
||||
simpleStyle={false}
|
||||
langDetector={langDetector}
|
||||
enDict={enDict}
|
||||
enSug={enSug}
|
||||
isPlaygound={true}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
OPT_SHORTCUT_SETTING,
|
||||
DEFAULT_BLACKLIST,
|
||||
DEFAULT_CSPLIST,
|
||||
DEFAULT_ORILIST,
|
||||
MSG_CONTEXT_MENUS,
|
||||
MSG_UPDATE_CSP,
|
||||
DEFAULT_HTTP_TIMEOUT,
|
||||
@@ -79,7 +80,10 @@ export default function Settings() {
|
||||
isExt && sendBgMsg(MSG_CONTEXT_MENUS, value);
|
||||
break;
|
||||
case "csplist":
|
||||
isExt && sendBgMsg(MSG_UPDATE_CSP, value);
|
||||
isExt && sendBgMsg(MSG_UPDATE_CSP, { csplist: value });
|
||||
break;
|
||||
case "orilist":
|
||||
isExt && sendBgMsg(MSG_UPDATE_CSP, { orilist: value });
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -116,6 +120,7 @@ export default function Settings() {
|
||||
touchTranslate = 2,
|
||||
blacklist = DEFAULT_BLACKLIST.join(",\n"),
|
||||
csplist = DEFAULT_CSPLIST.join(",\n"),
|
||||
orilist = DEFAULT_ORILIST.join(",\n"),
|
||||
transInterval = 100,
|
||||
langDetector = "-",
|
||||
preInit = true,
|
||||
@@ -399,6 +404,15 @@ export default function Settings() {
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("disabled_orilist")}
|
||||
helperText={i18n("pattern_helper")}
|
||||
name="orilist"
|
||||
value={orilist}
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
OPT_TRANBOX_TRIGGER_ALL,
|
||||
OPT_DICT_BAIDU,
|
||||
OPT_DICT_ALL,
|
||||
OPT_SUG_ALL,
|
||||
OPT_SUG_BAIDU,
|
||||
} from "../../config";
|
||||
import ShortcutInput from "./ShortcutInput";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
@@ -68,6 +70,7 @@ export default function Tranbox() {
|
||||
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
|
||||
// extStyles = "",
|
||||
enDict = OPT_DICT_BAIDU,
|
||||
enSug = OPT_SUG_BAIDU,
|
||||
} = tranboxSetting;
|
||||
|
||||
return (
|
||||
@@ -89,7 +92,7 @@ export default function Tranbox() {
|
||||
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={12} sm={12} md={12} lg={6}>
|
||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
@@ -181,6 +184,24 @@ export default function Tranbox() {
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||
<TextField
|
||||
fullWidth
|
||||
select
|
||||
size="small"
|
||||
name="enSug"
|
||||
value={enSug}
|
||||
label={i18n("english_suggest")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
|
||||
{OPT_SUG_ALL.map((item) => (
|
||||
<MenuItem value={item} key={item}>
|
||||
{item}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12} md={6} lg={3}>
|
||||
<TextField
|
||||
fullWidth
|
||||
|
||||
@@ -1,81 +1,40 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import FavBtn from "./FavBtn";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import AudioBtn from "./AudioBtn";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import { OPT_TRANS_BAIDU, PHONIC_MAP } from "../../config";
|
||||
import { apiTranslate } from "../../apis";
|
||||
import { isValidWord } from "../../libs/utils";
|
||||
import { OPT_DICT_BAIDU, OPT_DICT_YOUDAO, PHONIC_MAP } from "../../config";
|
||||
import CopyBtn from "./CopyBtn";
|
||||
import { useAsyncNow } from "../../hooks/Fetch";
|
||||
import { apiYoudaoDict } from "../../apis";
|
||||
|
||||
function DictBaidu({ text, setCopyText }) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [dictResult, setDictResult] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
setDictResult(null);
|
||||
|
||||
// if (!isValidWord(text)) {
|
||||
// useEffect(() => {
|
||||
// if (!data) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // todo: 修复
|
||||
// const dictRes = await apiTranslate({
|
||||
// text,
|
||||
// apiSlug: OPT_TRANS_BAIDU,
|
||||
// fromLang: "en",
|
||||
// toLang: "zh-CN",
|
||||
// });
|
||||
// const copyText = [
|
||||
// data.src,
|
||||
// data.voice
|
||||
// ?.map(Object.entries)
|
||||
// .map((item) => item[0])
|
||||
// .map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`)
|
||||
// .join(" "),
|
||||
// data.content[0].mean
|
||||
// .map(({ pre, cont }) => {
|
||||
// return `${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`;
|
||||
// })
|
||||
// .join("\n"),
|
||||
// ].join("\n");
|
||||
|
||||
// if (dictRes[2]?.type === 1) {
|
||||
// setDictResult(JSON.parse(dictRes[2].result));
|
||||
// }
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [text]);
|
||||
// setCopyText(copyText);
|
||||
// }, [data, setCopyText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dictResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
const copyText = [
|
||||
dictResult.src,
|
||||
dictResult.voice
|
||||
?.map(Object.entries)
|
||||
.map((item) => item[0])
|
||||
.map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`)
|
||||
.join(" "),
|
||||
dictResult.content[0].mean
|
||||
.map(({ pre, cont }) => {
|
||||
return `${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`;
|
||||
})
|
||||
.join("\n"),
|
||||
].join("\n");
|
||||
|
||||
setCopyText(copyText);
|
||||
}, [dictResult, setCopyText]);
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress size={16} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
|
||||
return <Typography>baidu: {text}</Typography>;
|
||||
return <Typography>baidu dict not supported yet</Typography>;
|
||||
|
||||
{
|
||||
/* {dictResult && (
|
||||
@@ -109,11 +68,56 @@ function DictBaidu({ text, setCopyText }) {
|
||||
}
|
||||
}
|
||||
|
||||
function DictYoudao({ text, setCopyText }) {
|
||||
const { loading, error, data } = useAsyncNow(apiYoudaoDict, text);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const copyText = [
|
||||
text,
|
||||
data?.ec?.word?.trs
|
||||
?.map(({ pos, tran }) => `${pos ? `[${pos}] ` : ""}${tran}`)
|
||||
.join("\n"),
|
||||
].join("\n");
|
||||
|
||||
setCopyText(copyText);
|
||||
}, [data, setCopyText]);
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress size={16} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography component="div">
|
||||
<Typography component="ul">
|
||||
{data?.ec?.word?.trs?.map(({ pos, tran }, idx) => (
|
||||
<Typography component="li" key={idx}>
|
||||
{pos && `[${pos}] `}
|
||||
{tran}
|
||||
</Typography>
|
||||
))}
|
||||
</Typography>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DictCont({ text, enDict }) {
|
||||
const [copyText, setCopyText] = useState(text);
|
||||
|
||||
const dictMap = {
|
||||
[OPT_TRANS_BAIDU]: <DictBaidu text={text} setCopyText={setCopyText} />,
|
||||
[OPT_DICT_BAIDU]: <DictBaidu text={text} setCopyText={setCopyText} />,
|
||||
[OPT_DICT_YOUDAO]: <DictYoudao text={text} setCopyText={setCopyText} />,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -130,6 +134,8 @@ export default function DictCont({ text, enDict }) {
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
{dictMap[enDict] || <Typography>Dict not support</Typography>}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { apiBaiduSuggest } from "../../apis";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import { apiBaiduSuggest, apiYoudaoSuggest } from "../../apis";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { OPT_SUG_BAIDU, OPT_SUG_YOUDAO } from "../../config";
|
||||
import { useAsyncNow } from "../../hooks/Fetch";
|
||||
|
||||
export default function SugCont({ text }) {
|
||||
const [sugs, setSugs] = useState([]);
|
||||
function SugBaidu({ text }) {
|
||||
const { loading, error, data } = useAsyncNow(apiBaiduSuggest, text);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
setSugs(await apiBaiduSuggest(text));
|
||||
} catch (err) {
|
||||
// skip
|
||||
if (loading) {
|
||||
return <CircularProgress size={16} />;
|
||||
}
|
||||
})();
|
||||
}, [text]);
|
||||
|
||||
if (sugs.length === 0) {
|
||||
return;
|
||||
if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
{sugs.map(({ k, v }) => (
|
||||
<>
|
||||
{data.map(({ k, v }) => (
|
||||
<Typography component="div" key={k}>
|
||||
<Typography>{k}</Typography>
|
||||
<Typography component="ul" style={{ margin: "0" }}>
|
||||
@@ -30,6 +32,49 @@ export default function SugCont({ text }) {
|
||||
</Typography>
|
||||
</Typography>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SugYoudao({ text }) {
|
||||
const { loading, error, data } = useAsyncNow(apiYoudaoSuggest, text);
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress size={16} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.map(({ entry, explain }) => (
|
||||
<Typography component="div" key={entry}>
|
||||
<Typography>{entry}</Typography>
|
||||
<Typography component="ul" style={{ margin: "0" }}>
|
||||
<Typography component="li">{explain}</Typography>
|
||||
</Typography>
|
||||
</Typography>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SugCont({ text, enSug }) {
|
||||
const sugMap = {
|
||||
[OPT_SUG_BAIDU]: <SugBaidu text={text} />,
|
||||
[OPT_SUG_YOUDAO]: <SugYoudao text={text} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<Divider />
|
||||
{sugMap[enSug] || <Typography>Sug not support</Typography>}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function TranBox({
|
||||
text,
|
||||
setText,
|
||||
setShowBox,
|
||||
tranboxSetting: { apiSlugs, fromLang, toLang, toLang2 },
|
||||
tranboxSetting: { enDict, enSug, apiSlugs, fromLang, toLang, toLang2 },
|
||||
transApis,
|
||||
boxSize,
|
||||
setBoxSize,
|
||||
@@ -128,7 +128,6 @@ export default function TranBox({
|
||||
setFollowSelection,
|
||||
extStyles = "",
|
||||
langDetector,
|
||||
enDict,
|
||||
}) {
|
||||
const [mouseHover, setMouseHover] = useState(false);
|
||||
// todo: 这里的 SettingProvider 不应和 background 的共用
|
||||
@@ -168,6 +167,7 @@ export default function TranBox({
|
||||
simpleStyle={simpleStyle}
|
||||
langDetector={langDetector}
|
||||
enDict={enDict}
|
||||
enSug={enSug}
|
||||
/>
|
||||
</Box>
|
||||
</DraggableResizable>
|
||||
|
||||
@@ -12,8 +12,10 @@ import {
|
||||
OPT_LANGS_TO,
|
||||
OPT_LANGDETECTOR_ALL,
|
||||
OPT_DICT_ALL,
|
||||
OPT_SUG_ALL,
|
||||
OPT_LANGS_MAP,
|
||||
OPT_DICT_MAP,
|
||||
OPT_SUG_MAP,
|
||||
} from "../../config";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import TranCont from "./TranCont";
|
||||
@@ -30,11 +32,12 @@ export default function TranForm({
|
||||
apiSlugs: initApiSlugs,
|
||||
fromLang: initFromLang,
|
||||
toLang: initToLang,
|
||||
toLang2,
|
||||
toLang2: initToLang2,
|
||||
transApis,
|
||||
simpleStyle = false,
|
||||
langDetector: initLangDetector = "-",
|
||||
enDict: initEnDict = "-",
|
||||
enSug: initEnSug = "-",
|
||||
isPlaygound = false,
|
||||
}) {
|
||||
const i18n = useI18n();
|
||||
@@ -44,11 +47,19 @@ export default function TranForm({
|
||||
const [apiSlugs, setApiSlugs] = useState(initApiSlugs);
|
||||
const [fromLang, setFromLang] = useState(initFromLang);
|
||||
const [toLang, setToLang] = useState(initToLang);
|
||||
const [toLang2, setToLang2] = useState(initToLang2);
|
||||
const [langDetector, setLangDetector] = useState(initLangDetector);
|
||||
const [enDict, setEnDict] = useState(initEnDict);
|
||||
const [enSug, setEnSug] = useState(initEnSug);
|
||||
const [deLang, setDeLang] = useState("");
|
||||
const [deLoading, setDeLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editMode) {
|
||||
setEditText(text);
|
||||
}
|
||||
}, [text, editMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!text.trim()) {
|
||||
setDeLang("");
|
||||
@@ -96,6 +107,7 @@ export default function TranForm({
|
||||
);
|
||||
|
||||
const isWord = useMemo(() => isValidWord(text), [text]);
|
||||
const xs = useMemo(() => (isPlaygound ? 3 : 4), [isPlaygound]);
|
||||
|
||||
return (
|
||||
<Stack spacing={simpleStyle ? 1 : 2}>
|
||||
@@ -103,18 +115,18 @@ export default function TranForm({
|
||||
<>
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{
|
||||
multiple: true,
|
||||
MenuProps: { disablePortal: true },
|
||||
MenuProps: { disablePortal: !isPlaygound },
|
||||
}}
|
||||
fullWidth
|
||||
size="small"
|
||||
value={apiSlugs}
|
||||
name="apiSlugs"
|
||||
label={i18n("translate_service")}
|
||||
label={i18n("translate_service_multiple")}
|
||||
onChange={(e) => {
|
||||
setApiSlugs(e.target.value);
|
||||
}}
|
||||
@@ -126,10 +138,10 @@ export default function TranForm({
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
SelectProps={{ MenuProps: { disablePortal: !isPlaygound } }}
|
||||
fullWidth
|
||||
size="small"
|
||||
name="fromLang"
|
||||
@@ -146,10 +158,10 @@ export default function TranForm({
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
SelectProps={{ MenuProps: { disablePortal: !isPlaygound } }}
|
||||
fullWidth
|
||||
size="small"
|
||||
name="toLang"
|
||||
@@ -166,16 +178,37 @@ export default function TranForm({
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{isPlaygound && (
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||
<>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
SelectProps={{
|
||||
MenuProps: { disablePortal: !isPlaygound },
|
||||
}}
|
||||
fullWidth
|
||||
size="small"
|
||||
name="toLang2"
|
||||
value={toLang2}
|
||||
label={i18n("to_lang2")}
|
||||
onChange={(e) => {
|
||||
setToLang2(e.target.value);
|
||||
}}
|
||||
>
|
||||
{OPT_LANGS_TO.map(([lang, name]) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{
|
||||
MenuProps: { disablePortal: !isPlaygound },
|
||||
}}
|
||||
fullWidth
|
||||
size="small"
|
||||
name="enDict"
|
||||
@@ -193,10 +226,35 @@ export default function TranForm({
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
SelectProps={{
|
||||
MenuProps: { disablePortal: !isPlaygound },
|
||||
}}
|
||||
fullWidth
|
||||
size="small"
|
||||
name="enSug"
|
||||
value={enSug}
|
||||
label={i18n("english_suggest")}
|
||||
onChange={(e) => {
|
||||
setEnSug(e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
|
||||
{OPT_SUG_ALL.map((item) => (
|
||||
<MenuItem value={item} key={item}>
|
||||
{item}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{
|
||||
MenuProps: { disablePortal: !isPlaygound },
|
||||
}}
|
||||
fullWidth
|
||||
size="small"
|
||||
name="langDetector"
|
||||
@@ -214,7 +272,7 @@ export default function TranForm({
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={4} sm={4} md={4} lg={4}>
|
||||
<Grid item xs={xs}>
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
@@ -229,9 +287,10 @@ export default function TranForm({
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box>
|
||||
<TextField
|
||||
@@ -267,6 +326,8 @@ export default function TranForm({
|
||||
size="small"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setEditMode(false);
|
||||
setText(editText.trim());
|
||||
}}
|
||||
>
|
||||
<DoneIcon fontSize="inherit" />
|
||||
@@ -295,10 +356,11 @@ export default function TranForm({
|
||||
))}
|
||||
|
||||
{isWord && OPT_DICT_MAP.has(enDict) && (
|
||||
<>
|
||||
<DictCont text={text} enDict={enDict} />
|
||||
<SugCont text={text} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isWord && OPT_SUG_MAP.has(enSug) && (
|
||||
<SugCont text={text} enSug={enSug} />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
OPT_TRANBOX_TRIGGER_CLICK,
|
||||
OPT_TRANBOX_TRIGGER_HOVER,
|
||||
OPT_TRANBOX_TRIGGER_SELECT,
|
||||
OPT_DICT_BAIDU,
|
||||
} from "../../config";
|
||||
import { isMobile } from "../../libs/mobile";
|
||||
import { kissLog } from "../../libs/log";
|
||||
@@ -35,7 +34,6 @@ export default function Slection({
|
||||
btnOffsetY,
|
||||
boxOffsetX = 0,
|
||||
boxOffsetY = 10,
|
||||
enDict = OPT_DICT_BAIDU,
|
||||
} = tranboxSetting;
|
||||
|
||||
const boxWidth =
|
||||
@@ -238,7 +236,6 @@ export default function Slection({
|
||||
setFollowSelection={setFollowSelection}
|
||||
// extStyles={extStyles}
|
||||
langDetector={langDetector}
|
||||
enDict={enDict}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user