diff --git a/src/components/common/OpenProjectModal.tsx b/src/components/common/OpenProjectModal.tsx index a98b531..ab0173c 100644 --- a/src/components/common/OpenProjectModal.tsx +++ b/src/components/common/OpenProjectModal.tsx @@ -4,6 +4,8 @@ import { KGProjectStorage, type ProjectMeta } from '../../core/io/KGProjectStora import { isValidProjectName } from '../../util/projectNameUtil'; import './OpenProjectModal.css'; import { showAlert, showConfirm, showPrompt } from '../../util/dialogUtil'; +import { useI18n } from '../../i18n/useI18n'; +import { translate } from '../../i18n/translate'; interface OpenProjectModalProps { onClose: () => void; @@ -34,7 +36,7 @@ const matchesFilter = (projectName: string, filter: string): boolean => { }; const formatDate = (timestamp: number): string => { - if (timestamp === 0) return 'Unknown'; + if (timestamp === 0) return translate('openProjectModal.date.unknown'); const date = new Date(timestamp); return date.toLocaleDateString(undefined, { year: 'numeric', @@ -52,6 +54,7 @@ const OpenProjectModal: React.FC = ({ currentProjectName, onCreateNewProject }) => { + const { t } = useI18n(); const [projects, setProjects] = useState([]); const [isLoading, setIsLoading] = useState(true); const [filter, setFilter] = useState(''); @@ -145,12 +148,12 @@ const OpenProjectModal: React.FC = ({ const handleDuplicate = async (e: React.MouseEvent, projectName: string) => { e.stopPropagation(); - const newName = await showPrompt('Enter a name for the duplicated project:', projectName); + const newName = await showPrompt(t('openProjectModal.dialog.duplicateName'), projectName); if (!newName || newName.trim() === '') return; const trimmed = newName.trim(); if (!isValidProjectName(trimmed)) { - await showAlert('Invalid project name. Only letters, numbers, spaces, hyphens, underscores, periods, and parentheses are allowed.'); + await showAlert(t('openProjectModal.dialog.invalidName')); return; } @@ -165,7 +168,7 @@ const OpenProjectModal: React.FC = ({ startClose(); } catch (error) { console.error('Error duplicating project:', error); - await showAlert(`Failed to duplicate project: ${error}`); + await showAlert(t('openProjectModal.dialog.duplicateError', { error: String(error) })); } }; @@ -177,7 +180,7 @@ const OpenProjectModal: React.FC = ({ await fetchProjects(viewMode); } catch (error) { console.error('Error deleting project:', error); - await showAlert(`Failed to delete project: ${error}`); + await showAlert(t('openProjectModal.dialog.deleteError', { error: String(error) })); } }; @@ -189,14 +192,14 @@ const OpenProjectModal: React.FC = ({ await fetchProjects(viewMode); } catch (error) { console.error('Error restoring project:', error); - await showAlert(`Failed to restore project: ${error}`); + await showAlert(t('openProjectModal.dialog.restoreError', { error: String(error) })); } }; const handlePermanentDelete = async (e: React.MouseEvent, projectName: string) => { e.stopPropagation(); const confirmed = await showConfirm( - `Are you sure you want to permanently delete "${projectName}"?\n\nThis operation cannot be undone.` + t('openProjectModal.dialog.confirmPermanentDelete', { name: projectName }) ); if (!confirmed) return; @@ -210,7 +213,7 @@ const OpenProjectModal: React.FC = ({ } } catch (error) { console.error('Error permanently deleting project:', error); - await showAlert(`Failed to permanently delete project: ${error}`); + await showAlert(t('openProjectModal.dialog.permanentDeleteError', { error: String(error) })); } }; @@ -243,8 +246,8 @@ const OpenProjectModal: React.FC = ({ >
-

Open Project

