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

This commit is contained in:
2025-11-16 14:57:05 +08:00
parent f339e5e2f0
commit b1a3add1d9
50 changed files with 391 additions and 428 deletions

181
messages/en-US.json Normal file
View File

@@ -0,0 +1,181 @@
{
"alphabet": {
"chooseCharacters": "Please select the characters you want to learn",
"japanese": "Japanese Kana",
"english": "English Alphabet",
"uyghur": "Uyghur Alphabet",
"esperanto": "Esperanto Alphabet",
"loading": "Loading...",
"loadFailed": "Loading failed, please try again",
"hideLetter": "Hide Letter",
"showLetter": "Show Letter",
"hideIPA": "Hide IPA",
"showIPA": "Show IPA"
},
"folders": {
"title": "Folders",
"subtitle": "Manage your collections",
"newFolder": "New Folder",
"creating": "Creating...",
"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"
},
"folder_id": {
"unauthorized": "You are not the owner of this folder",
"back": "Back",
"textPairs": "Text Pairs",
"itemsCount": "{count} items",
"memorize": "Memorize",
"loadingTextPairs": "Loading text pairs...",
"noTextPairs": "No text pairs in this folder",
"addNewTextPair": "Add New Text Pair",
"add": "Add",
"updateTextPair": "Update Text Pair",
"update": "Update",
"text1": "Text 1",
"text2": "Text 2",
"locale1": "Locale 1",
"locale2": "Locale 2",
"edit": "Edit",
"delete": "Delete"
},
"home": {
"title": "Learn Languages",
"description": "Here is a very useful website to help you learn almost every language in the world, including constructed ones.",
"explore": "Explore",
"fortune": {
"quote": "Stay hungry, stay foolish.",
"author": "— Steve Jobs"
},
"translator": {
"name": "Translator",
"description": "Translate to any language and annotate with International Phonetic Alphabet (IPA)"
},
"textSpeaker": {
"name": "Text Speaker",
"description": "Recognize and read text aloud, supports loop playback and speed adjustment"
},
"srtPlayer": {
"name": "SRT Video Player",
"description": "Play videos sentence by sentence based on SRT subtitle files to mimic native speaker pronunciation"
},
"alphabet": {
"name": "Memorize Alphabet",
"description": "Start learning a new language from the alphabet"
},
"memorize": {
"name": "Memorize Words",
"description": "Language A to Language B, Language B to Language A, supports dictation"
},
"moreFeatures": {
"name": "More Features",
"description": "Under development, stay tuned"
}
},
"login": {
"loading": "Loading...",
"githubLogin": "GitHub Login"
},
"memorize": {
"choose": {
"back": "Back",
"choose": "Choose"
},
"edit": {
"back": "Back",
"save": "Save Word Pairs",
"locale1": "Locale 1",
"locale2": "Locale 2"
},
"folder_selector": {
"selectFolder": "Select a folder",
"noFolders": "No folders found",
"folderInfo": "{id}. {name} ({count})"
},
"main": {
"title": "Memorize",
"locale1": "Your selected locale 1 is {locale}",
"locale2": "Your selected locale 2 is {locale}",
"total": "There are {total} word pairs in total",
"start": "Start",
"import": "Import",
"export": "Export",
"edit": "Edit"
},
"memorize": {
"showAnswer": "Show Answer",
"next": "Next",
"reverse": "Reverse",
"dictation": "Dictation",
"noTextPairs": "No text pairs available",
"progress": "{current}/{total}"
},
"page": {
"unauthorized": "You are not authorized to access this folder"
},
"start": {
"show": "Show",
"reverse": "Reverse",
"dictation": "Dictation",
"back": "Back",
"next": "Next"
}
},
"navbar": {
"title": "learn-languages",
"about": "About",
"sourceCode": "GitHub",
"login": "Login",
"profile": "Profile",
"folders": "Folders"
},
"profile": {
"myProfile": "My Profile",
"email": "Email: {email}",
"logout": "Logout"
},
"srt_player": {
"uploadVideo": "Upload Video",
"uploadSubtitle": "Upload Subtitle",
"pause": "Pause",
"play": "Play",
"previous": "Previous",
"next": "Next",
"restart": "Restart",
"autoPause": "Auto Pause ({enabled})"
},
"text_speaker": {
"generateIPA": "Generate IPA",
"viewSavedItems": "View Saved Items",
"confirmDeleteAll": "Are you sure you want to delete everything? (Y/N)"
},
"translator": {
"detectLanguage": "detect language",
"generateIPA": "generate ipa",
"translateInto": "translate into",
"chinese": "Chinese",
"english": "English",
"italian": "Italian",
"other": "Other",
"translating": "translating...",
"translate": "translate",
"inputLanguage": "Input a language.",
"history": "History",
"enterLanguage": "Enter language",
"add_to_folder": {
"notAuthenticated": "You are not authenticated",
"chooseFolder": "Choose a Folder to Add to",
"noFolders": "No folders found",
"folderInfo": "{id}. {name}",
"close": "Close",
"success": "Text pair added to folder",
"error": "Failed to add text pair to folder"
}
}
}

