feat: added i18n support for Open Project pop-up

This commit is contained in:
Xiaohan-Tian
2026-05-30 22:18:22 -07:00
parent b5546aa230
commit 4488fe8ecd
3 changed files with 90 additions and 27 deletions
+30 -27
View File
@@ -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<OpenProjectModalProps> = ({
currentProjectName,
onCreateNewProject
}) => {
const { t } = useI18n();
const [projects, setProjects] = useState<ProjectMeta[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [filter, setFilter] = useState('');
@@ -145,12 +148,12 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
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<OpenProjectModalProps> = ({
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<OpenProjectModalProps> = ({
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<OpenProjectModalProps> = ({
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<OpenProjectModalProps> = ({
}
} 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<OpenProjectModalProps> = ({
>
<div className={`open-project-panel${isClosing ? ' open-project-panel-closing' : ''}`}>
<div className="open-project-header">
<h3 className="open-project-title">Open Project</h3>
<button className="open-project-close-btn" onClick={startClose} aria-label="Close">
<h3 className="open-project-title">{t('openProjectModal.title')}</h3>
<button className="open-project-close-btn" onClick={startClose} aria-label={t('openProjectModal.close')}>
<FaTimes />
</button>
</div>
@@ -253,7 +256,7 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
<div className="open-project-filter-row">
<input
type="text"
placeholder="Filter projects..."
placeholder={t('openProjectModal.filter.placeholder')}
value={filter}
onChange={(e) => setFilter(e.target.value)}
autoFocus
@@ -263,13 +266,13 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
className={`open-project-toggle-btn ${viewMode === 'projects' ? 'active' : ''}`}
onClick={() => handleViewModeChange('projects')}
>
Projects
{t('openProjectModal.viewToggle.projects')}
</button>
<button
className={`open-project-toggle-btn ${viewMode === 'trash' ? 'active' : ''}`}
onClick={() => handleViewModeChange('trash')}
>
Trash
{t('openProjectModal.viewToggle.trash')}
</button>
</div>
</div>
@@ -277,7 +280,7 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
{viewMode === 'trash' && (
<div className="open-project-trash-hint">
Deleted projects are automatically removed after 30 days.
{t('openProjectModal.trash.hint')}
</div>
)}
@@ -286,29 +289,29 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
className={`open-project-sort-col open-project-sort-col-name ${sortField === 'name' ? 'active' : ''}`}
onClick={() => handleSortClick('name')}
>
Name <SortIcon field="name" />
{t('openProjectModal.sort.name')} <SortIcon field="name" />
</span>
<span
className={`open-project-sort-col open-project-sort-col-created ${sortField === 'createdAt' ? 'active' : ''}`}
onClick={() => handleSortClick('createdAt')}
>
Created <SortIcon field="createdAt" />
{t('openProjectModal.sort.created')} <SortIcon field="createdAt" />
</span>
<span
className={`open-project-sort-col open-project-sort-col-updated ${sortField === 'updatedAt' ? 'active' : ''}`}
onClick={() => handleSortClick('updatedAt')}
>
Updated <SortIcon field="updatedAt" />
{t('openProjectModal.sort.updated')} <SortIcon field="updatedAt" />
</span>
</div>
{isLoading ? (
<div className="open-project-loading">Loading projects...</div>
<div className="open-project-loading">{t('openProjectModal.loading')}</div>
) : filteredProjects.length === 0 ? (
<div className="open-project-empty">
{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')}
</div>
) : (
<div className="open-project-list">
@@ -321,9 +324,9 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
<div className="open-project-item-info">
<div className="open-project-item-name">{project.name}</div>
<div className="open-project-item-meta">
Created {formatDate(project.createdAt)} &middot; Updated {formatDate(project.updatedAt)}
{t('openProjectModal.meta.created', { date: formatDate(project.createdAt) })} &middot; {t('openProjectModal.meta.updated', { date: formatDate(project.updatedAt) })}
{viewMode === 'trash' && project.deletedAt && (
<> &middot; Deleted {formatDate(project.deletedAt)}</>
<> &middot; {t('openProjectModal.meta.deleted', { date: formatDate(project.deletedAt) })}</>
)}
</div>
</div>
@@ -334,19 +337,19 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
className="open-project-item-open-btn"
onClick={(e) => { e.stopPropagation(); handleOpen(project.name); }}
>
Open
{t('openProjectModal.action.open')}
</button>
<button
className="open-project-item-action-btn open-project-item-duplicate-btn"
onClick={(e) => handleDuplicate(e, project.name)}
title="Duplicate"
title={t('openProjectModal.action.duplicate')}
>
<FaCopy />
</button>
<button
className="open-project-item-action-btn open-project-item-delete-btn"
onClick={(e) => handleSoftDelete(e, project.name)}
title="Delete"
title={t('openProjectModal.action.delete')}
>
<FaTrash />
</button>
@@ -356,14 +359,14 @@ const OpenProjectModal: React.FC<OpenProjectModalProps> = ({
<button
className="open-project-item-action-btn open-project-item-restore-btn"
onClick={(e) => handleRestore(e, project.name)}
title="Restore"
title={t('openProjectModal.action.restore')}
>
<FaUndo />
</button>
<button
className="open-project-item-action-btn open-project-item-permdelete-btn"
onClick={(e) => handlePermanentDelete(e, project.name)}
title="Delete permanently"
title={t('openProjectModal.action.deletePermanently')}
>
<FaTrash />
</button>
+30
View File
@@ -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}',
};
+30
View File
@@ -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}',
};