From cbb5d25e548274d0ddf348566692b31691de5df4 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Mon, 29 Dec 2025 11:42:58 +0800 Subject: [PATCH] ... --- messages/en-US.json | 51 +---------------- messages/zh-CN.json | 57 ++----------------- .../srt-player/utils/subtitleParser.ts | 3 +- src/app/(features)/text-speaker/page.tsx | 10 ++-- src/app/(features)/translator/page.tsx | 3 +- src/app/folders/FoldersClient.tsx | 5 +- .../folders/[folder_id]/AddTextPairModal.tsx | 49 +--------------- src/app/folders/[folder_id]/InFolder.tsx | 5 +- .../[folder_id]/UpdateTextPairModal.tsx | 29 ++++------ src/components/ui/LocaleSelector.tsx | 48 ++++++++++++++++ src/lib/browser/localStorageOperators.ts | 5 +- src/lib/logger.ts | 29 ++++++++++ 12 files changed, 111 insertions(+), 183 deletions(-) create mode 100644 src/components/ui/LocaleSelector.tsx create mode 100644 src/lib/logger.ts 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)/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/page.tsx b/src/app/(features)/text-speaker/page.tsx index 7bc6529..5a6dc46 100644 --- a/src/app/(features)/text-speaker/page.tsx +++ b/src/app/(features)/text-speaker/page.tsx @@ -17,6 +17,7 @@ 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() { @@ -75,7 +76,7 @@ export default function TextSpeakerPage() { setIPA(data.ipa); }) .catch((e) => { - console.error(e); + logger.error("生成 IPA 失败", e); setIPA(""); }); } @@ -96,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; @@ -123,8 +123,7 @@ export default function TextSpeakerPage() { load(objurlRef.current); play(); } catch (e) { - console.error(e); - + logger.error("播放音频失败", e); setPause(true); setLocale(null); @@ -181,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; @@ -218,7 +216,7 @@ export default function TextSpeakerPage() { } setIntoLocalStorage(save); } catch (e) { - console.error(e); + logger.error("保存到本地存储失败", e); setLocale(null); } finally { setSaving(false); diff --git a/src/app/(features)/translator/page.tsx b/src/app/(features)/translator/page.tsx index 4a0f501..998a069 100644 --- a/src/app/(features)/translator/page.tsx +++ b/src/app/(features)/translator/page.tsx @@ -8,6 +8,7 @@ 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/folders/FoldersClient.tsx b/src/app/folders/FoldersClient.tsx index 28c1b72..e1305a6 100644 --- a/src/app/folders/FoldersClient.tsx +++ b/src/app/folders/FoldersClient.tsx @@ -8,6 +8,7 @@ import { Trash2, } from "lucide-react"; import { useEffect, useState } from "react"; +import { logger } from "@/lib/logger"; import { useRouter } from "next/navigation"; import { Folder } from "../../../generated/prisma/browser"; import { @@ -101,7 +102,7 @@ export default function FoldersClient({ userId }: { userId: string }) { setLoading(false); }) .catch((error) => { - console.error(error); + logger.error("加载文件夹失败", error); toast.error("加载出错,请重试。"); }); }, [userId]); @@ -111,7 +112,7 @@ export default function FoldersClient({ userId }: { userId: string }) { const updatedFolders = await getFoldersWithTotalPairsByUserId(userId); setFolders(updatedFolders); } catch (error) { - console.error(error); + logger.error("更新文件夹失败", error); } }; diff --git a/src/app/folders/[folder_id]/AddTextPairModal.tsx b/src/app/folders/[folder_id]/AddTextPairModal.tsx index 1c0a39b..0b8e867 100644 --- a/src/app/folders/[folder_id]/AddTextPairModal.tsx +++ b/src/app/folders/[folder_id]/AddTextPairModal.tsx @@ -1,9 +1,9 @@ import { LightButton } from "@/components/ui/buttons"; import Input from "@/components/ui/Input"; +import { LocaleSelector } from "@/components/ui/LocaleSelector"; import { X } from "lucide-react"; import { useRef, useState } from "react"; import { useTranslations } from "next-intl"; -import { LOCALES } from "@/config/locales"; interface AddTextPairModalProps { isOpen: boolean; @@ -16,53 +16,6 @@ interface AddTextPairModalProps { ) => void; } -const COMMON_LOCALES = [ - { label: "中文", value: "zh-CN" }, - { label: "英文", value: "en-US" }, - { label: "意大利语", value: "it-IT" }, - { label: "日语", value: "ja-JP" }, - { label: "其他", value: "other" }, -]; - -interface LocaleSelectorProps { - value: string; - onChange: (val: string) => void; -} - -function LocaleSelector({ value, onChange }: LocaleSelectorProps) { - const isCommonLocale = COMMON_LOCALES.some((l) => l.value === value && l.value !== "other"); - const showFullList = value === "other" || !isCommonLocale; - - return ( -
- - {showFullList && ( - - )} -
- ); -} - export default function AddTextPairModal({ isOpen, onClose, diff --git a/src/app/folders/[folder_id]/InFolder.tsx b/src/app/folders/[folder_id]/InFolder.tsx index 2c1dc40..a421772 100644 --- a/src/app/folders/[folder_id]/InFolder.tsx +++ b/src/app/folders/[folder_id]/InFolder.tsx @@ -13,6 +13,7 @@ import TextPairCard from "./TextPairCard"; import { useTranslations } from "next-intl"; import PageLayout from "@/components/ui/PageLayout"; import { GreenButton } from "@/components/ui/buttons"; +import { logger } from "@/lib/logger"; import { IconButton } from "@/components/ui/buttons"; import CardList from "@/components/ui/CardList"; @@ -38,7 +39,7 @@ export default function InFolder({ folderId }: { folderId: number }) { const data = await getPairsByFolderId(folderId); setTextPairs(data as TextPair[]); } catch (error) { - console.error("Failed to fetch text pairs:", error); + logger.error("获取文本对失败", error); } finally { setLoading(false); } @@ -51,7 +52,7 @@ export default function InFolder({ folderId }: { folderId: number }) { const data = await getPairsByFolderId(folderId); setTextPairs(data as TextPair[]); } catch (error) { - console.error("Failed to fetch text pairs:", error); + logger.error("获取文本对失败", error); } }; diff --git a/src/app/folders/[folder_id]/UpdateTextPairModal.tsx b/src/app/folders/[folder_id]/UpdateTextPairModal.tsx index 9ee3a57..a002429 100644 --- a/src/app/folders/[folder_id]/UpdateTextPairModal.tsx +++ b/src/app/folders/[folder_id]/UpdateTextPairModal.tsx @@ -1,7 +1,8 @@ import { LightButton } from "@/components/ui/buttons"; import Input from "@/components/ui/Input"; +import { LocaleSelector } from "@/components/ui/LocaleSelector"; import { X } from "lucide-react"; -import { useRef } from "react"; +import { useRef, useState } from "react"; import { PairUpdateInput } from "../../../../generated/prisma/models"; import { TextPair } from "./InFolder"; import { useTranslations } from "next-intl"; @@ -22,23 +23,23 @@ export default function UpdateTextPairModal({ const t = useTranslations("folder_id"); const input1Ref = useRef(null); const input2Ref = useRef(null); - const input3Ref = useRef(null); - const input4Ref = useRef(null); + const [locale1, setLocale1] = useState(textPair.locale1); + const [locale2, setLocale2] = useState(textPair.locale2); + if (!isOpen) return null; const handleUpdate = () => { if ( !input1Ref.current?.value || !input2Ref.current?.value || - !input3Ref.current?.value || - !input4Ref.current?.value + !locale1 || + !locale2 ) return; const text1 = input1Ref.current.value; const text2 = input2Ref.current.value; - const locale1 = input3Ref.current.value; - const locale2 = input4Ref.current.value; + if ( typeof text1 === "string" && typeof text2 === "string" && @@ -50,8 +51,6 @@ export default function UpdateTextPairModal({ locale2.trim() !== "" ) { onUpdate(textPair.id, { text1, text2, locale1, locale2 }); - input1Ref.current.value = ""; - input2Ref.current.value = ""; } }; return ( @@ -90,19 +89,11 @@ export default function UpdateTextPairModal({
{t("locale1")} - +
{t("locale2")} - +
{t("update")} diff --git a/src/components/ui/LocaleSelector.tsx b/src/components/ui/LocaleSelector.tsx new file mode 100644 index 0000000..3b13af3 --- /dev/null +++ b/src/components/ui/LocaleSelector.tsx @@ -0,0 +1,48 @@ +import { LOCALES } from "@/config/locales"; + +const COMMON_LOCALES = [ + { label: "中文", value: "zh-CN" }, + { label: "英文", value: "en-US" }, + { label: "意大利语", value: "it-IT" }, + { label: "日语", value: "ja-JP" }, + { label: "其他", value: "other" }, +]; + +interface LocaleSelectorProps { + value: string; + onChange: (val: string) => void; +} + +export function LocaleSelector({ value, onChange }: LocaleSelectorProps) { + const isCommonLocale = COMMON_LOCALES.some((l) => l.value === value && l.value !== "other"); + const showFullList = value === "other" || !isCommonLocale; + + return ( +
+ + {showFullList && ( + + )} +
+ ); +} diff --git a/src/lib/browser/localStorageOperators.ts b/src/lib/browser/localStorageOperators.ts index f0ece58..b85af55 100644 --- a/src/lib/browser/localStorageOperators.ts +++ b/src/lib/browser/localStorageOperators.ts @@ -6,6 +6,7 @@ import { } from "@/lib/interfaces"; import z from "zod"; import { shallowEqual } from "../utils"; +import { logger } from "@/lib/logger"; export const getLocalStorageOperator = ( key: string, @@ -24,14 +25,14 @@ export const getLocalStorageOperator = ( if (result.success) { return result.data; } else { - console.error( + logger.error( "Invalid data structure in localStorage:", result.error, ); return [] as z.infer; } } catch (e) { - console.error(`Failed to parse ${key} data:`, e); + logger.error(`Failed to parse ${key} data:`, e); return [] as z.infer; } }, diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 0000000..70b6dfc --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,29 @@ +/** + * 统一的日志工具 + * 在生产环境中可以通过环境变量控制日志级别 + */ + +type LogLevel = 'info' | 'warn' | 'error'; + +const isDevelopment = process.env.NODE_ENV === 'development'; + +export const logger = { + error: (message: string, error?: unknown) => { + if (isDevelopment) { + console.error(message, error); + } + // 在生产环境中,这里可以发送到错误追踪服务(如 Sentry) + }, + + warn: (message: string, data?: unknown) => { + if (isDevelopment) { + console.warn(message, data); + } + }, + + info: (message: string, data?: unknown) => { + if (isDevelopment) { + console.info(message, data); + } + }, +};