181
messages/zh-CN.json Normal file
View File

@@ -0,0 +1,181 @@
{
"alphabet": {
"chooseCharacters": "请选择您想学习的字符",
"japanese": "日语假名",
"english": "英文字母",
"uyghur": "维吾尔字母",
"esperanto": "世界语字母",
"loading": "加载中...",
"loadFailed": "加载失败,请重试",
"hideLetter": "隐藏字母",
"showLetter": "显示字母",
"hideIPA": "隐藏IPA",
"showIPA": "显示IPA"
},
"folders": {
"title": "文件夹",
"subtitle": "管理您的集合",
"newFolder": "新建文件夹",
"creating": "创建中...",
"noFoldersYet": "还没有文件夹",
"folderInfo": "{id}. {name} ({totalPairs})",
"enterFolderName": "输入文件夹名称:",
"confirmDelete": "输入 \"{name}\" 以删除:",
"createFolderSuccess": "文件夹创建成功",
"deleteFolderSuccess": "文件夹删除成功",
"createFolderError": "创建文件夹失败",
"deleteFolderError": "删除文件夹失败"
},
"folder_id": {
"unauthorized": "您不是此文件夹的所有者",
"back": "返回",
"textPairs": "文本对",
"itemsCount": "{count} 个项目",
"memorize": "记忆",
"loadingTextPairs": "加载文本对中...",
"noTextPairs": "此文件夹中没有文本对",
"addNewTextPair": "添加新文本对",
"add": "添加",
"updateTextPair": "更新文本对",
"update": "更新",
"text1": "文本1",
"text2": "文本2",
"locale1": "语言1",
"locale2": "语言2",
"edit": "编辑",
"delete": "删除"
},
"home": {
"title": "学语言",
"description": "这里是一个非常有用的网站,可以帮助您学习世界上几乎每一种语言,包括人造语言。",
"explore": "探索网站",
"fortune": {
"quote": "求知若饥,虚心若愚。",
"author": "—— 史蒂夫·乔布斯"
},
"translator": {
"name": "翻译器",
"description": "翻译到任何语言并标注国际音标IPA"
},
"textSpeaker": {
"name": "朗读器",
"description": "识别并朗读文本,支持循环朗读、朗读速度调节"
},
"srtPlayer": {
"name": "逐句视频播放器",
"description": "基于SRT字幕文件逐句播放视频以模仿母语者的发音"
},
"alphabet": {
"name": "背字母",
"description": "从字母表开始新语言的学习"
},
"memorize": {
"name": "背单词",
"description": "语言A到语言B语言B到语言A支持听写"
},
"moreFeatures": {
"name": "更多功能",
"description": "开发中,敬请期待"
}
},
"login": {
"loading": "加载中...",
"githubLogin": "GitHub登录"
},
"memorize": {
"choose": {
"back": "返回",
"choose": "选择"
},
"edit": {
"back": "返回",
"save": "保存单词对",
"locale1": "区域1",
"locale2": "区域2"
},
"folder_selector": {
"selectFolder": "选择文件夹",
"noFolders": "未找到文件夹",
"folderInfo": "{id}. {name} ({count})"
},
"main": {
"title": "记忆",
"locale1": "您选择的区域一是{locale}",
"locale2": "您选择的区域二是{locale}",
"total": "总计有{total}个单词对",
"start": "开始",
"import": "导入",
"export": "导出",
"edit": "编辑"
},
"memorize": {
"showAnswer": "显示答案",
"next": "下一个",
"reverse": "反向",
"dictation": "听写",
"noTextPairs": "没有可用的文本对",
"progress": "{current}/{total}"
},
"page": {
"unauthorized": "您无权访问该文件夹"
},
"start": {
"show": "显示",
"reverse": "反向",
"dictation": "听写",
"back": "返回",
"next": "下个"
}
},
"navbar": {
"title": "学语言",
"about": "关于",
"sourceCode": "源码",
"login": "登录",
"profile": "个人资料",
"folders": "文件夹"
},
"profile": {
"myProfile": "我的个人资料",
"email": "邮箱:{email}",
"logout": "退出登录"
},
"srt_player": {
"uploadVideo": "上传视频",
"uploadSubtitle": "上传字幕",
"pause": "暂停",
"play": "播放",
"previous": "上句",
"next": "下句",
"restart": "句首",
"autoPause": "自动暂停({enabled})"
},
"text_speaker": {
"generateIPA": "生成IPA",
"viewSavedItems": "查看保存项",
"confirmDeleteAll": "确定删光吗?(Y/N)"
},
"translator": {
"detectLanguage": "检测语言",
"generateIPA": "生成国际音标",
"translateInto": "翻译为",
"chinese": "中文",
"english": "英文",
"italian": "意大利语",
"other": "其他",
"translating": "翻译中...",
"translate": "翻译",
"inputLanguage": "请输入语言。",
"history": "历史记录",
"enterLanguage": "输入语言",
"add_to_folder": {
"notAuthenticated": "您未通过身份验证",
"chooseFolder": "选择要添加到的文件夹",
"noFolders": "未找到文件夹",
"folderInfo": "{id}. {name}",
"close": "关闭",
"success": "文本对已添加到文件夹",
"error": "添加文本对到文件夹失败"
}
}
}

