diff --git a/messages/en-US.json b/messages/en-US.json
index a92aeb7..66fc7a5 100644
--- a/messages/en-US.json
+++ b/messages/en-US.json
@@ -24,11 +24,7 @@
"noFoldersYet": "No folders yet",
"folderInfo": "{id}. {name} ({totalPairs})",
"enterFolderName": "Enter folder name:",
- "confirmDelete": "Type \"{name}\" to delete:",
- "createFolderSuccess": "Folder created successfully",
- "deleteFolderSuccess": "Folder deleted successfully",
- "createFolderError": "Failed to create folder",
- "deleteFolderError": "Failed to delete folder"
+ "confirmDelete": "Type \"{name}\" to delete:"
},
"folder_id": {
"unauthorized": "You are not the owner of this folder",
@@ -82,10 +78,6 @@
"description": "Under development, stay tuned"
}
},
- "login": {
- "loading": "Loading...",
- "githubLogin": "GitHub Login"
- },
"auth": {
"title": "Authentication",
"signIn": "Sign In",
@@ -93,18 +85,11 @@
"email": "Email",
"password": "Password",
"confirmPassword": "Confirm Password",
- "name": "Name",
- "signInButton": "Sign In",
- "signUpButton": "Sign Up",
"noAccount": "Don't have an account?",
"hasAccount": "Already have an account?",
- "signInWithGitHub": "Sign In with GitHub",
- "signUpWithGitHub": "Sign Up with GitHub",
"invalidEmail": "Please enter a valid email address",
"passwordTooShort": "Password must be at least 8 characters",
"passwordsNotMatch": "Passwords do not match",
- "signInFailed": "Sign in failed, please check your email and password",
- "signUpFailed": "Sign up failed, please try again later",
"nameRequired": "Please enter your name",
"emailRequired": "Please enter your email",
"passwordRequired": "Please enter your password",
@@ -151,42 +136,10 @@
"next": "Next",
"restart": "Restart",
"autoPause": "Auto Pause ({enabled})",
- "playbackSpeed": "Playback Speed",
- "subtitleSettings": "Subtitle Settings",
- "fontSize": "Font Size",
- "backgroundColor": "Background Color",
- "textColor": "Text Color",
- "fontFamily": "Font Family",
- "opacity": "Opacity",
- "position": "Position",
- "top": "Top",
- "center": "Center",
- "bottom": "Bottom",
- "keyboardShortcuts": "Keyboard Shortcuts",
- "uploadVideoAndSubtitle": "Please upload video and subtitle files",
- "uploadVideoFile": "Please upload video file",
- "uploadSubtitleFile": "Please upload subtitle file",
- "processingSubtitle": "Processing subtitle file...",
- "needBothFiles": "Both video and subtitle files are required to start learning",
- "videoFile": "Video File",
- "subtitleFile": "Subtitle File",
- "uploaded": "Uploaded",
- "notUploaded": "Not Uploaded",
- "upload": "Upload",
- "autoPauseStatus": "Auto Pause: {enabled}",
"on": "On",
"off": "Off",
"videoUploadFailed": "Video upload failed",
- "subtitleUploadFailed": "Subtitle upload failed",
- "subtitleLoadSuccess": "Subtitle file loaded successfully",
- "subtitleLoadFailed": "Subtitle file loading failed",
- "shortcuts": {
- "playPause": "Play/Pause",
- "next": "Next",
- "previous": "Previous",
- "restart": "Restart",
- "autoPause": "Toggle Auto Pause"
- }
+ "subtitleUploadFailed": "Subtitle upload failed"
},
"text_speaker": {
"generateIPA": "Generate IPA",
diff --git a/messages/zh-CN.json b/messages/zh-CN.json
index 95bf56c..22cdc7d 100644
--- a/messages/zh-CN.json
+++ b/messages/zh-CN.json
@@ -24,11 +24,7 @@
"noFoldersYet": "还没有文件夹",
"folderInfo": "{id}. {name} ({totalPairs})",
"enterFolderName": "输入文件夹名称:",
- "confirmDelete": "输入 \"{name}\" 以删除:",
- "createFolderSuccess": "文件夹创建成功",
- "deleteFolderSuccess": "文件夹删除成功",
- "createFolderError": "创建文件夹失败",
- "deleteFolderError": "删除文件夹失败"
+ "confirmDelete": "输入 \"{name}\" 以删除:"
},
"folder_id": {
"unauthorized": "您不是此文件夹的所有者",
@@ -82,10 +78,6 @@
"description": "开发中,敬请期待"
}
},
- "login": {
- "loading": "加载中...",
- "githubLogin": "GitHub登录"
- },
"auth": {
"title": "登录",
"signIn": "登录",
@@ -93,28 +85,18 @@
"email": "邮箱",
"password": "密码",
"confirmPassword": "确认密码",
- "name": "用户名",
- "signInButton": "登录",
- "signUpButton": "注册",
"noAccount": "还没有账户?",
"hasAccount": "已有账户?",
- "signInWithGitHub": "使用GitHub登录",
- "signUpWithGitHub": "使用GitHub注册",
"invalidEmail": "请输入有效的邮箱地址",
"passwordTooShort": "密码至少需要8个字符",
"passwordsNotMatch": "两次输入的密码不匹配",
- "signInFailed": "登录失败,请检查您的邮箱和密码",
- "signUpFailed": "注册失败,请稍后再试",
"nameRequired": "请输入用户名",
"emailRequired": "请输入邮箱",
"passwordRequired": "请输入密码",
- "confirmPasswordRequired": "请确认密码"
+ "confirmPasswordRequired": "请确认密码",
+ "loading": "加载中..."
},
"memorize": {
- "choose": {
- "back": "返回",
- "choose": "选择"
- },
"folder_selector": {
"selectFolder": "选择文件夹",
"noFolders": "未找到文件夹",
@@ -155,41 +137,10 @@
"next": "下句",
"restart": "句首",
"autoPause": "自动暂停({enabled})",
- "playbackSpeed": "播放速度",
- "subtitleSettings": "字幕设置",
- "fontSize": "字体大小",
- "backgroundColor": "背景颜色",
- "textColor": "文字颜色",
- "fontFamily": "字体",
- "opacity": "透明度",
- "position": "位置",
- "top": "顶部",
- "center": "居中",
- "bottom": "底部",
- "keyboardShortcuts": "键盘快捷键",
- "uploadVideoAndSubtitle": "请上传视频和字幕文件",
- "uploadVideoFile": "请上传视频文件",
- "uploadSubtitleFile": "请上传字幕文件",
- "processingSubtitle": "字幕文件正在处理中...",
- "needBothFiles": "需要同时上传视频和字幕文件才能开始学习",
- "videoFile": "视频文件",
- "subtitleFile": "字幕文件",
- "uploaded": "已上传",
- "notUploaded": "未上传",
- "autoPauseStatus": "自动暂停: {enabled}",
"on": "开",
"off": "关",
"videoUploadFailed": "视频上传失败",
- "subtitleUploadFailed": "字幕上传失败",
- "subtitleLoadSuccess": "字幕文件加载成功",
- "subtitleLoadFailed": "字幕文件加载失败",
- "shortcuts": {
- "playPause": "播放/暂停",
- "next": "下一句",
- "previous": "上一句",
- "restart": "句首",
- "autoPause": "切换自动暂停"
- }
+ "subtitleUploadFailed": "字幕上传失败"
},
"text_speaker": {
"generateIPA": "生成IPA",
diff --git a/src/app/(features)/alphabet/AlphabetCard.tsx b/src/app/(features)/alphabet/AlphabetCard.tsx
index 637a120..38b4036 100644
--- a/src/app/(features)/alphabet/AlphabetCard.tsx
+++ b/src/app/(features)/alphabet/AlphabetCard.tsx
@@ -3,7 +3,7 @@
import { useState, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
import { Letter, SupportedAlphabets } from "@/lib/interfaces";
-import IconClick from "@/components/ui/buttons/IconClick";
+import { IconClick } from "@/components/ui/buttons";
import IMAGES from "@/config/images";
import { ChevronLeft, ChevronRight } from "lucide-react";
@@ -99,7 +99,7 @@ export default function AlphabetCard({ alphabet, alphabetType, onBack }: Alphabe
return (
- {/* 返回按钮 */}
+ {/* 右上角返回按钮 */}
- {/* 主卡片 */}
+ {/* 白色主卡片容器 */}
- {/* 进度指示器 */}
+ {/* 顶部进度指示器和显示选项按钮 */}
+ {/* 当前字母进度 */}
{currentIndex + 1} / {alphabet.length}
+ {/* 显示选项切换按钮组 */}
+ {/* IPA 音标显示切换 */}
+ {/* 罗马音显示切换(仅日语显示) */}
{hasRomanization && (
)}
+ {/* 随机模式切换 */}
- {/* 字母显示区域 */}
+ {/* 字母主要内容显示区域 */}
+ {/* 字母本身(可隐藏) */}
{showLetter ? (
{currentLetter.letter}
@@ -174,13 +180,15 @@ export default function AlphabetCard({ alphabet, alphabetType, onBack }: Alphabe
?
)}
-
+
+ {/* IPA 音标显示 */}
{showIPA && (
{currentLetter.letter_sound_ipa}
)}
-
+
+ {/* 罗马音显示(日语) */}
{showRoman && hasRomanization && currentLetter.roman_letter && (
{currentLetter.roman_letter}
@@ -188,8 +196,9 @@ export default function AlphabetCard({ alphabet, alphabetType, onBack }: Alphabe
)}
- {/* 导航控制 */}
+ {/* 底部导航控制区域 */}
+ {/* 上一个按钮 */}
+ {/* 中间区域:随机按钮或进度条 */}
{isRandomMode ? (
+ // 随机模式:显示随机切换按钮
- {/* 操作提示 */}
+ {/* 底部操作提示文字 */}
{isRandomMode
@@ -246,7 +260,7 @@ export default function AlphabetCard({ alphabet, alphabetType, onBack }: Alphabe
- {/* 触摸事件处理 */}
+ {/* 全屏触摸事件监听层(用于滑动切换) */}
+ {/* 页面标题 */}
{t("chooseCharacters")}
+ {/* 副标题说明 */}
选择一种语言的字母表开始学习
-
+
+ {/* 语言选择按钮网格 */}
+ {/* 日语假名选项 */}
setChosenAlphabet("japanese")}
className="p-6 text-lg font-medium hover:scale-105 transition-transform"
@@ -67,7 +71,8 @@ export default function Alphabet() {
{t("japanese")}
-
+
+ {/* 英语字母选项 */}
setChosenAlphabet("english")}
className="p-6 text-lg font-medium hover:scale-105 transition-transform"
@@ -77,7 +82,8 @@ export default function Alphabet() {
{t("english")}
-
+
+ {/* 维吾尔语字母选项 */}
setChosenAlphabet("uyghur")}
className="p-6 text-lg font-medium hover:scale-105 transition-transform"
@@ -87,7 +93,8 @@ export default function Alphabet() {
{t("uyghur")}
-
+
+ {/* 世界语字母选项 */}
setChosenAlphabet("esperanto")}
className="p-6 text-lg font-medium hover:scale-105 transition-transform"
diff --git a/src/app/(features)/memorize/FolderSelector.tsx b/src/app/(features)/memorize/FolderSelector.tsx
index e519249..2a91d62 100644
--- a/src/app/(features)/memorize/FolderSelector.tsx
+++ b/src/app/(features)/memorize/FolderSelector.tsx
@@ -1,8 +1,6 @@
"use client";
-import Container from "@/components/ui/Container";
import { useRouter } from "next/navigation";
-import { Center } from "@/components/common/Center";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { Folder } from "../../../../generated/prisma/browser";
@@ -15,49 +13,83 @@ interface FolderSelectorProps {
const FolderSelector: React.FC = ({ folders }) => {
const t = useTranslations("memorize.folder_selector");
const router = useRouter();
+
return (
-
-
- {(folders.length === 0 && (
-
- {t("noFolders")}
-
- folders
-
-
- )) || (
- <>
-
- {t("selectFolder")}
-
-
- {folders
- .toSorted((a, b) => a.id - b.id)
- .map((folder) => (
-
- router.push(`/memorize?folder_id=${folder.id}`)
- }
- className="flex flex-row justify-center items-center group p-2 gap-2 hover:cursor-pointer hover:bg-gray-50"
- >
-
-
-
- {t("folderInfo", {
- id: folder.id,
- name: folder.name,
- count: folder.total,
- })}
-
-
-
- ))}
+
+
+
+ {folders.length === 0 ? (
+ // 空状态 - 显示提示和跳转按钮
+
+
+ {t("noFolders")}
+
+
+ Go to Folders
+
- >
- )}
-
-
+ ) : (
+ <>
+ {/* 页面标题 */}
+
+ {t("selectFolder")}
+
+ {/* 文件夹列表 */}
+
+ {folders
+ .toSorted((a, b) => a.id - b.id)
+ .map((folder) => (
+
+ router.push(`/memorize?folder_id=${folder.id}`)
+ }
+ className="flex flex-row items-center p-4 gap-3 hover:cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-b-0"
+ >
+ {/* 文件夹图标 */}
+
+
+
+ {/* 文件夹信息 */}
+
+
+ {folder.name}
+
+
+ {t("folderInfo", {
+ id: folder.id,
+ name: folder.name,
+ count: folder.total,
+ })}
+
+
+ {/* 右箭头 */}
+
+
+ ))}
+
+ >
+ )}
+
+
+
);
};
diff --git a/src/app/(features)/memorize/Memorize.tsx b/src/app/(features)/memorize/Memorize.tsx
index 3d112fc..4f13dbc 100644
--- a/src/app/(features)/memorize/Memorize.tsx
+++ b/src/app/(features)/memorize/Memorize.tsx
@@ -1,7 +1,6 @@
"use client";
import { useState } from "react";
-import LightButton from "@/components/ui/buttons/LightButton";
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
import { getTTSAudioUrl } from "@/lib/browser/tts";
import { VOICES } from "@/config/locales";
@@ -28,7 +27,13 @@ const Memorize: React.FC
= ({ textPairs }) => {
const { load, play } = useAudioPlayer();
if (textPairs.length === 0) {
- return {t("noTextPairs")}
;
+ return (
+
+ );
}
const rng = new SeededRandom(textPairs[0].folderId);
@@ -38,135 +43,160 @@ const Memorize: React.FC = ({ textPairs }) => {
const getTextPairs = () => disorder ? disorderedTextPairs : textPairs;
+ const handleIndexClick = () => {
+ const newIndex = prompt("Input a index number.")?.trim();
+ if (
+ newIndex &&
+ isNonNegativeInteger(newIndex) &&
+ parseInt(newIndex) <= textPairs.length &&
+ parseInt(newIndex) > 0
+ ) {
+ setIndex(parseInt(newIndex) - 1);
+ }
+ };
+
+ const handleNext = async () => {
+ if (show === "answer") {
+ const newIndex = (index + 1) % getTextPairs().length;
+ setIndex(newIndex);
+ if (dictation)
+ getTTSAudioUrl(
+ getTextPairs()[newIndex][reverse ? "text2" : "text1"],
+ VOICES.find(
+ (v) =>
+ v.locale ===
+ getTextPairs()[newIndex][
+ reverse ? "locale2" : "locale1"
+ ],
+ )!.short_name,
+ ).then((url) => {
+ load(url);
+ play();
+ });
+ }
+ setShow(show === "question" ? "answer" : "question");
+ };
+
+ const handlePrevious = () => {
+ setIndex(
+ (index - 1 + getTextPairs().length) % getTextPairs().length,
+ );
+ setShow("question");
+ };
+
+ const toggleReverse = () => setReverse(!reverse);
+ const toggleDictation = () => setDictation(!dictation);
+ const toggleDisorder = () => setDisorder(!disorder);
+
+ const createText = (text: string) => {
+ return (
+
+ {text}
+
+ );
+ };
+
+ const [text1, text2] = reverse
+ ? [getTextPairs()[index].text2, getTextPairs()[index].text1]
+ : [getTextPairs()[index].text1, getTextPairs()[index].text2];
+
return (
- <>
- {(getTextPairs().length > 0 && (
- <>
-
-
{
- const newIndex = prompt("Input a index number.")?.trim();
- if (
- newIndex &&
- isNonNegativeInteger(newIndex) &&
- parseInt(newIndex) <= textPairs.length &&
- parseInt(newIndex) > 0
- ) {
- setIndex(parseInt(newIndex) - 1);
- }
- }}
+
+
+
+ {/* 进度指示器 */}
+
+
- {index + 1}
- {"/" + getTextPairs().length}
-
-
- {(() => {
- const createText = (text: string) => {
+ {index + 1} / {getTextPairs().length}
+
+
+
+ {/* 文本显示区域 */}
+
+ {(() => {
+ if (dictation) {
+ if (show === "question") {
return (
-
- {text}
+
);
- };
-
- const [text1, text2] = reverse
- ? [getTextPairs()[index].text2, getTextPairs()[index].text1]
- : [getTextPairs()[index].text1, getTextPairs()[index].text2];
-
- if (dictation) {
- // dictation
- if (show === "question") {
- return createText("");
- } else {
- return (
- <>
- {createText(text1)}
- {createText(text2)}
- >
- );
- }
} else {
- // non-dictation
- if (show === "question") {
- return createText(text1);
- } else {
- return (
- <>
- {createText(text1)}
- {createText(text2)}
- >
- );
- }
+ return (
+
+ {createText(text1)}
+
+ {createText(text2)}
+
+ );
}
- })()}
-
+ } else {
+ if (show === "question") {
+ return createText(text1);
+ } else {
+ return (
+
+ {createText(text1)}
+
+ {createText(text2)}
+
+ );
+ }
+ }
+ })()}
+
+ {/* 底部按钮 */}
- {
- if (show === "answer") {
- const newIndex = (index + 1) % getTextPairs().length;
- setIndex(newIndex);
- if (dictation)
- getTTSAudioUrl(
- getTextPairs()[newIndex][reverse ? "text2" : "text1"],
- VOICES.find(
- (v) =>
- v.locale ===
- getTextPairs()[newIndex][
- reverse ? "locale2" : "locale1"
- ],
- )!.short_name,
- ).then((url) => {
- load(url);
- play();
- });
- }
- setShow(show === "question" ? "answer" : "question");
- }}
+
{show === "question" ? t("answer") : t("next")}
-
- {
- setIndex(
- (index - 1 + getTextPairs().length) % getTextPairs().length,
- );
- setShow("question");
- }}
+
+
{t("previous")}
-
- {
- setReverse(!reverse);
- }}
- selected={reverse}
+
+
{t("reverse")}
-
- {
- setDictation(!dictation);
- }}
- selected={dictation}
+
+
{t("dictation")}
-
- {
- setDisorder(!disorder);
- }}
- selected={disorder}
+
+
{t("disorder")}
-
+
- >
- )) ||
{t("noTextPairs")}
}
- >
+
+
+
);
};
diff --git a/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx b/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx
index 37e53e9..5c1e469 100644
--- a/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx
+++ b/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx
@@ -1,6 +1,6 @@
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
import SubtitleDisplay from "./SubtitleDisplay";
-import LightButton from "@/components/ui/buttons/LightButton";
+import { LightButton } from "@/components/ui/buttons";
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
import { useTranslations } from "next-intl";
diff --git a/src/app/(features)/srt-player/components/atoms/PlayButton.tsx b/src/app/(features)/srt-player/components/atoms/PlayButton.tsx
index d3d7332..c9bb4d3 100644
--- a/src/app/(features)/srt-player/components/atoms/PlayButton.tsx
+++ b/src/app/(features)/srt-player/components/atoms/PlayButton.tsx
@@ -2,7 +2,7 @@
import React from "react";
import { useTranslations } from "next-intl";
-import LightButton from "@/components/ui/buttons/LightButton";
+import { LightButton } from "@/components/ui/buttons";
import { PlayButtonProps } from "../../types/player";
export default function PlayButton({ isPlaying, onToggle, disabled, className }: PlayButtonProps) {
diff --git a/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx b/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx
index 0ef233e..36644f1 100644
--- a/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx
+++ b/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx
@@ -1,7 +1,7 @@
"use client";
import React from "react";
-import LightButton from "@/components/ui/buttons/LightButton";
+import { LightButton } from "@/components/ui/buttons";
import { SpeedControlProps } from "../../types/player";
import { getPlaybackRateOptions, getPlaybackRateLabel } from "../../utils/timeUtils";
diff --git a/src/app/(features)/srt-player/components/compounds/ControlBar.tsx b/src/app/(features)/srt-player/components/compounds/ControlBar.tsx
index 25e5312..91f946d 100644
--- a/src/app/(features)/srt-player/components/compounds/ControlBar.tsx
+++ b/src/app/(features)/srt-player/components/compounds/ControlBar.tsx
@@ -3,7 +3,7 @@
import React from "react";
import { useTranslations } from "next-intl";
import { ChevronLeft, ChevronRight, RotateCcw, Pause } from "lucide-react";
-import DarkButton from "@/components/ui/buttons/DarkButton";
+import { LightButton } from "@/components/ui/buttons";
import { ControlBarProps } from "../../types/controls";
import PlayButton from "../atoms/PlayButton";
import SpeedControl from "../atoms/SpeedControl";
@@ -31,32 +31,32 @@ export default function ControlBar({
disabled={disabled}
/>
-
{t("previous")}
-
+
-
{t("next")}
-
+
-
{t("restart")}
-
+
-
{t("autoPause", { enabled: autoPause ? t("on") : t("off") })}
-
+
);
}
\ No newline at end of file
diff --git a/src/app/(features)/srt-player/components/compounds/UploadZone.tsx b/src/app/(features)/srt-player/components/compounds/UploadZone.tsx
index 9286cd7..7f7d9b7 100644
--- a/src/app/(features)/srt-player/components/compounds/UploadZone.tsx
+++ b/src/app/(features)/srt-player/components/compounds/UploadZone.tsx
@@ -4,7 +4,7 @@ import React from "react";
import { useTranslations } from "next-intl";
import { toast } from "sonner";
import { Video, FileText } from "lucide-react";
-import DarkButton from "@/components/ui/buttons/DarkButton";
+import { LightButton } from "@/components/ui/buttons";
import { FileUploadProps } from "../../types/controls";
import { useFileUpload } from "../../hooks/useFileUpload";
@@ -26,21 +26,21 @@ export default function UploadZone({ onVideoUpload, onSubtitleUpload, className
return (
-
{t("uploadVideo")}
-
+
-
{t("uploadSubtitle")}
-
+
);
}
\ No newline at end of file
diff --git a/src/app/(features)/srt-player/page.tsx b/src/app/(features)/srt-player/page.tsx
index ba802ae..bc1add3 100644
--- a/src/app/(features)/srt-player/page.tsx
+++ b/src/app/(features)/srt-player/page.tsx
@@ -14,7 +14,7 @@ import SubtitleArea from "./components/compounds/SubtitleArea";
import ControlBar from "./components/compounds/ControlBar";
import UploadZone from "./components/compounds/UploadZone";
import SeekBar from "./components/atoms/SeekBar";
-import DarkButton from "@/components/ui/buttons/DarkButton";
+import { LightButton } from "@/components/ui/buttons";
export default function SrtPlayerPage() {
const t = useTranslations("home");
@@ -182,13 +182,13 @@ export default function SrtPlayerPage() {
-
{state.video.url ? srtT("uploaded") : srtT("upload")}
-
+
@@ -206,13 +206,13 @@ export default function SrtPlayerPage() {
-
{state.subtitle.url ? srtT("uploaded") : srtT("upload")}
-
+
diff --git a/src/app/(features)/srt-player/utils/subtitleParser.ts b/src/app/(features)/srt-player/utils/subtitleParser.ts
index e686185..6145e89 100644
--- a/src/app/(features)/srt-player/utils/subtitleParser.ts
+++ b/src/app/(features)/srt-player/utils/subtitleParser.ts
@@ -1,4 +1,5 @@
import { SubtitleEntry } from "../types/subtitle";
+import { logger } from "@/lib/logger";
export function parseSrt(data: string): SubtitleEntry[] {
const lines = data.split(/\r?\n/);
@@ -93,7 +94,7 @@ export async function loadSubtitle(url: string): Promise {
const data = await response.text();
return parseSrt(data);
} catch (error) {
- console.error('Failed to load subtitle:', error);
+ logger.error('加载字幕失败', error);
return [];
}
}
\ No newline at end of file
diff --git a/src/app/(features)/text-speaker/SaveList.tsx b/src/app/(features)/text-speaker/SaveList.tsx
index 25d81e0..e67d065 100644
--- a/src/app/(features)/text-speaker/SaveList.tsx
+++ b/src/app/(features)/text-speaker/SaveList.tsx
@@ -6,7 +6,7 @@ import {
TextSpeakerArraySchema,
TextSpeakerItemSchema,
} from "@/lib/interfaces";
-import IconClick from "@/components/ui/buttons/IconClick";
+import { IconClick } from "@/components/ui/buttons";
import IMAGES from "@/config/images";
import { useTranslations } from "next-intl";
import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators";
diff --git a/src/app/(features)/text-speaker/page.tsx b/src/app/(features)/text-speaker/page.tsx
index d6a9136..5a6dc46 100644
--- a/src/app/(features)/text-speaker/page.tsx
+++ b/src/app/(features)/text-speaker/page.tsx
@@ -1,7 +1,7 @@
"use client";
-import LightButton from "@/components/ui/buttons/LightButton";
-import IconClick from "@/components/ui/buttons/IconClick";
+import { LightButton } from "@/components/ui/buttons";
+import { IconClick } from "@/components/ui/buttons";
import IMAGES from "@/config/images";
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
import {
@@ -17,6 +17,8 @@ import { useTranslations } from "next-intl";
import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators";
import { getTTSAudioUrl } from "@/lib/browser/tts";
import { genIPA, genLocale } from "@/lib/server/translatorActions";
+import { logger } from "@/lib/logger";
+import PageLayout from "@/components/ui/PageLayout";
export default function TextSpeakerPage() {
const t = useTranslations("text_speaker");
@@ -74,7 +76,7 @@ export default function TextSpeakerPage() {
setIPA(data.ipa);
})
.catch((e) => {
- console.error(e);
+ logger.error("生成 IPA 失败", e);
setIPA("");
});
}
@@ -95,7 +97,6 @@ export default function TextSpeakerPage() {
try {
let theLocale = locale;
if (!theLocale) {
- console.log("downloading text info");
const tmp_locale = await genLocale(textRef.current.slice(0, 30));
setLocale(tmp_locale);
theLocale = tmp_locale;
@@ -122,8 +123,7 @@ export default function TextSpeakerPage() {
load(objurlRef.current);
play();
} catch (e) {
- console.error(e);
-
+ logger.error("播放音频失败", e);
setPause(true);
setLocale(null);
@@ -180,7 +180,6 @@ export default function TextSpeakerPage() {
try {
let theLocale = locale;
if (!theLocale) {
- console.log("downloading text info");
const tmp_locale = await genLocale(textRef.current.slice(0, 30));
setLocale(tmp_locale);
theLocale = tmp_locale;
@@ -217,7 +216,7 @@ export default function TextSpeakerPage() {
}
setIntoLocalStorage(save);
} catch (e) {
- console.error(e);
+ logger.error("保存到本地存储失败", e);
setLocale(null);
} finally {
setSaving(false);
@@ -225,24 +224,30 @@ export default function TextSpeakerPage() {
};
return (
- <>
+
+ {/* 文本输入区域 */}
+ {/* 文本输入框 */}
+ {/* IPA 显示区域 */}
{(ipa.length !== 0 && (
-
+
{ipa}
)) ||
}
-
+
+ {/* 控制按钮区域 */}
+
+ {/* 速度调节面板 */}
{showSpeedAdjust && (
-
+
)}
+ {/* 播放/暂停按钮 */}
+ {/* 自动暂停按钮 */}
{
@@ -299,6 +306,7 @@ export default function TextSpeakerPage() {
src={autopause ? IMAGES.autoplay : IMAGES.autopause}
alt="autoplayorpause"
>
+ {/* 速度调节按钮 */}
setShowSpeedAdjust(!showSpeedAdjust)}
@@ -306,6 +314,7 @@ export default function TextSpeakerPage() {
alt="speed"
className={`${showSpeedAdjust ? "bg-gray-200" : ""}`}
>
+ {/* 保存按钮 */}
+ {/* 功能开关按钮 */}
-
- >
+ {/* 保存列表 */}
+ {showSaveList && (
+
+
+
+ )}
+
);
}
diff --git a/src/app/(features)/translator/AddToFolder.tsx b/src/app/(features)/translator/AddToFolder.tsx
index bd47cfc..382f263 100644
--- a/src/app/(features)/translator/AddToFolder.tsx
+++ b/src/app/(features)/translator/AddToFolder.tsx
@@ -1,6 +1,6 @@
"use client";
-import LightButton from "@/components/ui/buttons/LightButton";
+import { LightButton } from "@/components/ui/buttons";
import Container from "@/components/ui/Container";
import { TranslationHistorySchema } from "@/lib/interfaces";
import { Dispatch, useEffect, useState } from "react";
diff --git a/src/app/(features)/translator/FolderSelector.tsx b/src/app/(features)/translator/FolderSelector.tsx
index b7a6388..5bb696c 100644
--- a/src/app/(features)/translator/FolderSelector.tsx
+++ b/src/app/(features)/translator/FolderSelector.tsx
@@ -2,7 +2,7 @@ import Container from "@/components/ui/Container";
import { useEffect, useState } from "react";
import { Folder } from "../../../../generated/prisma/browser";
import { getFoldersByUserId } from "@/lib/server/services/folderService";
-import LightButton from "@/components/ui/buttons/LightButton";
+import { LightButton } from "@/components/ui/buttons";
import { Folder as Fd } from "lucide-react";
interface FolderSelectorProps {
diff --git a/src/app/(features)/translator/page.tsx b/src/app/(features)/translator/page.tsx
index b257ecb..998a069 100644
--- a/src/app/(features)/translator/page.tsx
+++ b/src/app/(features)/translator/page.tsx
@@ -1,13 +1,14 @@
"use client";
-import LightButton from "@/components/ui/buttons/LightButton";
-import IconClick from "@/components/ui/buttons/IconClick";
+import { LightButton } from "@/components/ui/buttons";
+import { IconClick } from "@/components/ui/buttons";
import IMAGES from "@/config/images";
import { VOICES } from "@/config/locales";
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
import { TranslationHistorySchema } from "@/lib/interfaces";
import { tlsoPush, tlso } from "@/lib/browser/localStorageOperators";
import { getTTSAudioUrl } from "@/lib/browser/tts";
+import { logger } from "@/lib/logger";
import { Plus, Trash } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react";
@@ -67,7 +68,7 @@ export default function TranslatorPage() {
lastTTS.current.url = url;
} catch (error) {
toast.error("Failed to generate audio");
- console.error(error);
+ logger.error("生成音频失败", error);
}
}
await play();
diff --git a/src/app/auth/AuthForm.tsx b/src/app/auth/AuthForm.tsx
index 3b61829..0e7f2a7 100644
--- a/src/app/auth/AuthForm.tsx
+++ b/src/app/auth/AuthForm.tsx
@@ -5,8 +5,7 @@ import { useTranslations } from "next-intl";
import { signInAction, signUpAction, SignUpState } from "@/lib/actions/auth";
import Container from "@/components/ui/Container";
import Input from "@/components/ui/Input";
-import LightButton from "@/components/ui/buttons/LightButton";
-import DarkButton from "@/components/ui/buttons/DarkButton";
+import { LightButton } from "@/components/ui/buttons";
import { authClient } from "@/lib/auth-client";
interface AuthFormProps {
@@ -18,7 +17,7 @@ export default function AuthForm({ redirectTo }: AuthFormProps) {
const [mode, setMode] = useState<'signin' | 'signup'>('signin');
const [clearSignIn, setClearSignIn] = useState(false);
const [clearSignUp, setClearSignUp] = useState(false);
-
+
const [signInState, signInActionForm, isSignInPending] = useActionState(
async (prevState: SignUpState | undefined, formData: FormData) => {
if (clearSignIn) {
@@ -44,7 +43,7 @@ export default function AuthForm({ redirectTo }: AuthFormProps) {
const validateForm = (formData: FormData): boolean => {
const newErrors: Record
= {};
-
+
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const name = formData.get("name") as string;
@@ -66,7 +65,7 @@ export default function AuthForm({ redirectTo }: AuthFormProps) {
if (!name) {
newErrors.name = t("nameRequired");
}
-
+
if (!confirmPassword) {
newErrors.confirmPassword = t("confirmPasswordRequired");
} else if (password !== confirmPassword) {
@@ -81,17 +80,17 @@ export default function AuthForm({ redirectTo }: AuthFormProps) {
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
-
+
// 基本客户端验证
if (!validateForm(formData)) {
return;
}
-
+
// 添加 redirectTo 到 formData
if (redirectTo) {
formData.append("redirectTo", redirectTo);
}
-
+
// 使用 startTransition 包装 action 调用
startTransition(() => {
// 根据模式调用相应的 action
@@ -115,17 +114,21 @@ export default function AuthForm({ redirectTo }: AuthFormProps) {
return (
+ {/* 页面标题 */}
{t(mode === 'signin' ? 'signIn' : 'signUp')}
+ {/* 服务器端错误提示 */}
{currentError?.message && (
{currentError.message}
)}
+ {/* 登录/注册表单 */}