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:
egg
2025-12-02 12:33:31 +08:00
parent 8d9b69ba93
commit a07aad96b3
15 changed files with 2663 additions and 2 deletions

View File

@@ -0,0 +1,91 @@
# Design: Add Translated PDF Export
## Context
The Tool_OCR project has implemented document translation using DIFY AI API, producing JSON files with translated content mapped by element_id. The existing PDF generator (`PDFGeneratorService`) can generate layout-preserving PDFs from UnifiedDocument but has no translation support.
**Key Constraint**: The PDF generator uses element_id to position content. Translation JSON uses the same element_id mapping, making merging straightforward.
## Goals / Non-Goals
**Goals:**
- Generate PDF with translated text preserving original layout
- Support all processing tracks (DIRECT, OCR, HYBRID)
- Maintain backward compatibility with existing PDF export
- Support table cell translation rendering
**Non-Goals:**
- Font optimization for target language scripts
- Interactive editing of translations
- Bilingual PDF output (original + translated side-by-side)
## Decisions
### Decision 1: Translation Merge Strategy
**What**: Merge translation data into UnifiedDocument in-memory before PDF generation.
**Why**: This approach:
- Reuses existing PDF rendering logic unchanged
- Keeps translation and PDF generation decoupled
- Allows easy testing of merged document
**Implementation**:
```python
def apply_translations(
unified_doc: UnifiedDocument,
translations: Dict[str, Any]
) -> UnifiedDocument:
"""Apply translations to UnifiedDocument, returning modified copy"""
doc_copy = unified_doc.copy(deep=True)
for page in doc_copy.pages:
for element in page.elements:
if element.element_id in translations:
translation = translations[element.element_id]
if isinstance(translation, str):
element.content = translation
elif isinstance(translation, dict) and 'cells' in translation:
# Handle table cells
apply_table_translation(element, translation)
return doc_copy
```
**Alternatives considered**:
- Modify PDF generator to accept translations directly - Would require significant refactoring
- Generate overlay PDF with translations - Complex positioning logic
### Decision 2: API Endpoint Design
**What**: Add `POST /api/v2/translate/{task_id}/pdf?lang={target_lang}` endpoint.
**Why**:
- Consistent with existing `/translate/{task_id}` pattern
- POST allows future expansion for PDF options
- Clear separation from existing `/download/pdf` endpoint
**Response**: Binary PDF file with `application/pdf` content-type.
### Decision 3: Frontend Integration
**What**: Add conditional "Download Translated PDF" button in TaskDetailPage.
**Why**:
- Only show when translation is complete
- Use existing download pattern from PDF export
## Risks / Trade-offs
| Risk | Mitigation |
|------|------------|
| Large documents may timeout | Use existing async pattern, add progress tracking |
| Font rendering for CJK scripts | Rely on existing NotoSansSC font registration |
| Translation missing for some elements | Use original content as fallback |
## Migration Plan
No migration needed - additive feature only.
## Open Questions
1. Should we support downloading multiple translated PDFs in batch?
2. Should translated PDF filename include source language as well as target?

View File

@@ -0,0 +1,29 @@
# Change: Add Translated PDF Export
## Why
The current translation feature produces JSON output files (`{filename}_translated_{lang}.json`) but does not support generating translated PDFs. Users need to download translated documents in PDF format with the original layout preserved but with translated text content. This is essential for document localization workflows where the final deliverable must be a properly formatted PDF.
## What Changes
- **PDF Generator**: Add translation parameter support to `PDFGeneratorService`
- **Translation Merger**: Create logic to merge translation JSON with UnifiedDocument
- **API Endpoint**: Add `POST /api/v2/translate/{task_id}/pdf` endpoint
- **Frontend UI**: Add "Download Translated PDF" button in TaskDetailPage
- **Batch Translation Enhancement**: Improve batch response parsing for edge cases
## Impact
- **Affected specs**: `translation`, `result-export`
- **Affected code**:
- `backend/app/services/pdf_generator_service.py` - Add translation rendering
- `backend/app/services/translation_service.py` - Add PDF generation integration
- `backend/app/routers/translate.py` - Add PDF download endpoint
- `frontend/src/pages/TaskDetailPage.tsx` - Add PDF download button
- `frontend/src/services/apiV2.ts` - Add PDF download API method
## Non-Goals
- Editing translated text before PDF export (future feature)
- Supporting formats other than PDF (Excel, Word)
- Font substitution for different target languages

View File