View File

@@ -1,13 +0,0 @@
{
"chooseCharacters": "Please select the characters you want to learn",
"japanese": "Japanese Kana",
"english": "English Alphabet",
"uyghur": "Uyghur Alphabet",
"esperanto": "Esperanto Alphabet",
"loading": "Loading...",
"loadFailed": "Loading failed, please try again",
"hideLetter": "Hide Letter",
"showLetter": "Show Letter",
"hideIPA": "Hide IPA",
"showIPA": "Show IPA"
}

View File

@@ -1,30 +0,0 @@
{
"unauthorized": "You are not the owner of this folder",
"back": "Back",
"backToFolders": "Back to folders",
"folderName": "Folder: {name}",
"textPairs": "Text Pairs",
"itemsCount": "{count} items",
"memorize": "Memorize",
"loadingTextPairs": "Loading text pairs...",
"noTextPairs": "No text pairs in this folder",
"addTextPair": "Add Text Pair",
"addNewTextPair": "Add New Text Pair",
"text1": "Text 1",
"text2": "Text 2",
"locale1": "Locale 1",
"locale2": "Locale 2",
"save": "Save",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"confirmDelete": "Are you sure you want to delete this text pair?",
"updateTextPair": "Update Text Pair",
"update": "Update",
"textPairSaved": "Text pair saved successfully",
"textPairUpdated": "Text pair updated successfully",
"textPairDeleted": "Text pair deleted successfully",
"errorSavingTextPair": "Failed to save text pair",
"errorUpdatingTextPair": "Failed to update text pair",
"errorDeletingTextPair": "Failed to delete text pair"
}

View File

@@ -1,14 +0,0 @@
{
"title": "Folders",
"subtitle": "Manage your collections",
"newFolder": "New Folder",
"creating": "Creating...",
"noFoldersYet": "No folders yet",
"folderInfo": "{id}. {name} ({totalPairs})",
"enterFolderName": "Enter folder name:",
"confirmDelete": "Type \"{name}\" to delete:",
"createFolderError": "Failed to create folder",
"deleteFolderError": "Failed to delete folder",
"createFolderSuccess": "Folder created successfully",
"deleteFolderSuccess": "Folder deleted successfully"
}

