feat: complete i18n support for all frontend pages and components

Add comprehensive bilingual (zh-TW/en-US) support across the entire frontend:

Pages updated:
- AdminDashboardPage: All 63+ strings translated
- TaskHistoryPage: All 80+ strings translated
- TaskDetailPage: All 90+ strings translated
- AuditLogsPage: All audit log UI translated
- ResultsPage/ProcessingPage: Fixed i18n integration
- UploadPage: Step indicators and file list UI translated

Components updated:
- TaskNotFound: Task deletion messages
- FileUpload: Prompts and file size limits
- ProcessingTrackSelector: Processing mode options with analysis info
- Layout: Navigation descriptions
- ProtectedRoute: Loading and access denied messages
- PDFViewer: Page navigation and error messages

Locale files: Added ~200 new translation keys to both zh-TW.json and en-US.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-14 11:56:18 +08:00
parent 3876477bda
commit 81a0a3ab0f
15 changed files with 1111 additions and 351 deletions

View File

@@ -124,8 +124,8 @@ function SingleTaskProcessing() {
updateTaskStatus(taskId, 'processing', forceTrack || undefined)
}
toast({
title: '開始處理',
description: 'OCR 處理已開始',
title: t('processing.startProcessing'),
description: t('processing.ocrStarted'),
variant: 'success',
})
},
@@ -200,7 +200,7 @@ function SingleTaskProcessing() {
<div className="flex items-center justify-center min-h-[60vh]">
<div className="text-center">
<Loader2 className="w-12 h-12 animate-spin text-primary mx-auto mb-4" />
<p className="text-muted-foreground">...</p>
<p className="text-muted-foreground">{t('processing.loadingTask')}</p>
</div>
</div>
)
@@ -226,13 +226,13 @@ function SingleTaskProcessing() {
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground">
{t('processing.noBatchMessage', { defaultValue: '尚未選擇任何任務。請先上傳檔案以建立任務。' })}
{t('processing.noBatchMessage')}
</p>
<Button
onClick={() => navigate('/upload')}
size="lg"
>
{t('processing.goToUpload', { defaultValue: '前往上傳頁面' })}
{t('processing.goToUpload')}
</Button>
</CardContent>
</Card>
@@ -252,7 +252,7 @@ function SingleTaskProcessing() {
<div>
<h1 className="page-title">{t('processing.title')}</h1>
<p className="text-muted-foreground mt-1">
ID: <span className="font-mono text-primary">{taskId}</span>
{t('taskDetail.taskId', { id: taskId })}
{taskDetail?.filename && ` · ${taskDetail.filename}`}
</p>
</div>
@@ -260,13 +260,13 @@ function SingleTaskProcessing() {
{isCompleted && (
<div className="flex items-center gap-2 text-success">
<CheckCircle className="w-6 h-6" />
<span className="font-semibold"></span>
<span className="font-semibold">{t('processing.completed')}</span>
</div>
)}
{isProcessing && (
<div className="flex items-center gap-2 text-primary">
<Loader2 className="w-6 h-6 animate-spin" />
<span className="font-semibold"></span>
<span className="font-semibold">{t('processing.processing')}</span>
</div>
)}
</div>
@@ -311,9 +311,9 @@ function SingleTaskProcessing() {
<FileText className="w-5 h-5 text-primary" />
</div>
<div>
<p className="text-xs text-muted-foreground mb-0.5"></p>
<p className="text-xs text-muted-foreground mb-0.5">{t('taskDetail.filename')}</p>
<p className="text-sm font-medium text-foreground truncate">
{taskDetail.filename || '未知檔案'}
{taskDetail.filename || t('common.unknownFile')}
</p>
</div>
</div>
@@ -325,7 +325,7 @@ function SingleTaskProcessing() {
<Clock className="w-5 h-5 text-success" />
</div>
<div>
<p className="text-xs text-muted-foreground mb-0.5"></p>
<p className="text-xs text-muted-foreground mb-0.5">{t('taskDetail.processingTime')}</p>
<p className="text-sm font-medium text-foreground">
{(taskDetail.processing_time_ms / 1000).toFixed(2)}s
</p>
@@ -342,7 +342,7 @@ function SingleTaskProcessing() {
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-destructive flex-shrink-0 mt-0.5" />
<div>
<p className="text-sm font-medium text-destructive mb-1"></p>
<p className="text-sm font-medium text-destructive mb-1">{t('processing.failed')}</p>
<p className="text-sm text-destructive/80">{taskDetail.error_message}</p>
</div>
</div>
@@ -380,7 +380,7 @@ function SingleTaskProcessing() {
size="lg"
>
<CheckCircle className="w-4 h-4" />
{t('results.viewTaskHistory')}
</Button>
)}
</div>
@@ -396,24 +396,24 @@ function SingleTaskProcessing() {
<div className="p-2 bg-primary/10 rounded-lg">
<FileText className="w-5 h-5 text-primary" />
</div>
<CardTitle></CardTitle>
<CardTitle>{t('taskDetail.title')}</CardTitle>
</div>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex justify-between py-2 border-b border-border">
<span className="text-sm text-muted-foreground"></span>
<span className="text-sm text-muted-foreground">{t('taskDetail.taskStatus')}</span>
{getStatusBadge(taskDetail.status)}
</div>
<div className="flex justify-between py-2 border-b border-border">
<span className="text-sm text-muted-foreground"></span>
<span className="text-sm text-muted-foreground">{t('taskDetail.createdAt')}</span>
<span className="text-sm font-medium">
{new Date(taskDetail.created_at).toLocaleString('zh-TW')}
</span>
</div>
{taskDetail.updated_at && (
<div className="flex justify-between py-2 border-b border-border">
<span className="text-sm text-muted-foreground"></span>
<span className="text-sm text-muted-foreground">{t('taskDetail.lastUpdated')}</span>
<span className="text-sm font-medium">
{new Date(taskDetail.updated_at).toLocaleString('zh-TW')}
</span>
@@ -421,7 +421,7 @@ function SingleTaskProcessing() {
)}
{taskDetail.completed_at && (
<div className="flex justify-between py-2 border-b border-border">
<span className="text-sm text-muted-foreground"></span>
<span className="text-sm text-muted-foreground">{t('taskDetail.completedAt')}</span>
<span className="text-sm font-medium">
{new Date(taskDetail.completed_at).toLocaleString('zh-TW')}
</span>
@@ -439,7 +439,7 @@ function SingleTaskProcessing() {
{isAnalyzing && (
<div className="flex items-center gap-2 p-4 bg-muted/30 rounded-lg border">
<Loader2 className="w-4 h-4 animate-spin text-primary" />
<span className="text-sm text-muted-foreground">...</span>
<span className="text-sm text-muted-foreground">{t('processing.analyzingDocument')}</span>
</div>
)}