fix: merge main branch and renumber migration 037→040
Resolve all merge conflicts between our automation feature branch and current main (v0.163.0, 920 commits ahead). Conflicts resolved: - app/api/__init__.py: add automation_router alongside main's new routers (classification_rules, qr_auth, sessions, system_reset) - app/config.py: add main's new settings (dropbox_use_global_credentials, factory_reset_on_startup, enable_factory_reset) - app/models.py: add main's new models (ClassificationRuleModel, UserSession, QRLoginChallenge, SharePoint integration type) - app/utils/settings_service.py: merge automation_hooks_enabled with main's new metadata entries - docs/API.md: merge automation API docs with main's classification rules docs - docs/ConfigurationGuide.md: add factory reset settings - tests/conftest.py: import both AutomationHook and new main models Migration renumbered: - 037_add_automation_hooks → 040_add_automation_hooks - down_revision: 039_add_classification_rules (was 036_add_document_translation_fields) - Chain: 036 → 037 → 038 → 039 → 040 (automation hooks) For all non-automation files with conflicts, main's version was taken since our branch did not modify those files (conflicts were from a stale prior merge). Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/cb62f012-3b69-4415-835e-3857ce3e9f45
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"common": {
|
||||
"retry": "Erneut versuchen",
|
||||
"cancel": "Abbrechen",
|
||||
"back": "Zurück",
|
||||
"error": "Fehler",
|
||||
"loading": "Laden…",
|
||||
"search": "Suchen",
|
||||
"clear_search": "Suche löschen"
|
||||
},
|
||||
"welcome": {
|
||||
"tagline": "Intelligente Dokumentenverarbeitung",
|
||||
"description": "Dokumente einlesen, OCR durchführen, Metadaten mit KI extrahieren und Dateien in Ihren Cloud-Speicher leiten – alles in einer nahtlosen Pipeline.",
|
||||
"get_started": "Loslegen",
|
||||
"hint": "Verbinden Sie sich mit Ihrem selbst gehosteten oder Cloud-DocuElevate-Server.",
|
||||
"feature_ocr_title": "OCR & Texterkennung",
|
||||
"feature_ocr_desc": "Gescannte PDFs und Bilder automatisch in durchsuchbaren Text umwandeln.",
|
||||
"feature_ai_title": "KI-Metadatenextraktion",
|
||||
"feature_ai_desc": "KI klassifiziert Dokumente und extrahiert Schlüsselfelder wie Datum, Beträge und Betreff.",
|
||||
"feature_cloud_title": "Multi-Cloud-Speicher",
|
||||
"feature_cloud_desc": "Verarbeitete Dateien an Dropbox, Google Drive, OneDrive, S3, Nextcloud und mehr weiterleiten."
|
||||
},
|
||||
"login": {
|
||||
"server_url": "Server-URL",
|
||||
"server_url_placeholder": "https://ihr-docuelevate-server.com",
|
||||
"sign_in_sso": "Mit SSO anmelden",
|
||||
"scan_qr": "📱 QR-Code scannen zum Anmelden",
|
||||
"hint": "Melden Sie sich per SSO an oder scannen Sie einen QR-Code aus der Web-App.",
|
||||
"back": "← Zurück",
|
||||
"or": "oder",
|
||||
"server_url_required": "Server-URL erforderlich",
|
||||
"server_url_required_msg": "Bitte geben Sie die URL Ihres DocuElevate-Servers ein.",
|
||||
"invalid_url": "Ungültige URL",
|
||||
"invalid_url_msg": "Die Server-URL muss mit http:// oder https:// beginnen",
|
||||
"sign_in_failed": "Anmeldung fehlgeschlagen",
|
||||
"qr_login_failed": "QR-Anmeldung fehlgeschlagen"
|
||||
},
|
||||
"upload": {
|
||||
"camera": "Kamera",
|
||||
"photos": "Fotos",
|
||||
"files": "Dateien",
|
||||
"camera_access_title": "Kamerazugriff erforderlich",
|
||||
"camera_access_msg": "Bitte erlauben Sie den Kamerazugriff in den Einstellungen, um Dokumente aufzunehmen.",
|
||||
"photo_access_title": "Fotobibliothek-Zugriff erforderlich",
|
||||
"photo_access_msg": "Bitte erlauben Sie den Zugriff auf die Fotobibliothek in den Einstellungen.",
|
||||
"file_picker_error": "Dateiauswahl-Fehler",
|
||||
"file_picker_error_msg": "Dateiauswahl konnte nicht geöffnet werden",
|
||||
"empty_title": "Tippen Sie auf Kamera, Fotos oder Dateien, um ein Dokument hochzuladen.",
|
||||
"empty_hint": "Sie können auch Dateien aus anderen Apps direkt an DocuElevate senden.",
|
||||
"sign_in_required": "Bitte melden Sie sich an, um Dokumente hochzuladen.",
|
||||
"status_queued": "In der Warteschlange…",
|
||||
"status_processing": "Wird verarbeitet…",
|
||||
"status_completed": "Verarbeitet",
|
||||
"status_failed": "Verarbeitung fehlgeschlagen",
|
||||
"status_duplicate": "Duplikat – bereits verarbeitet",
|
||||
"tap_retry": "Zum Wiederholen tippen",
|
||||
"retry_title": "Upload wiederholen",
|
||||
"retry_msg": "Möchten Sie den Upload von \"{filename}\" wiederholen?",
|
||||
"capture_label": "Dokument mit Kamera aufnehmen",
|
||||
"photo_label": "Foto aus der Bibliothek auswählen",
|
||||
"file_label": "Datei vom Gerät auswählen"
|
||||
},
|
||||
"files": {
|
||||
"title": "Meine Dokumente",
|
||||
"search_placeholder": "Dokumente durchsuchen…",
|
||||
"empty_title": "Noch keine Dokumente.",
|
||||
"empty_hint": "Laden Sie ein Dokument über den Upload-Tab hoch.",
|
||||
"search_empty": "Keine Dokumente gefunden.",
|
||||
"search_empty_hint": "Versuchen Sie einen anderen Suchbegriff.",
|
||||
"view_details": "Details für {filename} anzeigen"
|
||||
},
|
||||
"file_detail": {
|
||||
"title": "Dateidetails",
|
||||
"back": "Zurück zu Dateien",
|
||||
"file_size": "Dateigröße",
|
||||
"mime_type": "MIME-Typ",
|
||||
"uploaded": "Hochgeladen",
|
||||
"file_hash": "Datei-Hash",
|
||||
"last_step": "Letzter Schritt",
|
||||
"total_steps": "Gesamtschritte",
|
||||
"processing_log": "Verarbeitungsprotokoll",
|
||||
"no_logs": "Noch keine Verarbeitungsprotokolle.",
|
||||
"file_not_found": "Datei nicht gefunden"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
"not_signed_in": "Nicht angemeldet",
|
||||
"connection": "Verbindung",
|
||||
"server": "Server",
|
||||
"user_id": "Benutzer-ID",
|
||||
"legal": "Rechtliches",
|
||||
"privacy_policy": "Datenschutzrichtlinie",
|
||||
"terms_of_service": "Nutzungsbedingungen",
|
||||
"imprint": "Impressum",
|
||||
"sign_out": "Abmelden",
|
||||
"sign_out_title": "Abmelden",
|
||||
"sign_out_msg": "Möchten Sie sich wirklich abmelden?",
|
||||
"delete_account": "Konto löschen",
|
||||
"delete_account_title": "Konto löschen",
|
||||
"delete_account_msg": "Dadurch werden Ihr Konto und alle zugehörigen Daten dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"could_not_open": "Konnte {page} nicht öffnen. Bitte versuchen Sie es erneut.",
|
||||
"admin": "Admin",
|
||||
"settings": "Einstellungen",
|
||||
"language": "Sprache"
|
||||
},
|
||||
"legal": {
|
||||
"privacy_policy": "Datenschutz",
|
||||
"terms": "AGB",
|
||||
"imprint": "Impressum"
|
||||
},
|
||||
"tabs": {
|
||||
"upload": "Hochladen",
|
||||
"files": "Dateien",
|
||||
"profile": "Profil"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"common": {
|
||||
"retry": "Retry",
|
||||
"cancel": "Cancel",
|
||||
"back": "Back",
|
||||
"error": "Error",
|
||||
"loading": "Loading…",
|
||||
"search": "Search",
|
||||
"clear_search": "Clear search"
|
||||
},
|
||||
"welcome": {
|
||||
"tagline": "Intelligent Document Processing",
|
||||
"description": "Ingest documents, run OCR, extract metadata with AI, and route files to your cloud storage — all in one seamless pipeline.",
|
||||
"get_started": "Get Started",
|
||||
"hint": "Connect to your self-hosted or cloud DocuElevate server.",
|
||||
"feature_ocr_title": "OCR & Text Extraction",
|
||||
"feature_ocr_desc": "Convert scanned PDFs and images into fully searchable text automatically.",
|
||||
"feature_ai_title": "AI Metadata Extraction",
|
||||
"feature_ai_desc": "AI classifies documents and pulls out key fields like dates, amounts, and subjects.",
|
||||
"feature_cloud_title": "Multi-Cloud Storage",
|
||||
"feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more."
|
||||
},
|
||||
"login": {
|
||||
"server_url": "Server URL",
|
||||
"server_url_placeholder": "https://your-docuelevate-server.com",
|
||||
"sign_in_sso": "Sign in with SSO",
|
||||
"scan_qr": "📱 Scan QR Code to Login",
|
||||
"hint": "Sign in via SSO or scan a QR code from the web app.",
|
||||
"back": "← Back",
|
||||
"or": "or",
|
||||
"server_url_required": "Server URL required",
|
||||
"server_url_required_msg": "Please enter the URL of your DocuElevate server.",
|
||||
"invalid_url": "Invalid URL",
|
||||
"invalid_url_msg": "The server URL must start with http:// or https://",
|
||||
"sign_in_failed": "Sign-in failed",
|
||||
"qr_login_failed": "QR Login Failed"
|
||||
},
|
||||
"upload": {
|
||||
"camera": "Camera",
|
||||
"photos": "Photos",
|
||||
"files": "Files",
|
||||
"camera_access_title": "Camera access required",
|
||||
"camera_access_msg": "Please grant camera access in Settings to capture documents.",
|
||||
"photo_access_title": "Photo library access required",
|
||||
"photo_access_msg": "Please grant photo library access in Settings to select images.",
|
||||
"file_picker_error": "File picker error",
|
||||
"file_picker_error_msg": "Could not open file picker",
|
||||
"empty_title": "Tap Camera, Photos, or Files to upload a document.",
|
||||
"empty_hint": "You can also share files from other apps directly to DocuElevate.",
|
||||
"sign_in_required": "Please sign in to upload documents.",
|
||||
"status_queued": "Queued for processing…",
|
||||
"status_processing": "Processing…",
|
||||
"status_completed": "Processed",
|
||||
"status_failed": "Processing failed",
|
||||
"status_duplicate": "Duplicate – already processed",
|
||||
"tap_retry": "Tap to retry",
|
||||
"retry_title": "Retry Upload",
|
||||
"retry_msg": "Do you want to retry uploading \"{filename}\"?",
|
||||
"capture_label": "Capture document with camera",
|
||||
"photo_label": "Select photo from library",
|
||||
"file_label": "Pick file from device"
|
||||
},
|
||||
"files": {
|
||||
"title": "My Documents",
|
||||
"search_placeholder": "Search documents…",
|
||||
"empty_title": "No documents yet.",
|
||||
"empty_hint": "Upload a document from the Upload tab to get started.",
|
||||
"search_empty": "No documents match your search.",
|
||||
"search_empty_hint": "Try a different search term.",
|
||||
"view_details": "View details for {filename}"
|
||||
},
|
||||
"file_detail": {
|
||||
"title": "File Details",
|
||||
"back": "Back to Files",
|
||||
"file_size": "File Size",
|
||||
"mime_type": "MIME Type",
|
||||
"uploaded": "Uploaded",
|
||||
"file_hash": "File Hash",
|
||||
"last_step": "Last Step",
|
||||
"total_steps": "Total Steps",
|
||||
"processing_log": "Processing Log",
|
||||
"no_logs": "No processing logs yet.",
|
||||
"file_not_found": "File not found"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profile",
|
||||
"not_signed_in": "Not signed in",
|
||||
"connection": "Connection",
|
||||
"server": "Server",
|
||||
"user_id": "User ID",
|
||||
"legal": "Legal",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"terms_of_service": "Terms of Service",
|
||||
"imprint": "Imprint",
|
||||
"sign_out": "Sign out",
|
||||
"sign_out_title": "Sign out",
|
||||
"sign_out_msg": "Are you sure you want to sign out?",
|
||||
"delete_account": "Delete Account",
|
||||
"delete_account_title": "Delete Account",
|
||||
"delete_account_msg": "This will permanently delete your account and all associated data. This action cannot be undone.",
|
||||
"could_not_open": "Could not open the {page}. Please try again.",
|
||||
"admin": "Admin",
|
||||
"settings": "Settings",
|
||||
"language": "Language"
|
||||
},
|
||||
"legal": {
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"terms": "Terms",
|
||||
"imprint": "Imprint"
|
||||
},
|
||||
"tabs": {
|
||||
"upload": "Upload",
|
||||
"files": "Files",
|
||||
"profile": "Profile"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"common": {
|
||||
"retry": "Reintentar",
|
||||
"cancel": "Cancelar",
|
||||
"back": "Atrás",
|
||||
"error": "Error",
|
||||
"loading": "Cargando…",
|
||||
"search": "Buscar",
|
||||
"clear_search": "Borrar búsqueda"
|
||||
},
|
||||
"welcome": {
|
||||
"tagline": "Procesamiento Inteligente de Documentos",
|
||||
"description": "Ingiere documentos, ejecuta OCR, extrae metadatos con IA y envía archivos a tu almacenamiento en la nube — todo en una sola línea de trabajo.",
|
||||
"get_started": "Comenzar",
|
||||
"hint": "Conéctate a tu servidor DocuElevate autoalojado o en la nube.",
|
||||
"feature_ocr_title": "OCR y Extracción de Texto",
|
||||
"feature_ocr_desc": "Convierte PDFs e imágenes escaneadas en texto completamente buscable automáticamente.",
|
||||
"feature_ai_title": "Extracción de Metadatos con IA",
|
||||
"feature_ai_desc": "La IA clasifica documentos y extrae campos clave como fechas, montos y asuntos.",
|
||||
"feature_cloud_title": "Almacenamiento Multi-Nube",
|
||||
"feature_cloud_desc": "Envía archivos procesados a Dropbox, Google Drive, OneDrive, S3, Nextcloud y más."
|
||||
},
|
||||
"login": {
|
||||
"server_url": "URL del Servidor",
|
||||
"server_url_placeholder": "https://tu-servidor-docuelevate.com",
|
||||
"sign_in_sso": "Iniciar sesión con SSO",
|
||||
"scan_qr": "📱 Escanear código QR para iniciar sesión",
|
||||
"hint": "Inicia sesión mediante SSO o escanea un código QR desde la app web.",
|
||||
"back": "← Atrás",
|
||||
"or": "o",
|
||||
"server_url_required": "URL del servidor requerida",
|
||||
"server_url_required_msg": "Por favor ingresa la URL de tu servidor DocuElevate.",
|
||||
"invalid_url": "URL inválida",
|
||||
"invalid_url_msg": "La URL del servidor debe comenzar con http:// o https://",
|
||||
"sign_in_failed": "Error al iniciar sesión",
|
||||
"qr_login_failed": "Error en inicio de sesión QR"
|
||||
},
|
||||
"upload": {
|
||||
"camera": "Cámara",
|
||||
"photos": "Fotos",
|
||||
"files": "Archivos",
|
||||
"camera_access_title": "Acceso a la cámara requerido",
|
||||
"camera_access_msg": "Permite el acceso a la cámara en Ajustes para capturar documentos.",
|
||||
"photo_access_title": "Acceso a la biblioteca de fotos requerido",
|
||||
"photo_access_msg": "Permite el acceso a la biblioteca de fotos en Ajustes para seleccionar imágenes.",
|
||||
"file_picker_error": "Error del selector de archivos",
|
||||
"file_picker_error_msg": "No se pudo abrir el selector de archivos",
|
||||
"empty_title": "Toca Cámara, Fotos o Archivos para subir un documento.",
|
||||
"empty_hint": "También puedes compartir archivos desde otras apps directamente a DocuElevate.",
|
||||
"sign_in_required": "Inicia sesión para subir documentos.",
|
||||
"status_queued": "En cola para procesamiento…",
|
||||
"status_processing": "Procesando…",
|
||||
"status_completed": "Procesado",
|
||||
"status_failed": "Procesamiento fallido",
|
||||
"status_duplicate": "Duplicado – ya procesado",
|
||||
"tap_retry": "Toca para reintentar",
|
||||
"retry_title": "Reintentar Subida",
|
||||
"retry_msg": "¿Deseas reintentar la subida de \"{filename}\"?",
|
||||
"capture_label": "Capturar documento con la cámara",
|
||||
"photo_label": "Seleccionar foto de la biblioteca",
|
||||
"file_label": "Seleccionar archivo del dispositivo"
|
||||
},
|
||||
"files": {
|
||||
"title": "Mis Documentos",
|
||||
"search_placeholder": "Buscar documentos…",
|
||||
"empty_title": "Aún no hay documentos.",
|
||||
"empty_hint": "Sube un documento desde la pestaña Subir para comenzar.",
|
||||
"search_empty": "Ningún documento coincide con tu búsqueda.",
|
||||
"search_empty_hint": "Intenta con otro término de búsqueda.",
|
||||
"view_details": "Ver detalles de {filename}"
|
||||
},
|
||||
"file_detail": {
|
||||
"title": "Detalles del Archivo",
|
||||
"back": "Volver a Archivos",
|
||||
"file_size": "Tamaño",
|
||||
"mime_type": "Tipo MIME",
|
||||
"uploaded": "Subido",
|
||||
"file_hash": "Hash del Archivo",
|
||||
"last_step": "Último Paso",
|
||||
"total_steps": "Pasos Totales",
|
||||
"processing_log": "Registro de Procesamiento",
|
||||
"no_logs": "Aún no hay registros de procesamiento.",
|
||||
"file_not_found": "Archivo no encontrado"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Perfil",
|
||||
"not_signed_in": "No has iniciado sesión",
|
||||
"connection": "Conexión",
|
||||
"server": "Servidor",
|
||||
"user_id": "ID de Usuario",
|
||||
"legal": "Legal",
|
||||
"privacy_policy": "Política de Privacidad",
|
||||
"terms_of_service": "Términos de Servicio",
|
||||
"imprint": "Aviso Legal",
|
||||
"sign_out": "Cerrar sesión",
|
||||
"sign_out_title": "Cerrar sesión",
|
||||
"sign_out_msg": "¿Estás seguro de que deseas cerrar sesión?",
|
||||
"delete_account": "Eliminar Cuenta",
|
||||
"delete_account_title": "Eliminar Cuenta",
|
||||
"delete_account_msg": "Esto eliminará permanentemente tu cuenta y todos los datos asociados. Esta acción no se puede deshacer.",
|
||||
"could_not_open": "No se pudo abrir {page}. Inténtalo de nuevo.",
|
||||
"admin": "Admin",
|
||||
"settings": "Configuración",
|
||||
"language": "Idioma"
|
||||
},
|
||||
"legal": {
|
||||
"privacy_policy": "Privacidad",
|
||||
"terms": "Términos",
|
||||
"imprint": "Aviso Legal"
|
||||
},
|
||||
"tabs": {
|
||||
"upload": "Subir",
|
||||
"files": "Archivos",
|
||||
"profile": "Perfil"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"common": {
|
||||
"retry": "Réessayer",
|
||||
"cancel": "Annuler",
|
||||
"back": "Retour",
|
||||
"error": "Erreur",
|
||||
"loading": "Chargement…",
|
||||
"search": "Rechercher",
|
||||
"clear_search": "Effacer la recherche"
|
||||
},
|
||||
"welcome": {
|
||||
"tagline": "Traitement Intelligent de Documents",
|
||||
"description": "Ingérez des documents, lancez l'OCR, extrayez les métadonnées avec l'IA et transférez les fichiers vers votre stockage cloud — le tout dans un flux unique.",
|
||||
"get_started": "Commencer",
|
||||
"hint": "Connectez-vous à votre serveur DocuElevate auto-hébergé ou cloud.",
|
||||
"feature_ocr_title": "OCR et Extraction de Texte",
|
||||
"feature_ocr_desc": "Convertissez automatiquement les PDF scannés et les images en texte entièrement consultable.",
|
||||
"feature_ai_title": "Extraction de Métadonnées par IA",
|
||||
"feature_ai_desc": "L'IA classe les documents et extrait les champs clés comme les dates, montants et sujets.",
|
||||
"feature_cloud_title": "Stockage Multi-Cloud",
|
||||
"feature_cloud_desc": "Transférez les fichiers traités vers Dropbox, Google Drive, OneDrive, S3, Nextcloud et plus."
|
||||
},
|
||||
"login": {
|
||||
"server_url": "URL du Serveur",
|
||||
"server_url_placeholder": "https://votre-serveur-docuelevate.com",
|
||||
"sign_in_sso": "Se connecter avec SSO",
|
||||
"scan_qr": "📱 Scanner le code QR pour se connecter",
|
||||
"hint": "Connectez-vous via SSO ou scannez un code QR depuis l'application web.",
|
||||
"back": "← Retour",
|
||||
"or": "ou",
|
||||
"server_url_required": "URL du serveur requise",
|
||||
"server_url_required_msg": "Veuillez entrer l'URL de votre serveur DocuElevate.",
|
||||
"invalid_url": "URL invalide",
|
||||
"invalid_url_msg": "L'URL du serveur doit commencer par http:// ou https://",
|
||||
"sign_in_failed": "Échec de la connexion",
|
||||
"qr_login_failed": "Échec de la connexion QR"
|
||||
},
|
||||
"upload": {
|
||||
"camera": "Appareil photo",
|
||||
"photos": "Photos",
|
||||
"files": "Fichiers",
|
||||
"camera_access_title": "Accès à l'appareil photo requis",
|
||||
"camera_access_msg": "Veuillez autoriser l'accès à l'appareil photo dans les Réglages pour capturer des documents.",
|
||||
"photo_access_title": "Accès à la photothèque requis",
|
||||
"photo_access_msg": "Veuillez autoriser l'accès à la photothèque dans les Réglages pour sélectionner des images.",
|
||||
"file_picker_error": "Erreur du sélecteur de fichiers",
|
||||
"file_picker_error_msg": "Impossible d'ouvrir le sélecteur de fichiers",
|
||||
"empty_title": "Appuyez sur Appareil photo, Photos ou Fichiers pour télécharger un document.",
|
||||
"empty_hint": "Vous pouvez aussi partager des fichiers depuis d'autres applications vers DocuElevate.",
|
||||
"sign_in_required": "Veuillez vous connecter pour télécharger des documents.",
|
||||
"status_queued": "En file d'attente…",
|
||||
"status_processing": "En cours de traitement…",
|
||||
"status_completed": "Traité",
|
||||
"status_failed": "Échec du traitement",
|
||||
"status_duplicate": "Doublon – déjà traité",
|
||||
"tap_retry": "Appuyez pour réessayer",
|
||||
"retry_title": "Réessayer le téléchargement",
|
||||
"retry_msg": "Voulez-vous réessayer le téléchargement de \"{filename}\" ?",
|
||||
"capture_label": "Capturer un document avec l'appareil photo",
|
||||
"photo_label": "Sélectionner une photo de la bibliothèque",
|
||||
"file_label": "Choisir un fichier depuis l'appareil"
|
||||
},
|
||||
"files": {
|
||||
"title": "Mes Documents",
|
||||
"search_placeholder": "Rechercher des documents…",
|
||||
"empty_title": "Pas encore de documents.",
|
||||
"empty_hint": "Téléchargez un document depuis l'onglet Télécharger pour commencer.",
|
||||
"search_empty": "Aucun document ne correspond à votre recherche.",
|
||||
"search_empty_hint": "Essayez un autre terme de recherche.",
|
||||
"view_details": "Voir les détails de {filename}"
|
||||
},
|
||||
"file_detail": {
|
||||
"title": "Détails du Fichier",
|
||||
"back": "Retour aux Fichiers",
|
||||
"file_size": "Taille",
|
||||
"mime_type": "Type MIME",
|
||||
"uploaded": "Téléchargé",
|
||||
"file_hash": "Hash du Fichier",
|
||||
"last_step": "Dernière Étape",
|
||||
"total_steps": "Étapes Totales",
|
||||
"processing_log": "Journal de Traitement",
|
||||
"no_logs": "Pas encore de journaux de traitement.",
|
||||
"file_not_found": "Fichier non trouvé"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
"not_signed_in": "Non connecté",
|
||||
"connection": "Connexion",
|
||||
"server": "Serveur",
|
||||
"user_id": "ID Utilisateur",
|
||||
"legal": "Mentions Légales",
|
||||
"privacy_policy": "Politique de Confidentialité",
|
||||
"terms_of_service": "Conditions d'Utilisation",
|
||||
"imprint": "Mentions Légales",
|
||||
"sign_out": "Se déconnecter",
|
||||
"sign_out_title": "Se déconnecter",
|
||||
"sign_out_msg": "Êtes-vous sûr de vouloir vous déconnecter ?",
|
||||
"delete_account": "Supprimer le Compte",
|
||||
"delete_account_title": "Supprimer le Compte",
|
||||
"delete_account_msg": "Cela supprimera définitivement votre compte et toutes les données associées. Cette action est irréversible.",
|
||||
"could_not_open": "Impossible d'ouvrir {page}. Veuillez réessayer.",
|
||||
"admin": "Admin",
|
||||
"settings": "Paramètres",
|
||||
"language": "Langue"
|
||||
},
|
||||
"legal": {
|
||||
"privacy_policy": "Confidentialité",
|
||||
"terms": "Conditions",
|
||||
"imprint": "Mentions Légales"
|
||||
},
|
||||
"tabs": {
|
||||
"upload": "Télécharger",
|
||||
"files": "Fichiers",
|
||||
"profile": "Profil"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Lightweight i18n module for the DocuElevate mobile app.
|
||||
*
|
||||
* Uses the device locale (via expo-localization) to select the best matching
|
||||
* translation file. Falls back to English for missing keys or unsupported
|
||||
* locales.
|
||||
*
|
||||
* Supported languages: English, German, Spanish, French, Italian.
|
||||
*
|
||||
* ## React integration
|
||||
*
|
||||
* Wrap the app root in `<LocaleProvider>` and call `useLocale()` in any
|
||||
* component that renders translated strings. `useLocale()` returns the
|
||||
* active language code and a `setLang` setter that:
|
||||
* 1. Updates the in-memory `currentLanguage` variable (so `t()` picks it up)
|
||||
* 2. Triggers a React re-render of every consumer
|
||||
* 3. Persists the choice to AsyncStorage (survives app restarts)
|
||||
*
|
||||
* Language priority on startup:
|
||||
* server preference (from /api/mobile/whoami) > AsyncStorage > device locale > "en"
|
||||
*/
|
||||
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { getLocales } from "expo-localization";
|
||||
import React from "react";
|
||||
|
||||
import de from "./de.json";
|
||||
import en from "./en.json";
|
||||
import es from "./es.json";
|
||||
import fr from "./fr.json";
|
||||
import it from "./it.json";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Translation catalog
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type TranslationMap = Record<string, Record<string, string>>;
|
||||
|
||||
const translations: Record<string, TranslationMap> = { en, de, es, fr, it };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Locale detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LANG_STORAGE_KEY = "@docuelevate:language";
|
||||
|
||||
/** Resolve the best-matching language code from the device locale list. */
|
||||
function detectLanguage(): string {
|
||||
try {
|
||||
const locales = getLocales();
|
||||
if (locales.length > 0) {
|
||||
// Try exact match first (e.g. "de"), then fall back to language prefix
|
||||
const code = locales[0].languageCode?.toLowerCase();
|
||||
if (code && translations[code]) return code;
|
||||
}
|
||||
} catch {
|
||||
// getLocales() can throw on some platforms – default to English
|
||||
}
|
||||
return "en";
|
||||
}
|
||||
|
||||
let currentLanguage: string = detectLanguage();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Plain-function public API (framework-agnostic)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Translate a dot-separated key, e.g. `t("upload.camera")`.
|
||||
*
|
||||
* Supports simple placeholder interpolation:
|
||||
* `t("upload.retry_msg", { filename: "doc.pdf" })`
|
||||
* replaces `{filename}` in the translated string.
|
||||
*
|
||||
* Falls back to the English value, then to the raw key if no translation
|
||||
* exists.
|
||||
*/
|
||||
export function t(key: string, params?: Record<string, string>): string {
|
||||
const [section, ...rest] = key.split(".");
|
||||
const subKey = rest.join(".");
|
||||
|
||||
let value =
|
||||
translations[currentLanguage]?.[section]?.[subKey] ??
|
||||
translations.en?.[section]?.[subKey] ??
|
||||
key;
|
||||
|
||||
if (params) {
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
value = value.replaceAll(`{${k}}`, v);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Return the current language code (e.g. "en", "de"). */
|
||||
export function getLanguage(): string {
|
||||
return currentLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the active language in memory.
|
||||
* Prefer `useLocale().setLang` inside React components – it also persists
|
||||
* the choice and triggers re-renders.
|
||||
*/
|
||||
export function setLanguage(lang: string): void {
|
||||
if (translations[lang]) {
|
||||
currentLanguage = lang;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return true if the given language code is supported by the mobile app. */
|
||||
export function isLanguageSupported(lang: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(translations, lang);
|
||||
}
|
||||
|
||||
/** Return the list of supported language codes. */
|
||||
export function getSupportedLanguages(): { code: string; label: string }[] {
|
||||
return [
|
||||
{ code: "en", label: "English" },
|
||||
{ code: "de", label: "Deutsch" },
|
||||
{ code: "es", label: "Español" },
|
||||
{ code: "fr", label: "Français" },
|
||||
{ code: "it", label: "Italiano" },
|
||||
];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// React integration – context + provider + hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface LocaleContextValue {
|
||||
/** The active language code, e.g. "en" or "de". */
|
||||
lang: string;
|
||||
/**
|
||||
* Switch to a new language. Persists the choice to AsyncStorage and
|
||||
* triggers a re-render of every `useLocale()` consumer.
|
||||
*/
|
||||
setLang: (code: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const LocaleContext = React.createContext<LocaleContextValue>({
|
||||
lang: currentLanguage,
|
||||
// Default setter used outside of a provider – updates in-memory only.
|
||||
setLang: async (code: string) => {
|
||||
setLanguage(code);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Wrap the app root in `LocaleProvider` to enable reactive language switching.
|
||||
*
|
||||
* On mount it reads the persisted language from AsyncStorage so the user's
|
||||
* choice survives app restarts. The server-preferred language is applied
|
||||
* externally (see `AuthGuard` in `app/_layout.tsx`) after the profile is
|
||||
* fetched from `/api/mobile/whoami`.
|
||||
*/
|
||||
export function LocaleProvider({ children }: { children: React.ReactNode }): React.ReactElement {
|
||||
const [lang, setLangState] = React.useState(currentLanguage);
|
||||
|
||||
// Restore the persisted language preference once on app start.
|
||||
React.useEffect(() => {
|
||||
AsyncStorage.getItem(LANG_STORAGE_KEY)
|
||||
.then((saved) => {
|
||||
if (saved && isLanguageSupported(saved)) {
|
||||
setLanguage(saved);
|
||||
setLangState(saved);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Ignore read errors – fall back to device-detected language.
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setLang = React.useCallback(async (code: string): Promise<void> => {
|
||||
if (!isLanguageSupported(code)) return;
|
||||
setLanguage(code);
|
||||
setLangState(code);
|
||||
try {
|
||||
await AsyncStorage.setItem(LANG_STORAGE_KEY, code);
|
||||
} catch {
|
||||
// Ignore write errors – the in-memory change is still applied.
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); // setLangState is a React state setter – its identity is guaranteed stable
|
||||
|
||||
const value = React.useMemo(() => ({ lang, setLang }), [lang, setLang]);
|
||||
|
||||
return React.createElement(LocaleContext.Provider, { value }, children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that subscribes to language changes.
|
||||
*
|
||||
* Any component calling `useLocale()` re-renders automatically when the
|
||||
* language changes. Call `t()` freely inside the component body – the
|
||||
* re-render will pick up the new translations.
|
||||
*
|
||||
* ```tsx
|
||||
* function MyScreen() {
|
||||
* const { lang, setLang } = useLocale(); // subscribes to changes
|
||||
* return <Text>{t("common.loading")}</Text>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useLocale(): LocaleContextValue {
|
||||
return React.useContext(LocaleContext);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"common": {
|
||||
"retry": "Riprova",
|
||||
"cancel": "Annulla",
|
||||
"back": "Indietro",
|
||||
"error": "Errore",
|
||||
"loading": "Caricamento…",
|
||||
"search": "Cerca",
|
||||
"clear_search": "Cancella ricerca"
|
||||
},
|
||||
"welcome": {
|
||||
"tagline": "Elaborazione Intelligente dei Documenti",
|
||||
"description": "Acquisisci documenti, esegui l'OCR, estrai metadati con l'IA e invia i file al tuo cloud storage — tutto in un unico flusso.",
|
||||
"get_started": "Inizia",
|
||||
"hint": "Collegati al tuo server DocuElevate self-hosted o cloud.",
|
||||
"feature_ocr_title": "OCR ed Estrazione Testo",
|
||||
"feature_ocr_desc": "Converti automaticamente PDF e immagini scansionate in testo completamente ricercabile.",
|
||||
"feature_ai_title": "Estrazione Metadati con IA",
|
||||
"feature_ai_desc": "L'IA classifica i documenti ed estrae campi chiave come date, importi e oggetti.",
|
||||
"feature_cloud_title": "Archiviazione Multi-Cloud",
|
||||
"feature_cloud_desc": "Invia i file elaborati a Dropbox, Google Drive, OneDrive, S3, Nextcloud e altro."
|
||||
},
|
||||
"login": {
|
||||
"server_url": "URL del Server",
|
||||
"server_url_placeholder": "https://il-tuo-server-docuelevate.com",
|
||||
"sign_in_sso": "Accedi con SSO",
|
||||
"scan_qr": "📱 Scansiona il codice QR per accedere",
|
||||
"hint": "Accedi tramite SSO o scansiona un codice QR dall'app web.",
|
||||
"back": "← Indietro",
|
||||
"or": "o",
|
||||
"server_url_required": "URL del server richiesto",
|
||||
"server_url_required_msg": "Inserisci l'URL del tuo server DocuElevate.",
|
||||
"invalid_url": "URL non valido",
|
||||
"invalid_url_msg": "L'URL del server deve iniziare con http:// o https://",
|
||||
"sign_in_failed": "Accesso fallito",
|
||||
"qr_login_failed": "Accesso QR fallito"
|
||||
},
|
||||
"upload": {
|
||||
"camera": "Fotocamera",
|
||||
"photos": "Foto",
|
||||
"files": "File",
|
||||
"camera_access_title": "Accesso alla fotocamera richiesto",
|
||||
"camera_access_msg": "Consenti l'accesso alla fotocamera nelle Impostazioni per acquisire documenti.",
|
||||
"photo_access_title": "Accesso alla libreria foto richiesto",
|
||||
"photo_access_msg": "Consenti l'accesso alla libreria foto nelle Impostazioni per selezionare immagini.",
|
||||
"file_picker_error": "Errore nel selettore file",
|
||||
"file_picker_error_msg": "Impossibile aprire il selettore file",
|
||||
"empty_title": "Tocca Fotocamera, Foto o File per caricare un documento.",
|
||||
"empty_hint": "Puoi anche condividere file da altre app direttamente su DocuElevate.",
|
||||
"sign_in_required": "Accedi per caricare documenti.",
|
||||
"status_queued": "In coda per l'elaborazione…",
|
||||
"status_processing": "Elaborazione in corso…",
|
||||
"status_completed": "Elaborato",
|
||||
"status_failed": "Elaborazione fallita",
|
||||
"status_duplicate": "Duplicato – già elaborato",
|
||||
"tap_retry": "Tocca per riprovare",
|
||||
"retry_title": "Riprova Caricamento",
|
||||
"retry_msg": "Vuoi riprovare a caricare \"{filename}\"?",
|
||||
"capture_label": "Acquisisci documento con la fotocamera",
|
||||
"photo_label": "Seleziona foto dalla libreria",
|
||||
"file_label": "Seleziona file dal dispositivo"
|
||||
},
|
||||
"files": {
|
||||
"title": "I Miei Documenti",
|
||||
"search_placeholder": "Cerca documenti…",
|
||||
"empty_title": "Nessun documento ancora.",
|
||||
"empty_hint": "Carica un documento dalla scheda Carica per iniziare.",
|
||||
"search_empty": "Nessun documento corrisponde alla tua ricerca.",
|
||||
"search_empty_hint": "Prova con un altro termine di ricerca.",
|
||||
"view_details": "Visualizza dettagli per {filename}"
|
||||
},
|
||||
"file_detail": {
|
||||
"title": "Dettagli File",
|
||||
"back": "Torna ai File",
|
||||
"file_size": "Dimensione",
|
||||
"mime_type": "Tipo MIME",
|
||||
"uploaded": "Caricato",
|
||||
"file_hash": "Hash del File",
|
||||
"last_step": "Ultimo Passaggio",
|
||||
"total_steps": "Passaggi Totali",
|
||||
"processing_log": "Registro di Elaborazione",
|
||||
"no_logs": "Nessun registro di elaborazione ancora.",
|
||||
"file_not_found": "File non trovato"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profilo",
|
||||
"not_signed_in": "Non connesso",
|
||||
"connection": "Connessione",
|
||||
"server": "Server",
|
||||
"user_id": "ID Utente",
|
||||
"legal": "Legale",
|
||||
"privacy_policy": "Informativa sulla Privacy",
|
||||
"terms_of_service": "Termini di Servizio",
|
||||
"imprint": "Note Legali",
|
||||
"sign_out": "Esci",
|
||||
"sign_out_title": "Esci",
|
||||
"sign_out_msg": "Sei sicuro di voler uscire?",
|
||||
"delete_account": "Elimina Account",
|
||||
"delete_account_title": "Elimina Account",
|
||||
"delete_account_msg": "Questo eliminerà permanentemente il tuo account e tutti i dati associati. Questa azione non può essere annullata.",
|
||||
"could_not_open": "Impossibile aprire {page}. Riprova.",
|
||||
"admin": "Admin",
|
||||
"settings": "Impostazioni",
|
||||
"language": "Lingua"
|
||||
},
|
||||
"legal": {
|
||||
"privacy_policy": "Privacy",
|
||||
"terms": "Termini",
|
||||
"imprint": "Note Legali"
|
||||
},
|
||||
"tabs": {
|
||||
"upload": "Carica",
|
||||
"files": "File",
|
||||
"profile": "Profilo"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user