View File

@@ -1,33 +0,0 @@
{
"title": "Learn Languages",
"description": "Here is a very useful website to help you learn almost every language in the world, including constructed ones.",
"explore": "Explore",
"fortune": {
"quote": "Stay hungry, stay foolish.",
"author": "— Steve Jobs"
},
"translator": {
"name": "Translator",
"description": "Translate to any language and annotate with International Phonetic Alphabet (IPA)"
},
"textSpeaker": {
"name": "Text Speaker",
"description": "Recognize and read text aloud, supports loop playback and speed adjustment"
},
"srtPlayer": {
"name": "SRT Video Player",
"description": "Play videos sentence by sentence based on SRT subtitle files to mimic native speaker pronunciation"
},
"alphabet": {
"name": "Memorize Alphabet",
"description": "Start learning a new language from the alphabet"
},
"memorize": {
"name": "Memorize Words",
"description": "Language A to Language B, Language B to Language A, supports dictation"
},
"moreFeatures": {
"name": "More Features",
"description": "Under development, stay tuned"
}
}

View File

@@ -1,4 +0,0 @@
{
"loading": "Loading...",
"githubLogin": "GitHub Login"
}

View File

@@ -1,4 +0,0 @@
{
"back": "Back",
"choose": "Choose"
}

View File

@@ -1,6 +0,0 @@
{
"back": "Back",
"save": "Save Word Pairs",
"locale1": "Locale 1",
"locale2": "Locale 2"
}

View File

@@ -1,5 +0,0 @@
{
"title": "Select a folder",
"noFolders": "No folders found",
"folderInfo": "{id}. {name} ({count})"
}

View File

@@ -1,10 +0,0 @@
{
"title": "Memorize",
"locale1": "Your selected locale 1 is {locale}",
"locale2": "Your selected locale 2 is {locale}",
"total": "There are {total} word pairs in total",
"start": "Start",
"import": "Import",
"export": "Export",
"edit": "Edit"
}

View File

@@ -1,8 +0,0 @@
{
"showAnswer": "Show Answer",
"next": "Next",
"reverse": "Reverse",
"dictation": "Dictation",
"noTextPairs": "No text pairs available",
"progress": "{index}/{total}"
}

View File

@@ -1,3 +0,0 @@
{
"unauthorized": "Unauthorized access to this folder"
}

View File

@@ -1,7 +0,0 @@
{
"show": "Show",
"reverse": "Reverse",
"dictation": "Dictation",
"back": "Back",
"next": "Next"
}

View File

@@ -1,8 +0,0 @@
{
"title": "learn-languages",
"about": "About",
"sourceCode": "GitHub",
"login": "Login",
"profile": "Profile",
"folders": "Folders"
}

View File

@@ -1,5 +0,0 @@
{
"myProfile": "My Profile",
"email": "Email: {email}",
"logout": "Logout"
}

View File

@@ -1,10 +0,0 @@
{
"uploadVideo": "Upload Video",
"uploadSubtitle": "Upload Subtitle",
"pause": "Pause",
"play": "Play",
"previous": "Previous",
"next": "Next",
"restart": "Restart",
"autoPause": "Auto Pause ({enabled})"
}

View File

@@ -1,5 +0,0 @@
{
"generateIPA": "Generate IPA",
"viewSavedItems": "View Saved Items",
"confirmDeleteAll": "Are you sure you want to delete everything? (Y/N)"
}

View File

@@ -1,14 +0,0 @@
{
"detectLanguage": "detect language",
"generateIPA": "generate ipa",
"translateInto": "translate into",
"chinese": "Chinese",
"english": "English",
"italian": "Italian",
"other": "Other",
"translating": "translating...",
"translate": "translate",
"inputLanguage": "Input a language.",
"history": "History",
"enterLanguage": "Enter language"
}

View File

@@ -1,8 +0,0 @@
{
"chooseFolder": "Choose a Folder to Add to",
"notAuthenticated": "You are not authenticated",
"noFoldersFound": "No folders found",
"close": "Close",
"addToFolderSuccess": "Text pair added to folder",
"addToFolderError": "Failed to add text pair to folder"
}

