补全翻译
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-11-16 12:44:52 +08:00
parent 30fc4ed64d
commit 7c5fc40209
31 changed files with 279 additions and 54 deletions

View File

@@ -5,24 +5,26 @@ import { folder } from "../../../../generated/prisma/client";
import { Folder } from "lucide-react";
import { useRouter } from "next/navigation";
import { Center } from "@/components/Center";
import { useTranslations } from "next-intl";
interface FolderSelectorProps {
folders: (folder & { total_pairs: number })[];
}
const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
const t = useTranslations("memorize/folder-selector");
const router = useRouter();
return (
<Center>
<Container className="p-6 gap-4 flex flex-col">
{(folders.length === 0 && (
<h1 className="text-2xl text-gray-900 font-light">
No folders found.
{t("noFolders")}
</h1>
)) || (
<>
<h1 className="text-2xl text-gray-900 font-light">
Select a folder:
{t("selectFolder")}
</h1>
<div className="text-gray-900 border border-gray-200 rounded-2xl max-h-96 overflow-y-auto">
{folders.map((folder) => (
@@ -36,9 +38,12 @@ const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
<Folder />
<div className="flex-1 flex gap-2">
<span className="group-hover:text-blue-500">
{folder.id}. {folder.name}
{t("folderInfo", {
id: folder.id,
name: folder.name,
count: folder.total_pairs,
})}
</span>
<span>({folder.total_pairs})</span>
</div>
</div>
))}

View File

@@ -8,12 +8,14 @@ import LightButton from "@/components/buttons/LightButton";
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
import { getTTSAudioUrl } from "@/lib/tts";
import { VOICES } from "@/config/locales";
import { useTranslations } from "next-intl";
interface MemorizeProps {
textPairs: text_pair[];
}
const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
const t = useTranslations("memorize.memorize");
const [reverse, setReverse] = useState(false);
const [dictation, setDictation] = useState(false);
const [index, setIndex] = useState(0);
@@ -27,7 +29,7 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
<>
<div className="h-36 flex flex-col gap-2 justify-start items-center font-serif text-3xl">
<div className="text-sm text-gray-500">
{index + 1}/{textPairs.length}
{t("progress", { current: index + 1, total: textPairs.length })}
</div>
{dictation ? (
show === "question" ? (
@@ -86,7 +88,7 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
setShow(show === "question" ? "answer" : "question");
}}
>
{show === "question" ? "Show Answer" : "Next"}
{show === "question" ? t("showAnswer") : t("next")}
</LightButton>
<LightButton
onClick={() => {
@@ -94,7 +96,7 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
}}
selected={reverse}
>
Reverse
{t("reverse")}
</LightButton>
<LightButton
onClick={() => {
@@ -102,11 +104,11 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
}}
selected={dictation}
>
Dictation
{t("dictation")}
</LightButton>
</div>
</>
)) || <p>No text pairs available</p>}
)) || <p>{t("noTextPairs")}</p>}
</Container>
</Center>
);

View File

@@ -2,6 +2,7 @@
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import {
getFoldersWithTotalPairsByOwner,
getOwnerByFolderId,
@@ -18,9 +19,14 @@ export default async function MemorizePage({
}) {
const session = await getServerSession();
const username = session?.user?.name;
const t = await getTranslations("memorize.page");
const t = (await searchParams).folder_id;
const folder_id = t ? (isNonNegativeInteger(t) ? parseInt(t) : null) : null;
const tParam = (await searchParams).folder_id;
const folder_id = tParam
? isNonNegativeInteger(tParam)
? parseInt(tParam)
: null
: null;
if (!username)
redirect(
@@ -37,7 +43,7 @@ export default async function MemorizePage({
const owner = await getOwnerByFolderId(folder_id);
if (owner !== username) {
return <p>访</p>;
return <p>{t("unauthorized")}</p>;
}
return <Memorize textPairs={await getTextPairsByFolderId(folder_id)} />;

View File

@@ -9,6 +9,7 @@ import { getFoldersByOwner } from "@/lib/services/folderService";
import { Folder } from "lucide-react";
import { createTextPair } from "@/lib/services/textPairService";
import { toast } from "sonner";
import { useTranslations } from "next-intl";
interface AddToFolderProps {
item: z.infer<typeof TranslationHistorySchema>;
@@ -18,6 +19,7 @@ interface AddToFolderProps {
const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
const session = useSession();
const [folders, setFolders] = useState<folder[]>([]);
const t = useTranslations("translator.add-to-folder");
useEffect(() => {
const username = session.data!.user!.name as string;
@@ -28,7 +30,7 @@ const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
return (
<div className="fixed left-0 top-0 z-50 w-screen h-screen bg-black/50 flex justify-center items-center">
<Container className="p-6">
<div>You are not authenticated</div>;
<div>{t("notAuthenticated")}</div>
</Container>
</div>
);
@@ -36,7 +38,7 @@ const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
return (
<div className="fixed left-0 top-0 z-50 w-screen h-screen bg-black/50 flex justify-center items-center">
<Container className="p-6">
<h1>Choose a Folder to Add to</h1>
<h1>{t("chooseFolder")}</h1>
<div className="border border-gray-200 rounded-2xl">
{(folders.length > 0 &&
folders.map((folder) => (
@@ -56,20 +58,20 @@ const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
},
})
.then(() => {
toast.success("Text pair added to folder");
toast.success(t("success"));
setShow(false);
})
.catch(() => {
toast.error("Failed to add text pair to folder");
toast.error(t("error"));
});
}}
>
<Folder />
{folder.id}. {folder.name}
{t("folderInfo", { id: folder.id, name: folder.name })}
</button>
))) || <div>No folders found</div>}
))) || <div>{t("noFolders")}</div>}
</div>
<LightButton onClick={() => setShow(false)}>Close</LightButton>
<LightButton onClick={() => setShow(false)}>{t("close")}</LightButton>
</Container>
</div>
);

View File

@@ -238,7 +238,7 @@ export default function TranslatorPage() {
<LightButton
selected={!["chinese", "english", "italian"].includes(lang)}
onClick={() => {
const newLang = prompt("Enter language");
const newLang = prompt(t("enterLanguage"));
if (newLang) {
setLang(newLang);
}
@@ -261,7 +261,7 @@ export default function TranslatorPage() {
</div>
{history.length > 0 && (
<div className="m-6 flex flex-col items-center">
<h1 className="text-2xl font-light">History</h1>
<h1 className="text-2xl font-light">{t("history")}</h1>
<div className="border border-gray-200 rounded-2xl m-4">
{history.map((item, index) => (
<div key={index}>