-
@@ -253,7 +256,7 @@ const OpenProjectModal: React.FC = ({
setFilter(e.target.value)} autoFocus @@ -263,13 +266,13 @@ const OpenProjectModal: React.FC = ({ className={`open-project-toggle-btn ${viewMode === 'projects' ? 'active' : ''}`} onClick={() => handleViewModeChange('projects')} > - Projects + {t('openProjectModal.viewToggle.projects')}
@@ -277,7 +280,7 @@ const OpenProjectModal: React.FC = ({ {viewMode === 'trash' && (
- Deleted projects are automatically removed after 30 days. + {t('openProjectModal.trash.hint')}
)} @@ -286,29 +289,29 @@ const OpenProjectModal: React.FC = ({ className={`open-project-sort-col open-project-sort-col-name ${sortField === 'name' ? 'active' : ''}`} onClick={() => handleSortClick('name')} > - Name + {t('openProjectModal.sort.name')} handleSortClick('createdAt')} > - Created + {t('openProjectModal.sort.created')} handleSortClick('updatedAt')} > - Updated + {t('openProjectModal.sort.updated')} {isLoading ? ( -
Loading projects...
+
{t('openProjectModal.loading')}
) : filteredProjects.length === 0 ? (
{projects.length === 0 - ? (viewMode === 'trash' ? 'Trash is empty' : 'No saved projects') - : 'No projects match the filter'} + ? (viewMode === 'trash' ? t('openProjectModal.empty.trash') : t('openProjectModal.empty.noProjects')) + : t('openProjectModal.empty.noMatch')}
) : (
@@ -321,9 +324,9 @@ const OpenProjectModal: React.FC = ({
{project.name}
- Created {formatDate(project.createdAt)} · Updated {formatDate(project.updatedAt)} + {t('openProjectModal.meta.created', { date: formatDate(project.createdAt) })} · {t('openProjectModal.meta.updated', { date: formatDate(project.updatedAt) })} {viewMode === 'trash' && project.deletedAt && ( - <> · Deleted {formatDate(project.deletedAt)} + <> · {t('openProjectModal.meta.deleted', { date: formatDate(project.deletedAt) })} )}
@@ -334,19 +337,19 @@ const OpenProjectModal: React.FC = ({ className="open-project-item-open-btn" onClick={(e) => { e.stopPropagation(); handleOpen(project.name); }} > - Open + {t('openProjectModal.action.open')} @@ -356,14 +359,14 @@ const OpenProjectModal: React.FC = ({ diff --git a/src/i18n/messages/en_us.ts b/src/i18n/messages/en_us.ts index 20e667d..6d7c01a 100644 --- a/src/i18n/messages/en_us.ts +++ b/src/i18n/messages/en_us.ts @@ -659,4 +659,34 @@ export const enUsMessages: TranslationMessages = { 'kgone.separator.local.progress.readingAudio': 'Reading audio file...', 'kgone.separator.local.progress.running': 'Running browser separation...', 'kgone.separator.local.progress.complete': 'Separation complete.', + + 'openProjectModal.title': 'Open Project', + 'openProjectModal.close': 'Close', + 'openProjectModal.loading': 'Loading projects...', + 'openProjectModal.date.unknown': 'Unknown', + 'openProjectModal.filter.placeholder': 'Filter projects...', + 'openProjectModal.viewToggle.projects': 'Projects', + 'openProjectModal.viewToggle.trash': 'Trash', + 'openProjectModal.trash.hint': 'Deleted projects are automatically removed after 30 days.', + 'openProjectModal.sort.name': 'Name', + 'openProjectModal.sort.created': 'Created', + 'openProjectModal.sort.updated': 'Updated', + 'openProjectModal.empty.trash': 'Trash is empty', + 'openProjectModal.empty.noProjects': 'No saved projects', + 'openProjectModal.empty.noMatch': 'No projects match the filter', + 'openProjectModal.meta.created': 'Created {date}', + 'openProjectModal.meta.updated': 'Updated {date}', + 'openProjectModal.meta.deleted': 'Deleted {date}', + 'openProjectModal.action.open': 'Open', + 'openProjectModal.action.duplicate': 'Duplicate', + 'openProjectModal.action.delete': 'Delete', + 'openProjectModal.action.restore': 'Restore', + 'openProjectModal.action.deletePermanently': 'Delete permanently', + 'openProjectModal.dialog.duplicateName': 'Enter a name for the duplicated project:', + 'openProjectModal.dialog.invalidName': 'Invalid project name. Only letters, numbers, spaces, hyphens, underscores, periods, and parentheses are allowed.', + 'openProjectModal.dialog.duplicateError': 'Failed to duplicate project: {error}', + 'openProjectModal.dialog.deleteError': 'Failed to delete project: {error}', + 'openProjectModal.dialog.restoreError': 'Failed to restore project: {error}', + 'openProjectModal.dialog.confirmPermanentDelete': 'Are you sure you want to permanently delete "{name}"?\n\nThis operation cannot be undone.', + 'openProjectModal.dialog.permanentDeleteError': 'Failed to permanently delete project: {error}', }; diff --git a/src/i18n/messages/zh_cn.ts b/src/i18n/messages/zh_cn.ts index 2cd3023..71f1dcd 100644 --- a/src/i18n/messages/zh_cn.ts +++ b/src/i18n/messages/zh_cn.ts @@ -659,4 +659,34 @@ export const zhCnMessages: TranslationMessages = { 'kgone.separator.local.progress.readingAudio': '正在读取音频文件...', 'kgone.separator.local.progress.running': '正在浏览器中执行分离...', 'kgone.separator.local.progress.complete': '分离完成。', + + 'openProjectModal.title': '打开项目', + 'openProjectModal.close': '关闭', + 'openProjectModal.loading': '正在加载项目...', + 'openProjectModal.date.unknown': '未知', + 'openProjectModal.filter.placeholder': '筛选项目...', + 'openProjectModal.viewToggle.projects': '项目', + 'openProjectModal.viewToggle.trash': '回收站', + 'openProjectModal.trash.hint': '已删除的项目将在 30 天后自动移除。', + 'openProjectModal.sort.name': '名称', + 'openProjectModal.sort.created': '创建时间', + 'openProjectModal.sort.updated': '更新时间', + 'openProjectModal.empty.trash': '回收站为空', + 'openProjectModal.empty.noProjects': '暂无已保存的项目', + 'openProjectModal.empty.noMatch': '没有符合筛选条件的项目', + 'openProjectModal.meta.created': '创建于 {date}', + 'openProjectModal.meta.updated': '更新于 {date}', + 'openProjectModal.meta.deleted': '删除于 {date}', + 'openProjectModal.action.open': '打开', + 'openProjectModal.action.duplicate': '复制', + 'openProjectModal.action.delete': '删除', + 'openProjectModal.action.restore': '恢复', + 'openProjectModal.action.deletePermanently': '永久删除', + 'openProjectModal.dialog.duplicateName': '请输入复制项目的名称:', + 'openProjectModal.dialog.invalidName': '项目名称无效。只允许使用字母、数字、空格、连字符、下划线、句号和括号。', + 'openProjectModal.dialog.duplicateError': '复制项目失败:{error}', + 'openProjectModal.dialog.deleteError': '删除项目失败:{error}', + 'openProjectModal.dialog.restoreError': '恢复项目失败:{error}', + 'openProjectModal.dialog.confirmPermanentDelete': '确定要永久删除"{name}"吗?\n\n此操作无法撤销。', + 'openProjectModal.dialog.permanentDeleteError': '永久删除项目失败:{error}', };