View File

@@ -1,13 +0,0 @@
{
"chooseCharacters": "请选择您想学习的字符",
"japanese": "日语假名",
"english": "英文字母",
"uyghur": "维吾尔字母",
"esperanto": "世界语字母",
"loading": "加载中...",
"loadFailed": "加载失败,请重试",
"hideLetter": "隐藏字母",
"showLetter": "显示字母",
"hideIPA": "隐藏IPA",
"showIPA": "显示IPA"
}

View File

@@ -1,31 +0,0 @@
{
"unauthorized": "您不是此文件夹的所有者",
"back": "返回",
"backToFolders": "返回文件夹",
"folderName": "文件夹:{name}",
"textPairs": "文本对",
"itemsCount": "{count} 项",
"memorize": "记忆",
"loadingTextPairs": "正在加载文本对...",
"noTextPairs": "此文件夹中没有文本对",
"addTextPair": "添加文本对",
"addNewTextPair": "添加新文本对",
"text1": "文本1",
"text2": "文本2",
"locale1": "语言1",
"locale2": "语言2",
"save": "保存",
"cancel": "取消",
"add": "添加",
"edit": "编辑",
"delete": "删除",
"confirmDelete": "确定要删除这个文本对吗?",
"updateTextPair": "更新文本对",
"update": "更新",
"textPairSaved": "文本对保存成功",
"textPairUpdated": "文本对更新成功",
"textPairDeleted": "文本对删除成功",
"errorSavingTextPair": "保存文本对失败",
"errorUpdatingTextPair": "更新文本对失败",
"errorDeletingTextPair": "删除文本对失败"
}

View File

@@ -1,14 +0,0 @@
{
"title": "文件夹",
"subtitle": "管理您的集合",
"newFolder": "新建文件夹",
"creating": "创建中...",
"noFoldersYet": "还没有文件夹",
"folderInfo": "{id}. {name} ({totalPairs})",
"enterFolderName": "输入文件夹名称:",
"confirmDelete": "输入 \"{name}\" 以删除:",
"createFolderError": "创建文件夹失败",
"deleteFolderError": "删除文件夹失败",
"createFolderSuccess": "文件夹创建成功",
"deleteFolderSuccess": "文件夹删除成功"
}

View File

@@ -1,33 +0,0 @@
{
"title": "学语言",
"description": "这里是一个非常有用的网站,可以帮助你学习世界上几乎每一种语言,包括人造语言。",
"explore": "探索网站",
"fortune": {
"quote": "求知若饥,虚心若愚。",
"author": "—— 史蒂夫·乔布斯"
},
"translator": {
"name": "翻译器",
"description": "翻译到任何语言并标注国际音标IPA"
},
"textSpeaker": {
"name": "朗读器",
"description": "识别并朗读文本,支持循环朗读、朗读速度调节"
},
"srtPlayer": {
"name": "逐句视频播放器",
"description": "基于SRT字幕文件逐句播放视频以模仿母语者的发音"
},
"alphabet": {
"name": "背字母",
"description": "从字母表开始新语言的学习"
},
"memorize": {
"name": "背单词",
"description": "语言A到语言B语言B到语言A支持听写"
},
"moreFeatures": {
"name": "更多功能",
"description": "开发中,敬请期待"
}
}

View File

@@ -1,4 +0,0 @@
{
"loading": "加载中...",
"githubLogin": "GitHub 登录"
}

View File

@@ -1,4 +0,0 @@
{
"back": "返回",
"choose": "选择"
}

View File

@@ -1,6 +0,0 @@
{
"back": "返回",
"save": "保存单词对",
"locale1": "区域1",
"locale2": "区域2"
}

View File

@@ -1,5 +0,0 @@
{
"title": "选择文件夹",
"noFolders": "未找到文件夹",
"folderInfo": "{id}. {name} ({count})"
}

View File

@@ -1,10 +0,0 @@
{
"title": "记忆",
"locale1": "您选择的区域一是{locale}",
"locale2": "您选择的区域二是{locale}",
"total": "总计有{total}个单词对",
"start": "开始",
"import": "导入",
"export": "导出",
"edit": "编辑"
}

View File

