...
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-26 14:13:34 +08:00
parent 54e0eb452b
commit e8bc064ad5
5 changed files with 1224 additions and 190 deletions

View File

@@ -2,16 +2,14 @@ import Button from "@/components/Button";
import IconClick from "@/components/IconClick";
import IMAGES from "@/config/images";
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(
{
alphabet,
language,
setChosenAlphabet
}: {
alphabet: Letter[],
language: string,
setChosenAlphabet: Dispatch<SetStateAction<SupportedAlphabets | null>>
}
) {
@@ -34,7 +32,7 @@ export default function MemoryCard(
}
return (
<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">
<IconClick size={32} alt="close" src={IMAGES.close} onClick={() => setChosenAlphabet(null)}></IconClick>
</div>
@@ -47,12 +45,12 @@ export default function MemoryCard(
<IconClick size={48} alt="more" src={IMAGES.more_horiz} onClick={() => setMore(!more)}></IconClick>
{
more ? (<>
<Button className="w-20" label={letterDisplay ? '隐藏字母' : '显示字母'} onClick={() => { setLetterDisplay(!letterDisplay) }}></Button>
<Button className="w-20" label={ipaDisplay ? '隐藏IPA' : '显示IPA'} onClick={() => { setIPADisplay(!ipaDisplay) }}></Button>
<Button className="w-20" onClick={() => { setLetterDisplay(!letterDisplay) }}>{letterDisplay ? '隐藏字母' : '显示字母'}</Button>
<Button className="w-20" onClick={() => { setIPADisplay(!ipaDisplay) }}>{ipaDisplay ? '隐藏IPA' : '显示IPA'}</Button>
</>) : (<></>)
}
</div>
</div> */}
</div>
</div>
);
}

View File

@@ -75,7 +75,6 @@ export default function Alphabet() {
return (<>
<Navbar></Navbar>
<MemoryCard
language={chosenAlphabet}
alphabet={alphabetData[chosenAlphabet]}
setChosenAlphabet={setChosenAlphabet}>
</MemoryCard>

View File

@@ -10,6 +10,7 @@ import SaveList from "./SaveList";
import { TextSpeakerItemSchema } from "@/interfaces";
import z from "zod";
import { Navbar } from "@/components/Navbar";
import { VOICES } from "@/config/locales";
export default function TextSpeaker() {
const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -25,19 +26,7 @@ export default function TextSpeaker() {
const [ipa, setIPA] = useState<string>('');
const objurlRef = useRef<string | null>(null);
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();
useEffect(() => {
fetch('/list_of_voices.json')
.then(res => res.json())
.then(setVoicesData)
.catch(() => setVoicesData(null))
.finally(() => setLoading(false));
}, []);
useEffect(() => {
const audio = audioRef.current;
if (!audio) return;
@@ -56,11 +45,6 @@ export default function TextSpeaker() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [audioRef, autopause]);
if (loading) return <div>...</div>;
if (!voicesData) return <div></div>;
const speak = async () => {
if (processing) return;
setProcessing(true);
@@ -103,7 +87,7 @@ export default function TextSpeaker() {
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.';
objurlRef.current = await getTTSAudioUrl(

View File

@@ -7,15 +7,11 @@ import { useAudioPlayer } from "@/hooks/useAudioPlayer";
import IMAGES from "@/config/images";
import { getTTSAudioUrl } from "@/utils";
import { Navbar } from "@/components/Navbar";
import { VOICES } from "@/config/locales";
export default function Translator() {
const [ipaEnabled, setIPAEnabled] = useState(true);
const [voicesData, setVoicesData] = useState<{
locale: string,
short_name: string
}[] | null>(null);
const [loading, setLoading] = useState(true);
const [targetLang, setTargetLang] = useState('Italian');
const [targetLang, setTargetLang] = useState('Chinese');
const [sourceText, setSourceText] = useState('');
const [targetText, setTargetText] = useState('');
@@ -23,21 +19,10 @@ export default function Translator() {
const [targetIPA, setTargetIPA] = useState('');
const [sourceLocale, setSourceLocale] = useState<string | null>(null);
const [targetLocale, setTargetLocale] = useState<string | null>(null);
const [translating, setTranslating] = useState(false);
const { playAudio } = useAudioPlayer();
useEffect(() => {
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 tl = ['Chinese', 'English', 'Italian'];
const inputLanguage = () => {
const lang = prompt('Input a language.')?.trim();
@@ -136,7 +121,7 @@ export default function Translator() {
const info = await res.json();
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) {
return;
}
@@ -150,7 +135,7 @@ export default function Translator() {
return;
}
} else {
const voice = voicesData.find(v => v.locale.startsWith(sourceLocale!));
const voice = VOICES.find(v => v.locale.startsWith(sourceLocale!));
if (!voice) {
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;
const url = await getTTSAudioUrl(targetText, voice.short_name);
@@ -227,9 +212,9 @@ export default function Translator() {
</div>
<div className="option2 w-full flex gap-1 items-center flex-wrap">
<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('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>
</div>
</div>

File diff suppressed because it is too large Load Diff