feat: support bing dict

This commit is contained in:
Gabe
2025-10-03 22:07:48 +08:00
parent 171dbb7509
commit c353c88db8
7 changed files with 142 additions and 118 deletions

View File

@@ -3,6 +3,7 @@ import { fetchData } from "../libs/fetch";
import { import {
URL_CACHE_TRAN, URL_CACHE_TRAN,
URL_CACHE_DELANG, URL_CACHE_DELANG,
URL_CACHE_BINGDICT,
KV_SALT_SYNC, KV_SALT_SYNC,
OPT_LANGS_TO_SPEC, OPT_LANGS_TO_SPEC,
OPT_LANGS_SPEC_DEFAULT, OPT_LANGS_SPEC_DEFAULT,
@@ -107,6 +108,60 @@ export const apiMicrosoftLangdetect = async (text) => {
return ""; return "";
}; };
/**
* Microsoft词典
* @param {*} text
* @returns
*/
export const apiMicrosoftDict = async (text) => {
const cacheOpts = { text };
const cacheInput = `${URL_CACHE_BINGDICT}?${queryString.stringify(cacheOpts)}`;
const cache = await getHttpCachePolyfill(cacheInput);
if (cache) {
return cache;
}
const host = "https://cn.bing.com";
const url = `${host}/dict/search?q=${text}`;
const str = await fetchData(url, {}, { useCache: false });
const parser = new DOMParser();
const doc = parser.parseFromString(str, "text/html");
const word = doc.querySelector("#headword > h1").textContent.trim();
if (!word) {
return null;
}
const trs = [];
doc.querySelectorAll("div.qdef > ul > li").forEach(($li) => {
const pos = $li.querySelector(".pos")?.textContent?.trim();
const def = $li.querySelector(".def")?.textContent?.trim();
trs.push({ pos, def });
});
const aus = [];
const $audioUS = doc.querySelector("#bigaud_us");
const $audioUK = doc.querySelector("#bigaud_uk");
if ($audioUS) {
const audioUS = host + $audioUS?.dataset?.mp3link;
const $phoneticUS = $audioUS.parentElement?.previousElementSibling;
const phoneticUS = $phoneticUS?.textContent?.trim();
aus.push({ key: "US", audio: audioUS, phonetic: phoneticUS });
}
if ($audioUK) {
const audioUK = host + $audioUK?.dataset?.mp3link;
const $phoneticUK = $audioUK.parentElement?.previousElementSibling;
const phoneticUK = $phoneticUK?.textContent?.trim();
aus.push({ key: "UK", audio: audioUK, phonetic: phoneticUK });
}
const res = { word, trs, aus };
putHttpCachePolyfill(cacheInput, null, res);
return res;
};
/** /**
* 百度语言识别 * 百度语言识别
* @param {*} text * @param {*} text

View File

@@ -14,8 +14,9 @@ export const INPUT_PLACE_KEY = "{{key}}"; // 占位符
export const INPUT_PLACE_MODEL = "{{model}}"; // 占位符 export const INPUT_PLACE_MODEL = "{{model}}"; // 占位符
export const OPT_DICT_BAIDU = "Baidu"; export const OPT_DICT_BAIDU = "Baidu";
export const OPT_DICT_BING = "Bing";
export const OPT_DICT_YOUDAO = "Youdao"; export const OPT_DICT_YOUDAO = "Youdao";
export const OPT_DICT_ALL = [OPT_DICT_BAIDU, OPT_DICT_YOUDAO]; export const OPT_DICT_ALL = [OPT_DICT_BING, OPT_DICT_YOUDAO];
export const OPT_DICT_MAP = new Set(OPT_DICT_ALL); export const OPT_DICT_MAP = new Set(OPT_DICT_ALL);
export const OPT_SUG_BAIDU = "Baidu"; export const OPT_SUG_BAIDU = "Baidu";

View File

@@ -2,6 +2,7 @@ import { APP_LCNAME } from "./app";
export const URL_CACHE_TRAN = `https://${APP_LCNAME}/translate`; export const URL_CACHE_TRAN = `https://${APP_LCNAME}/translate`;
export const URL_CACHE_DELANG = `https://${APP_LCNAME}/detectlang`; export const URL_CACHE_DELANG = `https://${APP_LCNAME}/detectlang`;
export const URL_CACHE_BINGDICT = `https://${APP_LCNAME}/bingdict`;
export const URL_KISS_WORKER = "https://github.com/fishjar/kiss-worker"; export const URL_KISS_WORKER = "https://github.com/fishjar/kiss-worker";
export const URL_KISS_PROXY = "https://github.com/fishjar/kiss-proxy"; export const URL_KISS_PROXY = "https://github.com/fishjar/kiss-proxy";

View File

@@ -22,6 +22,7 @@ import { apiTranslate } from "../../apis";
import { OPT_TRANS_BAIDU, PHONIC_MAP } from "../../config"; import { OPT_TRANS_BAIDU, PHONIC_MAP } from "../../config";
import { useConfirm } from "../../hooks/Confirm"; import { useConfirm } from "../../hooks/Confirm";
import { useSetting } from "../../hooks/Setting"; import { useSetting } from "../../hooks/Setting";
import { DICT_MAP } from "../Selection/DictMap";
function FavAccordion({ word, index }) { function FavAccordion({ word, index }) {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
@@ -55,6 +56,7 @@ function FavAccordion({ word, index }) {
export default function FavWords() { export default function FavWords() {
const i18n = useI18n(); const i18n = useI18n();
const { favList, wordList, mergeWords, clearWords } = useFavWords(); const { favList, wordList, mergeWords, clearWords } = useFavWords();
const { setting } = useSetting();
const confirm = useConfirm(); const confirm = useConfirm();
const handleImport = (data) => { const handleImport = (data) => {
@@ -80,41 +82,22 @@ export default function FavWords() {
}; };
const handleTranslation = async () => { const handleTranslation = async () => {
const { enDict } = setting?.tranboxSetting;
const dict = DICT_MAP[enDict];
if (!dict) return "";
const tranList = []; const tranList = [];
for (const text of wordList) { for (const word of wordList) {
try { try {
// todo: 修复 const data = await dict.apiFn(word);
const dictRes = await apiTranslate({ const tran = dict.toText(data);
text, tranList.push([word, tran].join("\n"));
translator: OPT_TRANS_BAIDU,
fromLang: "en",
toLang: "zh-CN",
});
if (dictRes[2]?.type === 1) {
tranList.push(JSON.parse(dictRes[2].result));
}
} catch (err) { } catch (err) {
// skip // skip
} }
} }
return tranList return tranList.join("\n\n");
.map((dictResult) =>
[
`## ${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\n")
)
.join("\n\n");
}; };
return ( return (

View File

@@ -1,9 +1,9 @@
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import VolumeUpIcon from "@mui/icons-material/VolumeUp"; import VolumeUpIcon from "@mui/icons-material/VolumeUp";
import { useTextAudio } from "../../hooks/Audio"; import { useAudio } from "../../hooks/Audio";
export default function AudioBtn({ text, lan = "uk" }) { export default function AudioBtn({ src }) {
const { error, ready, playing, onPlay } = useTextAudio(text, lan); const { error, ready, playing, onPlay } = useAudio(src);
if (error || !ready) { if (error || !ready) {
return ( return (

View File

@@ -1,90 +1,28 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useMemo } from "react";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import FavBtn from "./FavBtn"; import FavBtn from "./FavBtn";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import AudioBtn from "./AudioBtn";
import CircularProgress from "@mui/material/CircularProgress"; import CircularProgress from "@mui/material/CircularProgress";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import Alert from "@mui/material/Alert"; import Alert from "@mui/material/Alert";
import { OPT_DICT_BAIDU, OPT_DICT_YOUDAO, PHONIC_MAP } from "../../config";
import CopyBtn from "./CopyBtn"; import CopyBtn from "./CopyBtn";
import { useAsyncNow } from "../../hooks/Fetch"; import { useAsyncNow } from "../../hooks/Fetch";
import { apiYoudaoDict } from "../../apis"; import { DICT_MAP } from "./DictMap";
function DictBaidu({ text, setCopyText }) { function DictBody({ text, setCopyText, dict }) {
// useEffect(() => { const { loading, error, data } = useAsyncNow(dict.apiFn, text);
// if (!data) {
// return;
// }
// 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");
// setCopyText(copyText);
// }, [data, setCopyText]);
return <Typography>baidu dict not supported yet</Typography>;
{
/* {dictResult && (
<Typography component="div">
<Typography component="div">
{dictResult.voice
?.map(Object.entries)
.map((item) => item[0])
.map(([key, val]) => (
<Typography
component="div"
key={key}
style={{ display: "inline-block" }}
>
<Typography component="span">{`${PHONIC_MAP[key]?.[0] || key} ${val}`}</Typography>
<AudioBtn text={dictResult.src} lan={PHONIC_MAP[key]?.[1]} />
</Typography>
))}
</Typography>
<Typography component="ul">
{dictResult.content[0].mean.map(({ pre, cont }, idx) => (
<Typography component="li" key={idx}>
{pre && `[${pre}] `}
{Object.keys(cont).join("; ")}
</Typography>
))}
</Typography>
</Typography>
)} */
}
}
function DictYoudao({ text, setCopyText }) {
const { loading, error, data } = useAsyncNow(apiYoudaoDict, text);
useEffect(() => { useEffect(() => {
if (!data) { if (!data) {
return; return;
} }
const copyText = [ const copyText = [text, dict.toText(data)].join("\n");
text,
data?.ec?.word?.trs
?.map(({ pos, tran }) => `${pos ? `[${pos}] ` : ""}${tran}`)
.join("\n"),
].join("\n");
setCopyText(copyText); setCopyText(copyText);
}, [data, setCopyText]); }, [data, text, dict, setCopyText]);
const uiAudio = useMemo(() => dict.uiAudio(data), [data, dict]);
const uiTrans = useMemo(() => dict.uiTrans(data), [data, dict]);
if (loading) { if (loading) {
return <CircularProgress size={16} />; return <CircularProgress size={16} />;
@@ -95,30 +33,20 @@ function DictYoudao({ text, setCopyText }) {
} }
if (!data) { if (!data) {
return; return <Typography>Empty result</Typography>;
} }
return ( return (
<Typography component="div"> <Typography component="div">
<Typography component="ul"> {uiAudio}
{data?.ec?.word?.trs?.map(({ pos, tran }, idx) => ( {uiTrans}
<Typography component="li" key={idx}>
{pos && `[${pos}] `}
{tran}
</Typography>
))}
</Typography>
</Typography> </Typography>
); );
} }
export default function DictCont({ text, enDict }) { export default function DictCont({ text, enDict }) {
const [copyText, setCopyText] = useState(text); const [copyText, setCopyText] = useState(text);
const dict = DICT_MAP[enDict];
const dictMap = {
[OPT_DICT_BAIDU]: <DictBaidu text={text} setCopyText={setCopyText} />,
[OPT_DICT_YOUDAO]: <DictYoudao text={text} setCopyText={setCopyText} />,
};
return ( return (
<Stack spacing={1}> <Stack spacing={1}>
@@ -136,7 +64,7 @@ export default function DictCont({ text, enDict }) {
<Divider /> <Divider />
{dictMap[enDict] || <Typography>Dict not support</Typography>} {dict && <DictBody text={text} setCopyText={setCopyText} dict={dict} />}
</Stack> </Stack>
); );
} }

View File

@@ -0,0 +1,56 @@
import Typography from "@mui/material/Typography";
import AudioBtn from "./AudioBtn";
import { OPT_DICT_BING, OPT_DICT_YOUDAO } from "../../config";
import { apiMicrosoftDict, apiYoudaoDict } from "../../apis";
export const DICT_MAP = {
[OPT_DICT_BING]: {
apiFn: apiMicrosoftDict,
toText: (data) =>
data.trs
?.map(({ pos, def }) => `${pos ? `[${pos}] ` : ""}${def}`)
.join("\n"),
uiAudio: (data) => (
<Typography component="div">
{data?.aus.map(({ key, audio, phonetic }) => (
<Typography
component="div"
key={key}
style={{ display: "inline-block" }}
>
<Typography component="span">{phonetic}</Typography>
<AudioBtn src={audio} />
</Typography>
))}
</Typography>
),
uiTrans: (data) => (
<Typography component="ul">
{data?.trs?.map(({ pos, def }, idx) => (
<Typography component="li" key={idx}>
{pos && `[${pos}] `}
{def}
</Typography>
))}
</Typography>
),
},
[OPT_DICT_YOUDAO]: {
apiFn: apiYoudaoDict,
toText: (data) =>
data?.ec?.word?.trs
?.map(({ pos, tran }) => `${pos ? `[${pos}] ` : ""}${tran}`)
.join("\n"),
uiAudio: () => null,
uiTrans: (data) => (
<Typography component="ul">
{data?.ec?.word?.trs?.map(({ pos, tran }, idx) => (
<Typography component="li" key={idx}>
{pos && `[${pos}] `}
{tran}
</Typography>
))}
</Typography>
),
},
};