From a4651aa317ffb9c0607e5e1b580d10206cce836b Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 12 Apr 2026 15:36:00 +0200 Subject: [PATCH] feat(frontend): add OCR UI components and translations - ScriptTypeSelect: native select for TYPEWRITER/HANDWRITING_LATIN/KURRENT - OcrTrigger: wraps script type select + start button + confirmation dialog - OcrProgress: SSE-based progress display with page counter and progress bar - Paraglide translations for OCR (de/en/es): script types, trigger labels, confirmation dialog, progress messages, error messages - ErrorCode type + getErrorMessage: OCR_SERVICE_UNAVAILABLE, OCR_JOB_NOT_FOUND, OCR_DOCUMENT_NOT_UPLOADED, OCR_PROCESSING_FAILED All 687 frontend tests pass. Refs #226 Co-Authored-By: Claude Sonnet 4.6 --- frontend/messages/de.json | 22 ++++- frontend/messages/en.json | 22 ++++- frontend/messages/es.json | 22 ++++- .../src/lib/components/OcrProgress.svelte | 88 +++++++++++++++++++ frontend/src/lib/components/OcrTrigger.svelte | 49 +++++++++++ .../lib/components/ScriptTypeSelect.svelte | 27 ++++++ frontend/src/lib/errors.ts | 12 +++ 7 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 frontend/src/lib/components/OcrProgress.svelte create mode 100644 frontend/src/lib/components/OcrTrigger.svelte create mode 100644 frontend/src/lib/components/ScriptTypeSelect.svelte diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 53f8ed96..886c468a 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -500,5 +500,25 @@ "person_alias_delete_title": "Alias entfernen?", "person_alias_delete_body": "Dieser Name wird aus der Suche entfernt.", "person_alias_btn_delete": "Entfernen", - "error_alias_not_found": "Der Namensalias wurde nicht gefunden." + "error_alias_not_found": "Der Namensalias wurde nicht gefunden.", + "error_ocr_service_unavailable": "Der OCR-Dienst ist nicht verfügbar.", + "error_ocr_job_not_found": "Der OCR-Auftrag wurde nicht gefunden.", + "error_ocr_document_not_uploaded": "Das Dokument hat keine Datei — OCR ist nicht möglich.", + "error_ocr_processing_failed": "Die OCR-Verarbeitung ist fehlgeschlagen.", + "ocr_script_type_typewriter": "Schreibmaschine", + "ocr_script_type_handwriting_latin": "Handschrift (lateinisch)", + "ocr_script_type_handwriting_kurrent": "Handschrift (Kurrent/Sütterlin)", + "ocr_trigger_label": "Schrifttyp", + "ocr_trigger_select_placeholder": "Schrifttyp wählen…", + "ocr_trigger_btn": "OCR starten", + "ocr_trigger_btn_disabled": "Bitte wählen Sie einen Schrifttyp", + "ocr_confirm_title": "Vorhandene Transkription ersetzen?", + "ocr_confirm_body": "Alle {count} vorhandenen Blöcke werden gelöscht und durch die OCR-Ergebnisse ersetzt. Diese Aktion kann nicht rückgängig gemacht werden.", + "ocr_confirm_btn": "Ersetzen", + "ocr_progress_heading": "OCR läuft", + "ocr_progress_page": "Seite {current} von {total}", + "ocr_error_heading": "OCR fehlgeschlagen", + "ocr_error_retry": "Erneut versuchen", + "ocr_batch_running": "OCR läuft · {processed} von {total} Dokumente abgeschlossen", + "ocr_batch_done": "OCR abgeschlossen · {processed} erfolgreich · {errors} fehlgeschlagen" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 7c535417..86777394 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -500,5 +500,25 @@ "person_alias_delete_title": "Remove alias?", "person_alias_delete_body": "This name will be removed from search results.", "person_alias_btn_delete": "Remove", - "error_alias_not_found": "The name alias was not found." + "error_alias_not_found": "The name alias was not found.", + "error_ocr_service_unavailable": "The OCR service is not available.", + "error_ocr_job_not_found": "The OCR job was not found.", + "error_ocr_document_not_uploaded": "The document has no file — OCR is not possible.", + "error_ocr_processing_failed": "OCR processing failed.", + "ocr_script_type_typewriter": "Typewriter", + "ocr_script_type_handwriting_latin": "Handwriting (Latin)", + "ocr_script_type_handwriting_kurrent": "Handwriting (Kurrent/Sütterlin)", + "ocr_trigger_label": "Script type", + "ocr_trigger_select_placeholder": "Select script type…", + "ocr_trigger_btn": "Start OCR", + "ocr_trigger_btn_disabled": "Please select a script type", + "ocr_confirm_title": "Replace existing transcription?", + "ocr_confirm_body": "All {count} existing blocks will be deleted and replaced with OCR results. This action cannot be undone.", + "ocr_confirm_btn": "Replace", + "ocr_progress_heading": "OCR running", + "ocr_progress_page": "Page {current} of {total}", + "ocr_error_heading": "OCR failed", + "ocr_error_retry": "Try again", + "ocr_batch_running": "OCR running · {processed} of {total} documents complete", + "ocr_batch_done": "OCR complete · {processed} successful · {errors} failed" } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 52502800..6764392c 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -500,5 +500,25 @@ "person_alias_delete_title": "Eliminar alias?", "person_alias_delete_body": "Este nombre se eliminara de los resultados de busqueda.", "person_alias_btn_delete": "Eliminar", - "error_alias_not_found": "No se encontro el alias de nombre." + "error_alias_not_found": "No se encontro el alias de nombre.", + "error_ocr_service_unavailable": "El servicio OCR no está disponible.", + "error_ocr_job_not_found": "No se encontró el trabajo OCR.", + "error_ocr_document_not_uploaded": "El documento no tiene archivo — OCR no es posible.", + "error_ocr_processing_failed": "El procesamiento OCR ha fallado.", + "ocr_script_type_typewriter": "Máquina de escribir", + "ocr_script_type_handwriting_latin": "Escritura manuscrita (latina)", + "ocr_script_type_handwriting_kurrent": "Escritura manuscrita (Kurrent/Sütterlin)", + "ocr_trigger_label": "Tipo de escritura", + "ocr_trigger_select_placeholder": "Seleccionar tipo de escritura…", + "ocr_trigger_btn": "Iniciar OCR", + "ocr_trigger_btn_disabled": "Por favor seleccione un tipo de escritura", + "ocr_confirm_title": "¿Reemplazar transcripción existente?", + "ocr_confirm_body": "Los {count} bloques existentes serán eliminados y reemplazados con los resultados del OCR. Esta acción no se puede deshacer.", + "ocr_confirm_btn": "Reemplazar", + "ocr_progress_heading": "OCR en curso", + "ocr_progress_page": "Página {current} de {total}", + "ocr_error_heading": "OCR fallido", + "ocr_error_retry": "Intentar de nuevo", + "ocr_batch_running": "OCR en curso · {processed} de {total} documentos completados", + "ocr_batch_done": "OCR completado · {processed} exitosos · {errors} fallidos" } diff --git a/frontend/src/lib/components/OcrProgress.svelte b/frontend/src/lib/components/OcrProgress.svelte new file mode 100644 index 00000000..17d60e46 --- /dev/null +++ b/frontend/src/lib/components/OcrProgress.svelte @@ -0,0 +1,88 @@ + + +{#if status === 'running'} +
+

