feat: add translated PDF export with layout preservation
Adds the ability to download translated documents as PDF files while
preserving the original document layout. Key changes:
- Add apply_translations() function to merge translation JSON with UnifiedDocument
- Add generate_translated_pdf() method to PDFGeneratorService
- Add POST /api/v2/translate/{task_id}/pdf endpoint
- Add downloadTranslatedPdf() method and PDF button in frontend
- Add comprehensive unit tests (52 tests: merge, PDF generation, API endpoints)
- Archive add-translated-pdf-export proposal
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,8 @@ import {
|
||||
Languages,
|
||||
Globe,
|
||||
CheckCircle,
|
||||
Trash2
|
||||
Trash2,
|
||||
FileOutput
|
||||
} from 'lucide-react'
|
||||
import type { ProcessingTrack, TranslationStatus, TranslationListItem } from '@/types/apiV2'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
@@ -327,6 +328,24 @@ export default function TaskDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadTranslatedPdf = async (lang: string) => {
|
||||
if (!taskId) return
|
||||
try {
|
||||
await apiClientV2.downloadTranslatedPdf(taskId, lang)
|
||||
toast({
|
||||
title: '下載成功',
|
||||
description: `翻譯 PDF (${lang}) 已下載`,
|
||||
variant: 'success',
|
||||
})
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: '下載失敗',
|
||||
description: error.response?.data?.detail || t('errors.networkError'),
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
@@ -603,7 +622,16 @@ export default function TaskDetailPage() {
|
||||
className="gap-1"
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
下載
|
||||
JSON
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={() => handleDownloadTranslatedPdf(item.target_lang)}
|
||||
className="gap-1"
|
||||
>
|
||||
<FileOutput className="w-3 h-3" />
|
||||
PDF
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -686,6 +686,23 @@ class ApiClientV2 {
|
||||
async deleteTranslation(taskId: string, lang: string): Promise<void> {
|
||||
await this.client.delete(`/translate/${taskId}/translations/${lang}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download translated PDF with layout preservation
|
||||
*/
|
||||
async downloadTranslatedPdf(taskId: string, lang: string): Promise<void> {
|
||||
const response = await this.client.post(`/translate/${taskId}/pdf`, null, {
|
||||
params: { lang },
|
||||
responseType: 'blob',
|
||||
})
|
||||
|
||||
const blob = new Blob([response.data], { type: 'application/pdf' })
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = `${taskId}_translated_${lang}.pdf`
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(link.href)
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
|
||||
Reference in New Issue
Block a user