@@ -0,0 +1,55 @@
## ADDED Requirements
### Requirement: Translated PDF Export API
The system SHALL expose an API endpoint for downloading translated documents as PDF files.
#### Scenario: Download translated PDF via API
- **GIVEN** a task with completed translation to English
- **WHEN** POST request to `/api/v2/translate/{task_id}/pdf?lang=en`
- **THEN** system returns PDF file with translated content
- **AND** Content-Type is `application/pdf`
- **AND** Content-Disposition suggests filename like `{task_id}_translated_en.pdf`
#### Scenario: Download translated PDF with layout preservation
- **WHEN** user downloads translated PDF
- **THEN** the PDF maintains original document layout
- **AND** text positions match original document coordinates
- **AND** images and tables appear at original positions
#### Scenario: Invalid language parameter
- **GIVEN** a task with translation only to English
- **WHEN** user requests PDF with `lang=ja` (Japanese)
- **THEN** system returns 404 Not Found
- **AND** response includes available languages in error message
#### Scenario: Task not found
- **GIVEN** non-existent task_id
- **WHEN** user requests translated PDF
- **THEN** system returns 404 Not Found
---
### Requirement: Frontend Translated PDF Download
The frontend SHALL provide UI controls for downloading translated PDFs.
#### Scenario: Show download button when translation complete
- **GIVEN** a task with translation status "completed"
- **WHEN** user views TaskDetailPage
- **THEN** page displays "Download Translated PDF" button
- **AND** button shows target language (e.g., "Download Translated PDF (English)")
#### Scenario: Hide download button when no translation
- **GIVEN** a task without any completed translations
- **WHEN** user views TaskDetailPage
- **THEN** "Download Translated PDF" button is not shown
#### Scenario: Download progress indication
- **GIVEN** user clicks "Download Translated PDF" button
- **WHEN** PDF generation is in progress
- **THEN** button shows loading state
- **AND** prevents double-click
- **WHEN** download completes
- **THEN** browser downloads PDF file
- **AND** button returns to normal state

View File

@@ -0,0 +1,72 @@
## ADDED Requirements
### Requirement: Translated PDF Generation
The system SHALL support generating PDF files with translated content while preserving the original document layout.
#### Scenario: Generate translated PDF from Direct track document
- **GIVEN** a completed translation for a Direct track processed document
- **WHEN** user requests translated PDF via `POST /api/v2/translate/{task_id}/pdf?lang={target_lang}`
- **THEN** the system loads the translation JSON file
- **AND** merges translations with UnifiedDocument by element_id
- **AND** generates PDF with translated text at original positions
- **AND** returns PDF file with Content-Type `application/pdf`
#### Scenario: Generate translated PDF from OCR track document
- **GIVEN** a completed translation for an OCR track processed document
- **WHEN** user requests translated PDF
- **THEN** the system generates PDF preserving all OCR layout information
- **AND** replaces original text with translated content
- **AND** maintains table structure with translated cell content
#### Scenario: Handle missing translations gracefully
- **GIVEN** a translation JSON missing some element_id entries
- **WHEN** generating translated PDF
- **THEN** the system uses original content for missing translations
- **AND** logs warning for each fallback
- **AND** completes PDF generation successfully
#### Scenario: Translated PDF for incomplete translation
- **GIVEN** a task with translation status "pending" or "translating"
- **WHEN** user requests translated PDF
- **THEN** the system returns 400 Bad Request
- **AND** includes error message indicating translation not complete
#### Scenario: Translated PDF for non-existent translation
- **GIVEN** a task that has not been translated to requested language
- **WHEN** user requests translated PDF with `lang=fr`
- **THEN** the system returns 404 Not Found
- **AND** includes error message indicating no translation for language
---
### Requirement: Translation Merge Service
The system SHALL provide a service to merge translation data with UnifiedDocument.
#### Scenario: Merge text element translations
- **GIVEN** a UnifiedDocument with text elements
- **AND** a translation JSON with matching element_ids
- **WHEN** applying translations
- **THEN** the system replaces content field for each matched element
- **AND** preserves all other element properties (bounding_box, style_info, etc.)
#### Scenario: Merge table cell translations
- **GIVEN** a UnifiedDocument containing table elements
- **AND** a translation JSON with table_cell translations like:
```json
{
"table_1_0": {
"cells": [{"row": 0, "col": 0, "content": "Translated"}]
}
}
```
- **WHEN** applying translations
- **THEN** the system updates cell content at matching row/col positions
- **AND** preserves cell structure and styling
#### Scenario: Non-destructive merge operation
- **GIVEN** a UnifiedDocument
- **WHEN** applying translations
- **THEN** the system creates a modified copy
- **AND** original UnifiedDocument remains unchanged

View File

@@ -0,0 +1,40 @@
# Tasks: Add Translated PDF Export
## 1. Backend - Translation Merger Service
- [x] 1.1 Create `apply_translations()` function in `translation_service.py`
- [x] 1.2 Implement table cell translation merging logic
- [x] 1.3 Add unit tests for translation merging
## 2. Backend - PDF Generator Enhancement
- [x] 2.1 Add `generate_translated_pdf()` method to `PDFGeneratorService`
- [x] 2.2 Load translation JSON and merge with UnifiedDocument
- [x] 2.3 Handle missing translations gracefully (fallback to original)
- [x] 2.4 Add unit tests for translated PDF generation
## 3. Backend - API Endpoint
- [x] 3.1 Add `POST /api/v2/translate/{task_id}/pdf` endpoint in `translate.py`
- [x] 3.2 Validate task exists and has completed translation
- [x] 3.3 Return appropriate errors (404 if no translation, 400 if task not complete)
- [x] 3.4 Add endpoint tests
## 4. Frontend - UI Integration
- [x] 4.1 Add `downloadTranslatedPdf()` method to `apiV2.ts`
- [x] 4.2 Add "Download Translated PDF" button in `TaskDetailPage.tsx`
- [x] 4.3 Show button only when translation status is "completed"
- [x] 4.4 Add loading state during PDF generation
## 5. Testing & Validation
- [x] 5.1 End-to-end test: translate document then download PDF
- [x] 5.2 Test with Direct track document
- [x] 5.3 Test with OCR track document
- [x] 5.4 Test with document containing tables
## 6. Documentation
- [ ] 6.1 Update API documentation with new endpoint
- [ ] 6.2 Add usage example in README if applicable