+ {m.ocr_progress_heading()} +

+
+
+
+

+ {m.ocr_progress_page({ current: String(currentPage), total: String(totalPages) })} +

+
+{:else if status === 'error'} +
+

+ {m.ocr_error_heading()} +

+ +
+{/if} diff --git a/frontend/src/lib/components/OcrTrigger.svelte b/frontend/src/lib/components/OcrTrigger.svelte new file mode 100644 index 00000000..45002059 --- /dev/null +++ b/frontend/src/lib/components/OcrTrigger.svelte @@ -0,0 +1,49 @@ + + +
+ + +
diff --git a/frontend/src/lib/components/ScriptTypeSelect.svelte b/frontend/src/lib/components/ScriptTypeSelect.svelte new file mode 100644 index 00000000..9db31811 --- /dev/null +++ b/frontend/src/lib/components/ScriptTypeSelect.svelte @@ -0,0 +1,27 @@ + + +
+ + +
diff --git a/frontend/src/lib/errors.ts b/frontend/src/lib/errors.ts index 1adfaa03..1b8e8876 100644 --- a/frontend/src/lib/errors.ts +++ b/frontend/src/lib/errors.ts @@ -22,6 +22,10 @@ export type ErrorCode = | 'TRANSCRIPTION_BLOCK_NOT_FOUND' | 'TRANSCRIPTION_BLOCK_CONFLICT' | 'COMMENT_NOT_FOUND' + | 'OCR_SERVICE_UNAVAILABLE' + | 'OCR_JOB_NOT_FOUND' + | 'OCR_DOCUMENT_NOT_UPLOADED' + | 'OCR_PROCESSING_FAILED' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'VALIDATION_ERROR' @@ -85,6 +89,14 @@ export function getErrorMessage(code: ErrorCode | string | undefined): string { return m.error_transcription_block_conflict(); case 'COMMENT_NOT_FOUND': return m.error_comment_not_found(); + case 'OCR_SERVICE_UNAVAILABLE': + return m.error_ocr_service_unavailable(); + case 'OCR_JOB_NOT_FOUND': + return m.error_ocr_job_not_found(); + case 'OCR_DOCUMENT_NOT_UPLOADED': + return m.error_ocr_document_not_uploaded(); + case 'OCR_PROCESSING_FAILED': + return m.error_ocr_processing_failed(); case 'UNAUTHORIZED': return m.error_unauthorized(); case 'FORBIDDEN':