feat: support youdao dict

This commit is contained in:
Gabe
2025-10-03 18:28:50 +08:00
parent 65e8fabe7d
commit 171dbb7509
19 changed files with 631 additions and 228 deletions

View File

@@ -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(() => {
// if (!data) {
// return;
// }
useEffect(() => {
(async () => {
try {
setLoading(true);
setError("");
setDictResult(null);
// 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 (!isValidWord(text)) {
// return;
// }
// setCopyText(copyText);
// }, [data, setCopyText]);
// // todo: 修复
// const dictRes = await apiTranslate({
// text,
// apiSlug: OPT_TRANS_BAIDU,
// fromLang: "en",
// toLang: "zh-CN",
// });
// if (dictRes[2]?.type === 1) {
// setDictResult(JSON.parse(dictRes[2].result));
// }
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
})();
}, [text]);
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>
);

View File

@@ -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
}
})();
}, [text]);
if (loading) {
return <CircularProgress size={16} />;
}
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>
);
}

View File

@@ -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>

View File

@@ -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,73 +178,120 @@ export default function TranForm({
))}
</TextField>
</Grid>
{isPlaygound && (
<>
<Grid item xs={xs}>
<TextField
select
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"
value={enDict}
label={i18n("english_dict")}
onChange={(e) => {
setEnDict(e.target.value);
}}
>
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
{OPT_DICT_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="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"
value={langDetector}
label={i18n("detected_lang")}
onChange={(e) => {
setLangDetector(e.target.value);
}}
>
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
{OPT_LANGDETECTOR_ALL.map((item) => (
<MenuItem value={item} key={item}>
{item}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={xs}>
<TextField
fullWidth
size="small"
name="deLang"
value={deLang && OPT_LANGS_MAP.get(deLang)}
label={i18n("detected_result")}
disabled
InputProps={{
startAdornment: deLoading ? (
<CircularProgress size={16} />
) : null,
}}
/>
</Grid>
</>
)}
</Grid>
</Box>
{isPlaygound && (
<Box>
<Grid container spacing={2} columns={12}>
<Grid item xs={4} sm={4} md={4} lg={4}>
<TextField
select
SelectProps={{ MenuProps: { disablePortal: true } }}
fullWidth
size="small"
name="enDict"
value={enDict}
label={i18n("english_dict")}
onChange={(e) => {
setEnDict(e.target.value);
}}
>
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
{OPT_DICT_ALL.map((item) => (
<MenuItem value={item} key={item}>
{item}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={4} sm={4} md={4} lg={4}>
<TextField
select
SelectProps={{ MenuProps: { disablePortal: true } }}
fullWidth
size="small"
name="langDetector"
value={langDetector}
label={i18n("detected_lang")}
onChange={(e) => {
setLangDetector(e.target.value);
}}
>
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
{OPT_LANGDETECTOR_ALL.map((item) => (
<MenuItem value={item} key={item}>
{item}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={4} sm={4} md={4} lg={4}>
<TextField
fullWidth
size="small"
name="deLang"
value={deLang && OPT_LANGS_MAP.get(deLang)}
label={i18n("detected_result")}
disabled
InputProps={{
startAdornment: deLoading ? (
<CircularProgress size={16} />
) : null,
}}
/>
</Grid>
</Grid>
</Box>
)}
<Box>
<TextField
size="small"
@@ -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} />
</>
<DictCont text={text} enDict={enDict} />
)}
{isWord && OPT_SUG_MAP.has(enSug) && (
<SugCont text={text} enSug={enSug} />
)}
</Stack>
);

View File

@@ -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}
/>
)}