@@ -1,8 +0,0 @@
{
"showAnswer": "显示答案",
"next": "下一个",
"reverse": "反向",
"dictation": "听写",
"noTextPairs": "没有可用的文本对",
"progress": "{index}/{total}"
}

View File

@@ -1,3 +0,0 @@
{
"unauthorized": "您无权访问该文件夹"
}

View File

@@ -1,7 +0,0 @@
{
"show": "显示",
"reverse": "反向",
"dictation": "听写",
"back": "返回",
"next": "下个"
}

View File

@@ -1,8 +0,0 @@
{
"title": "学语言",
"about": "关于",
"sourceCode": "源码",
"login": "登录",
"profile": "个人资料",
"folders": "文件夹"
}

View File

@@ -1,5 +0,0 @@
{
"myProfile": "我的个人资料",
"email": "邮箱:{email}",
"logout": "退出登录"
}

View File

@@ -1,10 +0,0 @@
{
"uploadVideo": "上传视频",
"uploadSubtitle": "上传字幕",
"pause": "暂停",
"play": "播放",
"previous": "上句",
"next": "下句",
"restart": "句首",
"autoPause": "自动暂停({enabled})"
}

View File

@@ -1,5 +0,0 @@
{
"generateIPA": "生成IPA",
"viewSavedItems": "查看保存项",
"confirmDeleteAll": "确定删光吗?(Y/N)"
}

View File

@@ -1,14 +0,0 @@
{
"detectLanguage": "检测语言",
"generateIPA": "生成国际音标",
"translateInto": "翻译为",
"chinese": "中文",
"english": "英文",
"italian": "意大利语",
"other": "其他",
"translating": "翻译中...",
"translate": "翻译",
"inputLanguage": "请输入语言。",
"history": "历史记录",
"enterLanguage": "输入语言"
}

View File

@@ -1,8 +0,0 @@
{
"chooseFolder": "选择要添加到的文件夹",
"notAuthenticated": "您未登录",
"noFoldersFound": "未找到文件夹",
"close": "关闭",
"addToFolderSuccess": "文本对已添加到文件夹",
"addToFolderError": "添加文本对到文件夹失败"
}

View File

