feat: harden PDF translation workflow

This commit is contained in:
Your Name
2026-03-30 14:24:18 +02:00
parent 499ebe3ce8
commit 6e8cf6f83a
17 changed files with 1358 additions and 1931 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,31 +2,31 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://dociva.io/blog/how-to-compress-pdf-online</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/blog/convert-images-without-losing-quality</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/blog/ocr-extract-text-from-images</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/blog/merge-split-pdf-files</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/blog/ai-chat-with-pdf-documents</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>

File diff suppressed because it is too large Load Diff

View File

@@ -2,55 +2,55 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://dociva.io/</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://dociva.io/tools</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/about</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.4</priority>
</url>
<url>
<loc>https://dociva.io/contact</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.4</priority>
</url>
<url>
<loc>https://dociva.io/privacy</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://dociva.io/terms</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://dociva.io/pricing</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/blog</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/developers</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>

View File

@@ -2,265 +2,265 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://dociva.io/tools/pdf-to-word</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dociva.io/tools/word-to-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dociva.io/tools/compress-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dociva.io/tools/merge-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://dociva.io/tools/split-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/rotate-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/pdf-to-images</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/images-to-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/watermark-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/protect-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/unlock-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/page-numbers</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/pdf-editor</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/pdf-flowchart</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/pdf-to-excel</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/remove-watermark-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/reorder-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/extract-pages</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/image-converter</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/image-resize</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/compress-image</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/ocr</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/remove-background</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/image-to-svg</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/html-to-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/chat-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/summarize-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/translate-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/extract-tables</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/qr-code</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/video-to-gif</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/word-counter</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/tools/text-cleaner</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/tools/pdf-to-pptx</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/excel-to-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/pptx-to-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/sign-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://dociva.io/tools/crop-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/flatten-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/repair-pdf</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/pdf-metadata</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://dociva.io/tools/image-crop</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/image-rotate-flip</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://dociva.io/tools/barcode-generator</loc>
<lastmod>2026-03-29</lastmod>
<lastmod>2026-03-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Languages } from 'lucide-react';
import { Languages, ShieldCheck, Sparkles } from 'lucide-react';
import FileUploader from '@/components/shared/FileUploader';
import ProgressBar from '@/components/shared/ProgressBar';
import AdSlot from '@/components/layout/AdSlot';
@@ -26,11 +26,22 @@ const LANGUAGES = [
{ value: 'it', label: 'Italiano' },
];
const getLanguageLabel = (value: string) => {
if (!value || value === 'auto') {
return null;
}
return LANGUAGES.find((language) => language.value === value)?.label ?? value;
};
export default function TranslatePdf() {
const { t } = useTranslation();
const [phase, setPhase] = useState<'upload' | 'processing' | 'done'>('upload');
const [sourceLang, setSourceLang] = useState('auto');
const [targetLang, setTargetLang] = useState('en');
const [translation, setTranslation] = useState('');
const [provider, setProvider] = useState('');
const [detectedSourceLanguage, setDetectedSourceLanguage] = useState('');
const {
file, uploadProgress, isUploading, taskId,
@@ -39,7 +50,7 @@ export default function TranslatePdf() {
endpoint: '/pdf-ai/translate',
maxSizeMB: 20,
acceptedTypes: ['pdf'],
extraData: { target_language: targetLang },
extraData: { target_language: targetLang, source_language: sourceLang },
});
const { status, result, error: taskError } = useTaskPolling({
@@ -47,6 +58,8 @@ export default function TranslatePdf() {
onComplete: (r) => {
setPhase('done');
setTranslation(r.translation || '');
setProvider(r.provider || '');
setDetectedSourceLanguage(r.detected_source_language || '');
dispatchRatingPrompt('translate-pdf');
},
onError: () => setPhase('done'),
@@ -63,7 +76,17 @@ export default function TranslatePdf() {
if (id) setPhase('processing');
};
const handleReset = () => { reset(); setPhase('upload'); setTargetLang('en'); setTranslation(''); };
const handleReset = () => {
reset();
setPhase('upload');
setSourceLang('auto');
setTargetLang('en');
setTranslation('');
setProvider('');
setDetectedSourceLanguage('');
};
const resolvedDetectedLanguage = getLanguageLabel(detectedSourceLanguage) || getLanguageLabel(sourceLang);
const schema = generateToolSchema({
name: t('tools.translatePdf.title'),
@@ -103,15 +126,44 @@ export default function TranslatePdf() {
{file && !isUploading && (
<>
<div className="rounded-2xl bg-white p-5 ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700">
<label className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
{t('tools.translatePdf.targetLang')}
</label>
<select value={targetLang} onChange={(e) => setTargetLang(e.target.value)}
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-700 dark:text-slate-200">
{LANGUAGES.map((lang) => (
<option key={lang.value} value={lang.value}>{lang.label}</option>
))}
</select>
<div className="mb-4 flex items-start gap-3 rounded-xl bg-slate-50 p-4 dark:bg-slate-900/60">
<ShieldCheck className="mt-0.5 h-5 w-5 text-emerald-600 dark:text-emerald-400" />
<div>
<p className="text-sm font-semibold text-slate-900 dark:text-slate-100">
{t('tools.translatePdf.engineTitle')}
</p>
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">
{t('tools.translatePdf.engineDescription')}
</p>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2">
<div>
<label className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
{t('tools.translatePdf.sourceLang')}
</label>
<select value={sourceLang} onChange={(e) => setSourceLang(e.target.value)}
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-700 dark:text-slate-200">
<option value="auto">{t('tools.translatePdf.autoDetect')}</option>
{LANGUAGES.map((lang) => (
<option key={`source-${lang.value}`} value={lang.value}>{lang.label}</option>
))}
</select>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-300">
{t('tools.translatePdf.targetLang')}
</label>
<select value={targetLang} onChange={(e) => setTargetLang(e.target.value)}
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-700 dark:text-slate-200">
{LANGUAGES.map((lang) => (
<option key={lang.value} value={lang.value}>{lang.label}</option>
))}
</select>
</div>
</div>
</div>
<button onClick={handleUpload} className="btn-primary w-full">
{t('tools.translatePdf.shortDesc')}
@@ -122,11 +174,39 @@ export default function TranslatePdf() {
)}
{phase === 'processing' && !result && (
<ProgressBar state={status?.state || 'PENDING'} message={status?.progress} />
<div className="space-y-4">
<ProgressBar state={status?.state || 'PENDING'} message={status?.progress} />
<div className="rounded-xl bg-white p-4 ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700">
<div className="flex items-start gap-3">
<Sparkles className="mt-0.5 h-5 w-5 text-purple-600 dark:text-purple-400" />
<p className="text-sm text-slate-600 dark:text-slate-400">
{t('tools.translatePdf.processingHint')}
</p>
</div>
</div>
</div>
)}
{phase === 'done' && translation && (
<div className="space-y-4">
<div className="grid gap-3 sm:grid-cols-2">
<div className="rounded-xl bg-white p-4 ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700">
<p className="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
{t('tools.translatePdf.sourceDetected')}
</p>
<p className="mt-1 text-sm font-medium text-slate-900 dark:text-slate-100">
{resolvedDetectedLanguage || t('tools.translatePdf.autoDetect')}
</p>
</div>
<div className="rounded-xl bg-white p-4 ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700">
<p className="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
{t('tools.translatePdf.translationEngine')}
</p>
<p className="mt-1 text-sm font-medium text-slate-900 dark:text-slate-100">
{provider || 'auto'}
</p>
</div>
</div>
<div className="rounded-2xl bg-white p-6 ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700">
<h3 className="mb-3 text-sm font-semibold text-slate-700 dark:text-slate-300">
{t('tools.translatePdf.resultTitle')}

View File

@@ -827,9 +827,16 @@
},
"translatePdf": {
"title": "ترجمة PDF",
"description": "ترجم محتوى مستند PDF إلى أي لغة باستخدام الذكاء الاصطناعي.",
"description": "ترجم ملفات PDF عبر مسار ترجمة احترافي مع fallback تلقائي وتعامل أفضل مع الملفات الممسوحة ضوئياً.",
"shortDesc": "ترجمة PDF",
"sourceLang": "لغة المصدر",
"targetLang": "اللغة المستهدفة",
"autoDetect": "اكتشاف تلقائي",
"engineTitle": "ترجمة مستندات بجاهزية إنتاجية",
"engineDescription": "يتم إرسال الملف أولاً إلى مزود ترجمة احترافي، ثم يتم التحويل تلقائياً إلى مسار AI فقط عند الحاجة. هذا يقلل مشاكل الضغط ويحسن ثبات النتيجة.",
"processingHint": "قد تتم ترجمة المستندات الكبيرة على عدة أجزاء مع retries وfallback بين المزودات. اترك الصفحة مفتوحة حتى يكتمل الطلب.",
"sourceDetected": "لغة المصدر المكتشفة",
"translationEngine": "محرك الترجمة",
"resultTitle": "الترجمة"
},
"tableExtractor": {

View File

@@ -827,9 +827,16 @@
},
"translatePdf": {
"title": "Translate PDF",
"description": "Translate your PDF document content to any language using AI.",
"description": "Translate PDF documents with a premium translation pipeline, automatic fallback, and better handling for scanned files.",
"shortDesc": "Translate PDF",
"sourceLang": "Source Language",
"targetLang": "Target Language",
"autoDetect": "Auto detect",
"engineTitle": "Production-grade document translation",
"engineDescription": "Your file is translated with a premium translation provider first, then automatically falls back to AI only if needed. This reduces high-demand failures and improves consistency.",
"processingHint": "Large documents may be translated in multiple chunks with retries and provider fallback. Keep this page open until the job completes.",
"sourceDetected": "Detected source",
"translationEngine": "Translation engine",
"resultTitle": "Translation"
},
"tableExtractor": {

View File

@@ -827,9 +827,16 @@
},
"translatePdf": {
"title": "Traduire un PDF",
"description": "Traduisez le contenu de votre document PDF dans n'importe quelle langue grâce à l'IA.",
"description": "Traduisez vos PDF avec un pipeline premium, un fallback automatique et une meilleure prise en charge des fichiers scannés.",
"shortDesc": "Traduire le PDF",
"sourceLang": "Langue source",
"targetLang": "Langue cible",
"autoDetect": "Détection automatique",
"engineTitle": "Traduction documentaire de niveau production",
"engineDescription": "Votre fichier passe d'abord par un fournisseur de traduction premium, puis bascule automatiquement vers l'IA seulement si nécessaire. Cela réduit les erreurs de forte demande et améliore la stabilité.",
"processingHint": "Les documents volumineux peuvent être traduits en plusieurs segments avec retries et fallback entre fournisseurs. Laissez cette page ouverte jusqu'à la fin du traitement.",
"sourceDetected": "Source détectée",
"translationEngine": "Moteur de traduction",
"resultTitle": "Traduction"
},
"tableExtractor": {

View File

@@ -237,6 +237,10 @@ export interface TaskResult {
summary?: string;
translation?: string;
target_language?: string;
source_language?: string;
detected_source_language?: string;
provider?: string;
chunks_translated?: number;
pages_analyzed?: number;
// Table extraction fields
tables?: Array<{ page: number; table_index: number; headers: string[]; rows: string[][] }>;