This commit is contained in:
@@ -2,16 +2,14 @@ import Button from "@/components/Button";
|
|||||||
import IconClick from "@/components/IconClick";
|
import IconClick from "@/components/IconClick";
|
||||||
import IMAGES from "@/config/images";
|
import IMAGES from "@/config/images";
|
||||||
import { Letter, SupportedAlphabets } from "@/interfaces";
|
import { Letter, SupportedAlphabets } from "@/interfaces";
|
||||||
import { Dispatch, KeyboardEvent, SetStateAction, useEffect, useRef, useState } from "react";
|
import { Dispatch, KeyboardEvent, SetStateAction, useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function MemoryCard(
|
export default function MemoryCard(
|
||||||
{
|
{
|
||||||
alphabet,
|
alphabet,
|
||||||
language,
|
|
||||||
setChosenAlphabet
|
setChosenAlphabet
|
||||||
}: {
|
}: {
|
||||||
alphabet: Letter[],
|
alphabet: Letter[],
|
||||||
language: string,
|
|
||||||
setChosenAlphabet: Dispatch<SetStateAction<SupportedAlphabets | null>>
|
setChosenAlphabet: Dispatch<SetStateAction<SupportedAlphabets | null>>
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -34,7 +32,7 @@ export default function MemoryCard(
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex justify-center items-center" onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => e.preventDefault()}>
|
<div className="w-full flex justify-center items-center" onKeyDown={(e: KeyboardEvent<HTMLDivElement>) => e.preventDefault()}>
|
||||||
{/* <div className="m-4 p-4 w-full md:w-[60dvw] flex-col rounded-2xl shadow border-gray-200 border flex justify-center items-center">
|
<div className="m-4 p-4 w-full md:w-[60dvw] flex-col rounded-2xl shadow border-gray-200 border flex justify-center items-center">
|
||||||
<div className="w-full flex justify-end items-center">
|
<div className="w-full flex justify-end items-center">
|
||||||
<IconClick size={32} alt="close" src={IMAGES.close} onClick={() => setChosenAlphabet(null)}></IconClick>
|
<IconClick size={32} alt="close" src={IMAGES.close} onClick={() => setChosenAlphabet(null)}></IconClick>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,12 +45,12 @@ export default function MemoryCard(
|
|||||||
<IconClick size={48} alt="more" src={IMAGES.more_horiz} onClick={() => setMore(!more)}></IconClick>
|
<IconClick size={48} alt="more" src={IMAGES.more_horiz} onClick={() => setMore(!more)}></IconClick>
|
||||||
{
|
{
|
||||||
more ? (<>
|
more ? (<>
|
||||||
<Button className="w-20" label={letterDisplay ? '隐藏字母' : '显示字母'} onClick={() => { setLetterDisplay(!letterDisplay) }}></Button>
|
<Button className="w-20" onClick={() => { setLetterDisplay(!letterDisplay) }}>{letterDisplay ? '隐藏字母' : '显示字母'}</Button>
|
||||||
<Button className="w-20" label={ipaDisplay ? '隐藏IPA' : '显示IPA'} onClick={() => { setIPADisplay(!ipaDisplay) }}></Button>
|
<Button className="w-20" onClick={() => { setIPADisplay(!ipaDisplay) }}>{ipaDisplay ? '隐藏IPA' : '显示IPA'}</Button>
|
||||||
</>) : (<></>)
|
</>) : (<></>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,6 @@ export default function Alphabet() {
|
|||||||
return (<>
|
return (<>
|
||||||
<Navbar></Navbar>
|
<Navbar></Navbar>
|
||||||
<MemoryCard
|
<MemoryCard
|
||||||
language={chosenAlphabet}
|
|
||||||
alphabet={alphabetData[chosenAlphabet]}
|
alphabet={alphabetData[chosenAlphabet]}
|
||||||
setChosenAlphabet={setChosenAlphabet}>
|
setChosenAlphabet={setChosenAlphabet}>
|
||||||
</MemoryCard>
|
</MemoryCard>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import SaveList from "./SaveList";
|
|||||||
import { TextSpeakerItemSchema } from "@/interfaces";
|
import { TextSpeakerItemSchema } from "@/interfaces";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { Navbar } from "@/components/Navbar";
|
import { Navbar } from "@/components/Navbar";
|
||||||
|
import { VOICES } from "@/config/locales";
|
||||||
|
|
||||||
export default function TextSpeaker() {
|
export default function TextSpeaker() {
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@@ -25,19 +26,7 @@ export default function TextSpeaker() {
|
|||||||
const [ipa, setIPA] = useState<string>('');
|
const [ipa, setIPA] = useState<string>('');
|
||||||
const objurlRef = useRef<string | null>(null);
|
const objurlRef = useRef<string | null>(null);
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [voicesData, setVoicesData] = useState<{
|
|
||||||
locale: string,
|
|
||||||
short_name: string
|
|
||||||
}[] | null>(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const { playAudio, stopAudio, audioRef } = useAudioPlayer();
|
const { playAudio, stopAudio, audioRef } = useAudioPlayer();
|
||||||
useEffect(() => {
|
|
||||||
fetch('/list_of_voices.json')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setVoicesData)
|
|
||||||
.catch(() => setVoicesData(null))
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const audio = audioRef.current;
|
const audio = audioRef.current;
|
||||||
if (!audio) return;
|
if (!audio) return;
|
||||||
@@ -56,11 +45,6 @@ export default function TextSpeaker() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [audioRef, autopause]);
|
}, [audioRef, autopause]);
|
||||||
|
|
||||||
|
|
||||||
if (loading) return <div>加载中...</div>;
|
|
||||||
if (!voicesData) return <div>加载失败</div>;
|
|
||||||
|
|
||||||
|
|
||||||
const speak = async () => {
|
const speak = async () => {
|
||||||
if (processing) return;
|
if (processing) return;
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
@@ -103,7 +87,7 @@ export default function TextSpeaker() {
|
|||||||
theLocale = textinfo.locale as string;
|
theLocale = textinfo.locale as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const voice = voicesData.find(v => v.locale.startsWith(theLocale));
|
const voice = VOICES.find(v => v.locale.startsWith(theLocale));
|
||||||
if (!voice) throw 'Voice not found.';
|
if (!voice) throw 'Voice not found.';
|
||||||
|
|
||||||
objurlRef.current = await getTTSAudioUrl(
|
objurlRef.current = await getTTSAudioUrl(
|
||||||
|
|||||||
@@ -7,15 +7,11 @@ import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
|||||||
import IMAGES from "@/config/images";
|
import IMAGES from "@/config/images";
|
||||||
import { getTTSAudioUrl } from "@/utils";
|
import { getTTSAudioUrl } from "@/utils";
|
||||||
import { Navbar } from "@/components/Navbar";
|
import { Navbar } from "@/components/Navbar";
|
||||||
|
import { VOICES } from "@/config/locales";
|
||||||
|
|
||||||
export default function Translator() {
|
export default function Translator() {
|
||||||
const [ipaEnabled, setIPAEnabled] = useState(true);
|
const [ipaEnabled, setIPAEnabled] = useState(true);
|
||||||
const [voicesData, setVoicesData] = useState<{
|
const [targetLang, setTargetLang] = useState('Chinese');
|
||||||
locale: string,
|
|
||||||
short_name: string
|
|
||||||
}[] | null>(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [targetLang, setTargetLang] = useState('Italian');
|
|
||||||
|
|
||||||
const [sourceText, setSourceText] = useState('');
|
const [sourceText, setSourceText] = useState('');
|
||||||
const [targetText, setTargetText] = useState('');
|
const [targetText, setTargetText] = useState('');
|
||||||
@@ -23,21 +19,10 @@ export default function Translator() {
|
|||||||
const [targetIPA, setTargetIPA] = useState('');
|
const [targetIPA, setTargetIPA] = useState('');
|
||||||
const [sourceLocale, setSourceLocale] = useState<string | null>(null);
|
const [sourceLocale, setSourceLocale] = useState<string | null>(null);
|
||||||
const [targetLocale, setTargetLocale] = useState<string | null>(null);
|
const [targetLocale, setTargetLocale] = useState<string | null>(null);
|
||||||
|
|
||||||
const [translating, setTranslating] = useState(false);
|
const [translating, setTranslating] = useState(false);
|
||||||
const { playAudio } = useAudioPlayer();
|
const { playAudio } = useAudioPlayer();
|
||||||
|
|
||||||
useEffect(() => {
|
const tl = ['Chinese', 'English', 'Italian'];
|
||||||
fetch('/list_of_voices.json')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(setVoicesData)
|
|
||||||
.catch(() => setVoicesData(null))
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}, []);
|
|
||||||
if (loading) return <div>加载中...</div>;
|
|
||||||
if (!voicesData) return <div>加载失败</div>;
|
|
||||||
|
|
||||||
const tl = ['English', 'Italian', 'Japanese'];
|
|
||||||
|
|
||||||
const inputLanguage = () => {
|
const inputLanguage = () => {
|
||||||
const lang = prompt('Input a language.')?.trim();
|
const lang = prompt('Input a language.')?.trim();
|
||||||
@@ -136,7 +121,7 @@ export default function Translator() {
|
|||||||
const info = await res.json();
|
const info = await res.json();
|
||||||
setSourceLocale(info.locale);
|
setSourceLocale(info.locale);
|
||||||
|
|
||||||
const voice = voicesData.find(v => v.locale.startsWith(info.locale));
|
const voice = VOICES.find(v => v.locale.startsWith(info.locale));
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,7 +135,7 @@ export default function Translator() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const voice = voicesData.find(v => v.locale.startsWith(sourceLocale!));
|
const voice = VOICES.find(v => v.locale.startsWith(sourceLocale!));
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -178,7 +163,7 @@ export default function Translator() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const voice = voicesData.find(v => v.locale.startsWith(targetLocale!));
|
const voice = VOICES.find(v => v.locale.startsWith(targetLocale!));
|
||||||
if (!voice) return;
|
if (!voice) return;
|
||||||
|
|
||||||
const url = await getTTSAudioUrl(targetText, voice.short_name);
|
const url = await getTTSAudioUrl(targetText, voice.short_name);
|
||||||
@@ -227,9 +212,9 @@ export default function Translator() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="option2 w-full flex gap-1 items-center flex-wrap">
|
<div className="option2 w-full flex gap-1 items-center flex-wrap">
|
||||||
<span>translate into</span>
|
<span>translate into</span>
|
||||||
|
<Button onClick={() => { setTargetLang('Chinese') }} selected={targetLang === 'Chinese'}>Chinese</Button>
|
||||||
<Button onClick={() => { setTargetLang('English') }} selected={targetLang === 'English'}>English</Button>
|
<Button onClick={() => { setTargetLang('English') }} selected={targetLang === 'English'}>English</Button>
|
||||||
<Button onClick={() => { setTargetLang('Italian') }} selected={targetLang === 'Italian'}>Italian</Button>
|
<Button onClick={() => { setTargetLang('Italian') }} selected={targetLang === 'Italian'}>Italian</Button>
|
||||||
<Button onClick={() => { setTargetLang('Japanese') }} selected={targetLang === 'Japanese'}>Japanese</Button>
|
|
||||||
<Button onClick={inputLanguage} selected={!(tl.includes(targetLang))}>{'Other' + (tl.includes(targetLang) ? '' : ': ' + targetLang)}</Button>
|
<Button onClick={inputLanguage} selected={!(tl.includes(targetLang))}>{'Other' + (tl.includes(targetLang) ? '' : ': ' + targetLang)}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user