@@ -12,7 +12,7 @@ interface FolderSelectorProps {
} }
const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => { const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
const t = useTranslations("memorize/folder-selector"); const t = useTranslations("memorize.folder_selector");
const router = useRouter(); const router = useRouter();
return ( return (
<Center> <Center>

View File

@@ -9,7 +9,7 @@ export default function UploadArea({
setVideoUrl: (url: string | null) => void; setVideoUrl: (url: string | null) => void;
setSrtUrl: (url: string | null) => void; setSrtUrl: (url: string | null) => void;
}) { }) {
const t = useTranslations("srt-player"); const t = useTranslations("srt_player");
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const uploadVideo = () => { const uploadVideo = () => {

View File

@@ -11,7 +11,7 @@ type VideoPanelProps = {
const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>( const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>(
({ videoUrl, srtUrl }, videoRef) => { ({ videoUrl, srtUrl }, videoRef) => {
const t = useTranslations("srt-player"); const t = useTranslations("srt_player");
videoRef = videoRef as React.RefObject<HTMLVideoElement>; videoRef = videoRef as React.RefObject<HTMLVideoElement>;
const [isPlaying, setIsPlaying] = useState<boolean>(false); const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [srtLength, setSrtLength] = useState<number>(0); const [srtLength, setSrtLength] = useState<number>(0);

View File

@@ -51,7 +51,7 @@ interface SaveListProps {
handleUse: (item: z.infer<typeof TextSpeakerItemSchema>) => void; handleUse: (item: z.infer<typeof TextSpeakerItemSchema>) => void;
} }
export default function SaveList({ show = false, handleUse }: SaveListProps) { export default function SaveList({ show = false, handleUse }: SaveListProps) {
const t = useTranslations("text-speaker"); const t = useTranslations("text_speaker");
const { get: getFromLocalStorage, set: setIntoLocalStorage } = const { get: getFromLocalStorage, set: setIntoLocalStorage } =
getLocalStorageOperator<typeof TextSpeakerArraySchema>( getLocalStorageOperator<typeof TextSpeakerArraySchema>(
"text-speaker", "text-speaker",

View File

@@ -4,7 +4,10 @@ import LightButton from "@/components/buttons/LightButton";
import IconClick from "@/components/IconClick"; import IconClick from "@/components/IconClick";
import IMAGES from "@/config/images"; import IMAGES from "@/config/images";
import { useAudioPlayer } from "@/hooks/useAudioPlayer"; import { useAudioPlayer } from "@/hooks/useAudioPlayer";
import { TextSpeakerArraySchema, TextSpeakerItemSchema } from "@/lib/interfaces"; import {
TextSpeakerArraySchema,
TextSpeakerItemSchema,
} from "@/lib/interfaces";
import { getLocalStorageOperator, getTTSAudioUrl } from "@/lib/utils"; import { getLocalStorageOperator, getTTSAudioUrl } from "@/lib/utils";
import { ChangeEvent, useEffect, useRef, useState } from "react"; import { ChangeEvent, useEffect, useRef, useState } from "react";
import z from "zod"; import z from "zod";
@@ -14,7 +17,7 @@ import { VOICES } from "@/config/locales";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
export default function TextSpeakerPage() { export default function TextSpeakerPage() {
const t = useTranslations("text-speaker"); const t = useTranslations("text_speaker");
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const [showSpeedAdjust, setShowSpeedAdjust] = useState(false); const [showSpeedAdjust, setShowSpeedAdjust] = useState(false);
const [showSaveList, setShowSaveList] = useState(false); const [showSaveList, setShowSaveList] = useState(false);
@@ -30,9 +33,11 @@ export default function TextSpeakerPage() {
const [processing, setProcessing] = useState(false); const [processing, setProcessing] = useState(false);
const { play, stop, load, audioRef } = useAudioPlayer(); const { play, stop, load, audioRef } = useAudioPlayer();
const { get: getFromLocalStorage, set: setIntoLocalStorage } = getLocalStorageOperator< const { get: getFromLocalStorage, set: setIntoLocalStorage } =
typeof TextSpeakerArraySchema getLocalStorageOperator<typeof TextSpeakerArraySchema>(
>("text-speaker", TextSpeakerArraySchema); "text-speaker",
TextSpeakerArraySchema,
);
useEffect(() => { useEffect(() => {
const audio = audioRef.current; const audio = audioRef.current;

View File

@@ -19,7 +19,7 @@ interface AddToFolderProps {
const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => { const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
const session = useSession(); const session = useSession();
const [folders, setFolders] = useState<folder[]>([]); const [folders, setFolders] = useState<folder[]>([]);
const t = useTranslations("translator.add-to-folder"); const t = useTranslations("translator.add_to_folder");
useEffect(() => { useEffect(() => {
const username = session.data!.user!.name as string; const username = session.data!.user!.name as string;

View File

@@ -20,7 +20,7 @@ export default function AddTextPairModal({
onClose, onClose,
onAdd, onAdd,
}: AddTextPairModalProps) { }: AddTextPairModalProps) {
const t = useTranslations("folders.folder_id"); const t = useTranslations("folder_id");
const input1Ref = useRef<HTMLInputElement>(null); const input1Ref = useRef<HTMLInputElement>(null);
const input2Ref = useRef<HTMLInputElement>(null); const input2Ref = useRef<HTMLInputElement>(null);
const input3Ref = useRef<HTMLInputElement>(null); const input3Ref = useRef<HTMLInputElement>(null);

View File

@@ -28,7 +28,7 @@ export default function InFolder({ folderId }: { folderId: number }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [openAddModal, setAddModal] = useState(false); const [openAddModal, setAddModal] = useState(false);
const router = useRouter(); const router = useRouter();
const t = useTranslations("folders.folder_id"); const t = useTranslations("folder_id");
useEffect(() => { useEffect(() => {
const fetchTextPairs = async () => { const fetchTextPairs = async () => {

View File

@@ -4,6 +4,7 @@ import { updateTextPairById } from "@/lib/services/textPairService";
import { useState } from "react"; import { useState } from "react";
import { text_pairUpdateInput } from "../../../../generated/prisma/models"; import { text_pairUpdateInput } from "../../../../generated/prisma/models";
import UpdateTextPairModal from "./UpdateTextPairModal"; import UpdateTextPairModal from "./UpdateTextPairModal";
import { useTranslations } from "next-intl";
interface TextPairCardProps { interface TextPairCardProps {
textPair: TextPair; textPair: TextPair;
@@ -17,6 +18,7 @@ export default function TextPairCard({
refreshTextPairs, refreshTextPairs,
}: TextPairCardProps) { }: TextPairCardProps) {
const [openUpdateModal, setOpenUpdateModal] = useState(false); const [openUpdateModal, setOpenUpdateModal] = useState(false);
const t = useTranslations("folder_id");
return ( return (
<div className="group border-b border-gray-100 hover:bg-gray-50 transition-colors"> <div className="group border-b border-gray-100 hover:bg-gray-50 transition-colors">
<div className="p-4"> <div className="p-4">
@@ -35,11 +37,16 @@ export default function TextPairCard({
<button <button
className="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-md transition-colors" className="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
onClick={() => setOpenUpdateModal(true)} onClick={() => setOpenUpdateModal(true)}
title={t("edit")}
> >
<Edit size={14} /> <Edit size={14} />
</button> </button>
<button className="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-md transition-colors"> <button
<Trash2 size={14} onClick={onDel} /> className="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-md transition-colors"
onClick={onDel}
title={t("delete")}
>
<Trash2 size={14} />
</button> </button>
</div> </div>
</div> </div>

View File

@@ -19,7 +19,7 @@ export default function UpdateTextPairModal({
onUpdate, onUpdate,
textPair, textPair,
}: UpdateTextPairModalProps) { }: UpdateTextPairModalProps) {
const t = useTranslations("folders.folder_id"); const t = useTranslations("folder_id");
const input1Ref = useRef<HTMLInputElement>(null); const input1Ref = useRef<HTMLInputElement>(null);
const input2Ref = useRef<HTMLInputElement>(null); const input2Ref = useRef<HTMLInputElement>(null);
const input3Ref = useRef<HTMLInputElement>(null); const input3Ref = useRef<HTMLInputElement>(null);

View File

@@ -11,7 +11,7 @@ export default async function FoldersPage({
const session = await getServerSession(); const session = await getServerSession();
const { folder_id } = await params; const { folder_id } = await params;
const id = Number(folder_id); const id = Number(folder_id);
const t = await getTranslations("folders.folder_id"); const t = await getTranslations("folder_id");
if (!id) { if (!id) {
redirect("/folders"); redirect("/folders");

View File

@@ -1,39 +1,6 @@
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "@/config/i18n"; import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "@/config/i18n";
import { getRequestConfig } from "next-intl/server"; import { getRequestConfig } from "next-intl/server";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { readFileSync, readdirSync, statSync } from "fs";
import { join } from "path";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function loadMessagesFromDir(dirPath: string): Record<string, any> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const messages: Record<string, any> = {};
try {
const items = readdirSync(dirPath);
for (const item of items) {
const fullPath = join(dirPath, item);
const stat = statSync(fullPath);
if (stat.isDirectory()) {
const dirMessages = loadMessagesFromDir(fullPath);
Object.assign(messages, { [item]: dirMessages });
} else if (item.endsWith(".json")) {
try {
const content = readFileSync(fullPath, "utf-8");
const jsonContent = JSON.parse(content);
Object.assign(messages, { [item.replace(".json", "")]: jsonContent });
} catch (error) {
console.warn(`Failed to load JSON file ${fullPath}:`, error);
}
}
}
} catch (error) {
console.warn(`Failed to read directory ${dirPath}:`, error);
}
return messages;
}
export default getRequestConfig(async () => { export default getRequestConfig(async () => {
const store = await cookies(); const store = await cookies();
@@ -43,11 +10,8 @@ export default getRequestConfig(async () => {
return locale; return locale;
})(); })();
const messagesPath = join(process.cwd(), "public/messages", locale);
const messages = loadMessagesFromDir(messagesPath);
return { return {
locale, locale,
messages, messages: (await import(`../../messages/${locale}.json`)).default,
}; };
}); });