diff --git a/src/medcheck/i18n/de.json b/src/medcheck/i18n/de.json
index 0fc5384..b5f4656 100644
--- a/src/medcheck/i18n/de.json
+++ b/src/medcheck/i18n/de.json
@@ -21,5 +21,88 @@
"none_specified": "Keine angegeben.",
"field_value": "Wert",
"field_key": "Feld",
- "disclaimer": "HAFTUNGSAUSSCHLUSS: Dieser Bericht wurde von einem automatisierten KI-System (MedCheck) erstellt und soll qualifizierte medizinische Fachkräfte unterstützen. Er stellt keine endgültige klinische Diagnose dar. Alle Befunde müssen von einem zugelassenen Radiologen oder Arzt überprüft und validiert werden."
-}
\ No newline at end of file
+ "disclaimer": "HAFTUNGSAUSSCHLUSS: Dieser Bericht wurde von einem automatisierten KI-System (MedCheck) erstellt und soll qualifizierte medizinische Fachkräfte unterstützen. Er stellt keine endgültige klinische Diagnose dar. Alle Befunde müssen von einem zugelassenen Radiologen oder Arzt überprüft und validiert werden.",
+ "ui_title": "MedCheck — KI-gestützte Analyse medizinischer Bildgebung",
+ "ui_tagline": "KI-gestützte Analyse medizinischer Bildgebung",
+ "ui_language": "Sprache",
+ "ui_hero_overline": "Analyse-Assistent",
+ "ui_hero_title": "Bildgebung in drei Schritten analysieren",
+ "ui_hero_lead": "Bilddaten laden, klinischen Kontext ergänzen und eine KI-gestützte Auswertung mit strukturiertem, radiologischem Bericht starten.",
+ "ui_preview_title": "Vorschau",
+ "ui_preview_body": "Die Analyse aus dem Browser ist noch nicht verfügbar — der Schritt „Analysieren“ liefert derzeit 501 Not Implemented.",
+ "ui_preview_cli": "Nutzen Sie stattdessen die Kommandozeile:",
+ "ui_step_label": "Schritt",
+ "ui_step1": "Daten laden",
+ "ui_step2": "Klinischer Kontext",
+ "ui_step3": "Analysieren",
+ "ui_upload_heading": "Bilddatei hochladen",
+ "ui_dropzone_cta": "Klicken zum Auswählen",
+ "ui_dropzone_or": "oder Datei hierher ziehen",
+ "ui_dropzone_formats": "DICOM, NIfTI, PNG, JPEG, TIFF — bis 500 MB",
+ "ui_autodetect": "Automatische Erkennung aktiv — das Dateiformat wird selbstständig identifiziert",
+ "ui_or": "ODER",
+ "ui_remote_url": "Externe URL",
+ "ui_url_hint": "Link zu einer DICOM-Datei oder einem easyRadiology-Portal",
+ "ui_patient_code": "Patienten-Code / ID",
+ "ui_back": "Zurück",
+ "ui_next_context": "Weiter: Klinischer Kontext",
+ "ui_next_analyze": "Weiter: Analysieren",
+ "ui_symptoms": "Symptome / Leitbeschwerde",
+ "ui_symptoms_hint": "z. B. anhaltende Kopfschmerzen, Sehstörungen, mediale Knieschmerzen",
+ "ui_trauma": "Trauma / Unfallhergang",
+ "ui_suspected_dx": "Verdachtsdiagnose",
+ "ui_anatomy": "Anatomie / Region",
+ "ui_anatomy_placeholder": "Region wählen",
+ "ui_anat_group_msk": "Muskuloskelettal",
+ "ui_anat_group_other": "Weitere Regionen",
+ "ui_anat_knee": "Knie",
+ "ui_anat_shoulder": "Schulter",
+ "ui_anat_hip": "Hüfte",
+ "ui_anat_wrist": "Handgelenk",
+ "ui_anat_ankle": "Sprunggelenk",
+ "ui_anat_spine": "Wirbelsäule",
+ "ui_anat_brain": "Gehirn",
+ "ui_anat_chest": "Thorax",
+ "ui_anat_abdomen": "Abdomen",
+ "ui_anat_other": "Sonstiges / Ganzkörper",
+ "ui_modules": "Analysemodule",
+ "ui_mod_ml": "ML-Klassifikation",
+ "ui_mod_seg": "Vision-Segmentierung",
+ "ui_mod_anomaly": "Anomalie-Erkennung",
+ "ui_mod_measure": "Automatische Messungen",
+ "ui_mod_compare": "Voraufnahmen-Vergleich",
+ "ui_mod_report": "Berichterstellung",
+ "ui_model": "KI-Modell",
+ "ui_model_claude": "Claude Opus 4.8 (beste Qualität)",
+ "ui_model_openai": "GPT-5.5 (beste Auflösung)",
+ "ui_model_gemini": "Gemini 3.5 Flash (am schnellsten)",
+ "ui_model_local": "Lokales LLaVA-Med (offline)",
+ "ui_report_language": "Berichtssprache",
+ "ui_report_format": "Berichtsformat",
+ "ui_fmt_pdf": "PDF (strukturierter Radiologiebericht)",
+ "ui_fmt_json": "JSON (strukturierte Daten)",
+ "ui_fmt_html": "HTML (interaktiv)",
+ "ui_consent_label": "Ich stimme zu, dass patientenbezogene Bilddaten zur Analyse an den ausgewählten externen Cloud-LLM-Anbieter übertragen werden.",
+ "ui_consent_hint": "Erforderlich für Cloud-Modelle. Ohne Zustimmung verlassen keine Daten Ihr System.",
+ "ui_consent_required": "Bitte stimmen Sie der Cloud-Übertragung zu oder wählen Sie das lokale Modell.",
+ "ui_start": "Analyse starten",
+ "ui_results": "Ergebnisse",
+ "ui_progress_waiting": "Warten auf den Start der Analyse …",
+ "ui_prog_sending": "Anfrage wird an den Server gesendet …",
+ "ui_no_results": "Noch keine Ergebnisse. Konfigurieren Sie die Schritte oben und starten Sie die Analyse.",
+ "ui_dl_pdf": "PDF-Bericht",
+ "ui_dl_json": "JSON-Daten",
+ "ui_dl_images": "Annotierte Bilder",
+ "ui_dl_pending": "Verfügbar nach Abschluss der Analyse",
+ "ui_footer_notice_title": "Kein Medizinprodukt",
+ "ui_footer_research": "Nur für Forschungs- und Ausbildungszwecke.",
+ "ui_footer_docs": "Dokumentation",
+ "ui_footer_project": "Projekt",
+ "ui_footer_license": "Lizenz (Apache 2.0)",
+ "ui_footer_security": "Sicherheitsrichtlinie",
+ "ui_footer_changelog": "Änderungsprotokoll",
+ "ui_footer_intended": "Verwendungszweck",
+ "ui_footer_modelcard": "Model Card",
+ "ui_footer_github": "GitHub-Repository",
+ "ui_version": "Version"
+}
diff --git a/src/medcheck/i18n/en.json b/src/medcheck/i18n/en.json
index 39a57de..0c4230b 100644
--- a/src/medcheck/i18n/en.json
+++ b/src/medcheck/i18n/en.json
@@ -21,5 +21,88 @@
"none_specified": "None specified.",
"field_value": "Value",
"field_key": "Field",
- "disclaimer": "DISCLAIMER: This report was generated by an automated AI system (MedCheck) and is intended to assist qualified medical professionals. It does not constitute a final clinical diagnosis. All findings must be reviewed and validated by a licensed radiologist or physician."
-}
\ No newline at end of file
+ "disclaimer": "DISCLAIMER: This report was generated by an automated AI system (MedCheck) and is intended to assist qualified medical professionals. It does not constitute a final clinical diagnosis. All findings must be reviewed and validated by a licensed radiologist or physician.",
+ "ui_title": "MedCheck — AI-assisted medical imaging analysis",
+ "ui_tagline": "AI-assisted medical imaging analysis",
+ "ui_language": "Language",
+ "ui_hero_overline": "Analysis wizard",
+ "ui_hero_title": "Analyze imaging data in three steps",
+ "ui_hero_lead": "Load imaging data, add clinical context, and run an AI-assisted analysis with a structured, radiology-style report.",
+ "ui_preview_title": "Preview",
+ "ui_preview_body": "Running an analysis from the browser is not yet available — the Analyze step currently returns 501 Not Implemented.",
+ "ui_preview_cli": "Use the command line instead:",
+ "ui_step_label": "Step",
+ "ui_step1": "Load data",
+ "ui_step2": "Clinical context",
+ "ui_step3": "Analyze",
+ "ui_upload_heading": "Upload imaging file",
+ "ui_dropzone_cta": "Click to browse",
+ "ui_dropzone_or": "or drag & drop a file here",
+ "ui_dropzone_formats": "DICOM, NIfTI, PNG, JPEG, TIFF — up to 500 MB",
+ "ui_autodetect": "Auto-detect active — the file format will be identified automatically",
+ "ui_or": "OR",
+ "ui_remote_url": "Remote URL",
+ "ui_url_hint": "Link to a DICOM file or an easyRadiology portal",
+ "ui_patient_code": "Patient code / ID",
+ "ui_back": "Back",
+ "ui_next_context": "Next: Clinical context",
+ "ui_next_analyze": "Next: Analyze",
+ "ui_symptoms": "Symptoms / chief complaint",
+ "ui_symptoms_hint": "e.g. persistent headache, vision changes, medial knee pain",
+ "ui_trauma": "Trauma / mechanism",
+ "ui_suspected_dx": "Suspected diagnosis",
+ "ui_anatomy": "Anatomy / region",
+ "ui_anatomy_placeholder": "Select region",
+ "ui_anat_group_msk": "Musculoskeletal",
+ "ui_anat_group_other": "Other regions",
+ "ui_anat_knee": "Knee",
+ "ui_anat_shoulder": "Shoulder",
+ "ui_anat_hip": "Hip",
+ "ui_anat_wrist": "Wrist",
+ "ui_anat_ankle": "Ankle",
+ "ui_anat_spine": "Spine",
+ "ui_anat_brain": "Brain",
+ "ui_anat_chest": "Chest / thorax",
+ "ui_anat_abdomen": "Abdomen",
+ "ui_anat_other": "Other / whole body",
+ "ui_modules": "Analysis modules",
+ "ui_mod_ml": "ML classification",
+ "ui_mod_seg": "Vision segmentation",
+ "ui_mod_anomaly": "Anomaly detection",
+ "ui_mod_measure": "Auto measurements",
+ "ui_mod_compare": "Prior comparison",
+ "ui_mod_report": "Report generation",
+ "ui_model": "AI model",
+ "ui_model_claude": "Claude Opus 4.8 (best quality)",
+ "ui_model_openai": "GPT-5.5 (best resolution)",
+ "ui_model_gemini": "Gemini 3.5 Flash (fastest)",
+ "ui_model_local": "Local LLaVA-Med (offline)",
+ "ui_report_language": "Report language",
+ "ui_report_format": "Report format",
+ "ui_fmt_pdf": "PDF (structured radiology report)",
+ "ui_fmt_json": "JSON (structured data)",
+ "ui_fmt_html": "HTML (interactive)",
+ "ui_consent_label": "I consent to transmitting patient-derived imaging data to the selected external cloud LLM provider for analysis.",
+ "ui_consent_hint": "Required for cloud models. Without consent, no data leaves your machine.",
+ "ui_consent_required": "Please consent to the cloud transmission or choose the local model.",
+ "ui_start": "Start analysis",
+ "ui_results": "Results",
+ "ui_progress_waiting": "Waiting for analysis to start …",
+ "ui_prog_sending": "Sending request to the server …",
+ "ui_no_results": "No results yet. Configure the steps above and start the analysis.",
+ "ui_dl_pdf": "PDF report",
+ "ui_dl_json": "JSON data",
+ "ui_dl_images": "Annotated images",
+ "ui_dl_pending": "Available after the analysis completes",
+ "ui_footer_notice_title": "Not a medical device",
+ "ui_footer_research": "For research and educational use only.",
+ "ui_footer_docs": "Documentation",
+ "ui_footer_project": "Project",
+ "ui_footer_license": "License (Apache 2.0)",
+ "ui_footer_security": "Security policy",
+ "ui_footer_changelog": "Changelog",
+ "ui_footer_intended": "Intended use",
+ "ui_footer_modelcard": "Model card",
+ "ui_footer_github": "GitHub repository",
+ "ui_version": "Version"
+}
diff --git a/src/medcheck/i18n/es.json b/src/medcheck/i18n/es.json
index 006ed35..5f42688 100644
--- a/src/medcheck/i18n/es.json
+++ b/src/medcheck/i18n/es.json
@@ -21,5 +21,88 @@
"none_specified": "Ninguna especificada.",
"field_value": "Valor",
"field_key": "Campo",
- "disclaimer": "AVISO LEGAL: Este informe fue generado por un sistema automatizado de IA (MedCheck) y está diseñado para asistir a profesionales médicos calificados. No constituye un diagnóstico clínico definitivo. Todos los hallazgos deben ser revisados y validados por un radiólogo o médico licenciado."
-}
\ No newline at end of file
+ "disclaimer": "AVISO LEGAL: Este informe fue generado por un sistema automatizado de IA (MedCheck) y está diseñado para asistir a profesionales médicos calificados. No constituye un diagnóstico clínico definitivo. Todos los hallazgos deben ser revisados y validados por un radiólogo o médico licenciado.",
+ "ui_title": "MedCheck — Análisis de imágenes médicas asistido por IA",
+ "ui_tagline": "Análisis de imágenes médicas asistido por IA",
+ "ui_language": "Idioma",
+ "ui_hero_overline": "Asistente de análisis",
+ "ui_hero_title": "Analizar imágenes en tres pasos",
+ "ui_hero_lead": "Cargue los datos de imagen, añada el contexto clínico e inicie un análisis asistido por IA con un informe radiológico estructurado.",
+ "ui_preview_title": "Vista previa",
+ "ui_preview_body": "El análisis desde el navegador aún no está disponible — el paso «Analizar» devuelve actualmente 501 Not Implemented.",
+ "ui_preview_cli": "Utilice la línea de comandos en su lugar:",
+ "ui_step_label": "Paso",
+ "ui_step1": "Cargar datos",
+ "ui_step2": "Contexto clínico",
+ "ui_step3": "Analizar",
+ "ui_upload_heading": "Subir archivo de imagen",
+ "ui_dropzone_cta": "Haga clic para explorar",
+ "ui_dropzone_or": "o arrastre y suelte un archivo aquí",
+ "ui_dropzone_formats": "DICOM, NIfTI, PNG, JPEG, TIFF — hasta 500 MB",
+ "ui_autodetect": "Detección automática activa — el formato del archivo se identificará automáticamente",
+ "ui_or": "O",
+ "ui_remote_url": "URL remota",
+ "ui_url_hint": "Enlace a un archivo DICOM o a un portal easyRadiology",
+ "ui_patient_code": "Código de paciente / ID",
+ "ui_back": "Atrás",
+ "ui_next_context": "Siguiente: Contexto clínico",
+ "ui_next_analyze": "Siguiente: Analizar",
+ "ui_symptoms": "Síntomas / motivo principal",
+ "ui_symptoms_hint": "p. ej. cefalea persistente, alteraciones visuales, dolor medial de rodilla",
+ "ui_trauma": "Trauma / mecanismo",
+ "ui_suspected_dx": "Diagnóstico sospechado",
+ "ui_anatomy": "Anatomía / región",
+ "ui_anatomy_placeholder": "Seleccionar región",
+ "ui_anat_group_msk": "Musculoesquelético",
+ "ui_anat_group_other": "Otras regiones",
+ "ui_anat_knee": "Rodilla",
+ "ui_anat_shoulder": "Hombro",
+ "ui_anat_hip": "Cadera",
+ "ui_anat_wrist": "Muñeca",
+ "ui_anat_ankle": "Tobillo",
+ "ui_anat_spine": "Columna vertebral",
+ "ui_anat_brain": "Cerebro",
+ "ui_anat_chest": "Tórax",
+ "ui_anat_abdomen": "Abdomen",
+ "ui_anat_other": "Otro / cuerpo entero",
+ "ui_modules": "Módulos de análisis",
+ "ui_mod_ml": "Clasificación ML",
+ "ui_mod_seg": "Segmentación de visión",
+ "ui_mod_anomaly": "Detección de anomalías",
+ "ui_mod_measure": "Mediciones automáticas",
+ "ui_mod_compare": "Comparación con estudios previos",
+ "ui_mod_report": "Generación de informes",
+ "ui_model": "Modelo de IA",
+ "ui_model_claude": "Claude Opus 4.8 (mejor calidad)",
+ "ui_model_openai": "GPT-5.5 (mejor resolución)",
+ "ui_model_gemini": "Gemini 3.5 Flash (el más rápido)",
+ "ui_model_local": "LLaVA-Med local (sin conexión)",
+ "ui_report_language": "Idioma del informe",
+ "ui_report_format": "Formato del informe",
+ "ui_fmt_pdf": "PDF (informe radiológico estructurado)",
+ "ui_fmt_json": "JSON (datos estructurados)",
+ "ui_fmt_html": "HTML (interactivo)",
+ "ui_consent_label": "Doy mi consentimiento para transmitir los datos de imagen del paciente al proveedor cloud de LLM seleccionado para su análisis.",
+ "ui_consent_hint": "Obligatorio para modelos en la nube. Sin consentimiento, ningún dato sale de su equipo.",
+ "ui_consent_required": "Por favor, consienta la transmisión a la nube o elija el modelo local.",
+ "ui_start": "Iniciar análisis",
+ "ui_results": "Resultados",
+ "ui_progress_waiting": "Esperando el inicio del análisis …",
+ "ui_prog_sending": "Enviando la solicitud al servidor …",
+ "ui_no_results": "Aún no hay resultados. Configure los pasos anteriores e inicie el análisis.",
+ "ui_dl_pdf": "Informe PDF",
+ "ui_dl_json": "Datos JSON",
+ "ui_dl_images": "Imágenes anotadas",
+ "ui_dl_pending": "Disponible al finalizar el análisis",
+ "ui_footer_notice_title": "No es un producto sanitario",
+ "ui_footer_research": "Solo para investigación y formación.",
+ "ui_footer_docs": "Documentación",
+ "ui_footer_project": "Proyecto",
+ "ui_footer_license": "Licencia (Apache 2.0)",
+ "ui_footer_security": "Política de seguridad",
+ "ui_footer_changelog": "Registro de cambios",
+ "ui_footer_intended": "Uso previsto",
+ "ui_footer_modelcard": "Ficha del modelo",
+ "ui_footer_github": "Repositorio de GitHub",
+ "ui_version": "Versión"
+}
diff --git a/src/medcheck/i18n/fr.json b/src/medcheck/i18n/fr.json
index 31e4a8f..d89566d 100644
--- a/src/medcheck/i18n/fr.json
+++ b/src/medcheck/i18n/fr.json
@@ -21,5 +21,88 @@
"none_specified": "Aucune spécifiée.",
"field_value": "Valeur",
"field_key": "Champ",
- "disclaimer": "AVERTISSEMENT : Ce rapport a été généré par un système d'IA automatisé (MedCheck) et est destiné à assister les professionnels de santé qualifiés. Il ne constitue pas un diagnostic clinique définitif. Toutes les observations doivent être examinées et validées par un radiologue ou un médecin agréé."
-}
\ No newline at end of file
+ "disclaimer": "AVERTISSEMENT : Ce rapport a été généré par un système d'IA automatisé (MedCheck) et est destiné à assister les professionnels de santé qualifiés. Il ne constitue pas un diagnostic clinique définitif. Toutes les observations doivent être examinées et validées par un radiologue ou un médecin agréé.",
+ "ui_title": "MedCheck — Analyse d'imagerie médicale assistée par IA",
+ "ui_tagline": "Analyse d'imagerie médicale assistée par IA",
+ "ui_language": "Langue",
+ "ui_hero_overline": "Assistant d'analyse",
+ "ui_hero_title": "Analyser l'imagerie en trois étapes",
+ "ui_hero_lead": "Chargez les données d'imagerie, ajoutez le contexte clinique et lancez une analyse assistée par IA avec un rapport radiologique structuré.",
+ "ui_preview_title": "Aperçu",
+ "ui_preview_body": "L'analyse depuis le navigateur n'est pas encore disponible — l'étape « Analyser » renvoie actuellement 501 Not Implemented.",
+ "ui_preview_cli": "Utilisez plutôt la ligne de commande :",
+ "ui_step_label": "Étape",
+ "ui_step1": "Charger les données",
+ "ui_step2": "Contexte clinique",
+ "ui_step3": "Analyser",
+ "ui_upload_heading": "Téléverser un fichier d'imagerie",
+ "ui_dropzone_cta": "Cliquer pour parcourir",
+ "ui_dropzone_or": "ou glisser-déposer un fichier ici",
+ "ui_dropzone_formats": "DICOM, NIfTI, PNG, JPEG, TIFF — jusqu'à 500 Mo",
+ "ui_autodetect": "Détection automatique active — le format du fichier sera identifié automatiquement",
+ "ui_or": "OU",
+ "ui_remote_url": "URL distante",
+ "ui_url_hint": "Lien vers un fichier DICOM ou un portail easyRadiology",
+ "ui_patient_code": "Code patient / ID",
+ "ui_back": "Retour",
+ "ui_next_context": "Suivant : Contexte clinique",
+ "ui_next_analyze": "Suivant : Analyser",
+ "ui_symptoms": "Symptômes / motif principal",
+ "ui_symptoms_hint": "p. ex. céphalées persistantes, troubles visuels, douleur médiale du genou",
+ "ui_trauma": "Traumatisme / mécanisme",
+ "ui_suspected_dx": "Diagnostic suspecté",
+ "ui_anatomy": "Anatomie / région",
+ "ui_anatomy_placeholder": "Choisir une région",
+ "ui_anat_group_msk": "Musculo-squelettique",
+ "ui_anat_group_other": "Autres régions",
+ "ui_anat_knee": "Genou",
+ "ui_anat_shoulder": "Épaule",
+ "ui_anat_hip": "Hanche",
+ "ui_anat_wrist": "Poignet",
+ "ui_anat_ankle": "Cheville",
+ "ui_anat_spine": "Colonne vertébrale",
+ "ui_anat_brain": "Cerveau",
+ "ui_anat_chest": "Thorax",
+ "ui_anat_abdomen": "Abdomen",
+ "ui_anat_other": "Autre / corps entier",
+ "ui_modules": "Modules d'analyse",
+ "ui_mod_ml": "Classification ML",
+ "ui_mod_seg": "Segmentation vision",
+ "ui_mod_anomaly": "Détection d'anomalies",
+ "ui_mod_measure": "Mesures automatiques",
+ "ui_mod_compare": "Comparaison antérieure",
+ "ui_mod_report": "Génération de rapport",
+ "ui_model": "Modèle d'IA",
+ "ui_model_claude": "Claude Opus 4.8 (meilleure qualité)",
+ "ui_model_openai": "GPT-5.5 (meilleure résolution)",
+ "ui_model_gemini": "Gemini 3.5 Flash (le plus rapide)",
+ "ui_model_local": "LLaVA-Med local (hors ligne)",
+ "ui_report_language": "Langue du rapport",
+ "ui_report_format": "Format du rapport",
+ "ui_fmt_pdf": "PDF (rapport radiologique structuré)",
+ "ui_fmt_json": "JSON (données structurées)",
+ "ui_fmt_html": "HTML (interactif)",
+ "ui_consent_label": "J'accepte la transmission des données d'imagerie du patient au fournisseur cloud de LLM sélectionné à des fins d'analyse.",
+ "ui_consent_hint": "Requis pour les modèles cloud. Sans consentement, aucune donnée ne quitte votre machine.",
+ "ui_consent_required": "Veuillez consentir à la transmission cloud ou choisir le modèle local.",
+ "ui_start": "Lancer l'analyse",
+ "ui_results": "Résultats",
+ "ui_progress_waiting": "En attente du démarrage de l'analyse …",
+ "ui_prog_sending": "Envoi de la requête au serveur …",
+ "ui_no_results": "Pas encore de résultats. Configurez les étapes ci-dessus et lancez l'analyse.",
+ "ui_dl_pdf": "Rapport PDF",
+ "ui_dl_json": "Données JSON",
+ "ui_dl_images": "Images annotées",
+ "ui_dl_pending": "Disponible une fois l'analyse terminée",
+ "ui_footer_notice_title": "Pas un dispositif médical",
+ "ui_footer_research": "Réservé à la recherche et à la formation.",
+ "ui_footer_docs": "Documentation",
+ "ui_footer_project": "Projet",
+ "ui_footer_license": "Licence (Apache 2.0)",
+ "ui_footer_security": "Politique de sécurité",
+ "ui_footer_changelog": "Journal des modifications",
+ "ui_footer_intended": "Usage prévu",
+ "ui_footer_modelcard": "Fiche du modèle",
+ "ui_footer_github": "Dépôt GitHub",
+ "ui_version": "Version"
+}
diff --git a/src/medcheck/web/app.py b/src/medcheck/web/app.py
index 2f1b432..46364f1 100644
--- a/src/medcheck/web/app.py
+++ b/src/medcheck/web/app.py
@@ -21,12 +21,17 @@
from medcheck import __version__
from medcheck.core.config import Settings
+from medcheck.i18n import get_strings
TEMPLATES_DIR = Path(__file__).parent / "templates"
STATIC_DIR = Path(__file__).parent / "static"
_api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
+# UI locales offered by the web page's language switcher. Keep in sync with the
+# AnalyzeRequest.language pattern and the i18n catalogs (medcheck/i18n/*.json).
+UI_LANGUAGES = ("en", "de", "fr", "es")
+
class AnalyzeRequest(BaseModel):
"""Validated request body for POST /api/analyze."""
@@ -201,8 +206,22 @@ def health() -> dict[str, Any]:
return {"status": "ok", "version": __version__}
@app.get("/", response_class=HTMLResponse)
- def index(request: Request) -> Any:
- return templates.TemplateResponse(request, "index.html", {"version": __version__})
+ def index(request: Request, lang: str | None = None) -> Any:
+ # ?lang= switches the UI locale; anything unknown falls back to the
+ # configured default (and ultimately to English inside get_strings).
+ ui_lang = lang if lang in UI_LANGUAGES else settings.default_language
+ if ui_lang not in UI_LANGUAGES:
+ ui_lang = "en"
+ return templates.TemplateResponse(
+ request,
+ "index.html",
+ {
+ "version": __version__,
+ "t": get_strings(ui_lang),
+ "lang": ui_lang,
+ "languages": UI_LANGUAGES,
+ },
+ )
@app.post("/api/analyze")
def analyze(
diff --git a/src/medcheck/web/static/app.js b/src/medcheck/web/static/app.js
index dcd21ee..7e787aa 100644
--- a/src/medcheck/web/static/app.js
+++ b/src/medcheck/web/static/app.js
@@ -3,17 +3,26 @@
(function () {
'use strict';
- // Tab switching
- function showTab(n) {
+ var CLOUD_MODELS = ['claude', 'openai', 'gemini'];
+
+ // Wizard step switching (stepper buttons + prev/next buttons use data-goto)
+ function showStep(n) {
document.querySelectorAll('.tab-pane').forEach(function (p) {
p.classList.remove('active');
});
- document.querySelectorAll('.tab-btn').forEach(function (b) {
- b.classList.remove('active');
+ var pane = document.getElementById('pane-' + n);
+ if (pane) pane.classList.add('active');
+
+ document.querySelectorAll('.pdp-stepper-step').forEach(function (s) {
+ var step = parseInt(s.getAttribute('data-step'), 10);
+ s.classList.toggle('active', step === n);
+ s.classList.toggle('completed', step < n);
+ if (step === n) {
+ s.setAttribute('aria-current', 'step');
+ } else {
+ s.removeAttribute('aria-current');
+ }
});
- document.getElementById('pane-' + n).classList.add('active');
- var tabs = document.querySelectorAll('.tab-btn');
- if (tabs[n - 1]) tabs[n - 1].classList.add('active');
}
// File dropzone - use textContent for safe DOM updates
@@ -23,35 +32,124 @@
var subtext = document.getElementById('dropzoneSubtext');
var sizeMB = (f.size / 1024 / 1024).toFixed(2);
subtext.textContent = f.name + ' (' + sizeMB + ' MB)';
- document.getElementById('autoDetectBadge').style.display = 'inline-flex';
+ var badge = document.getElementById('autoDetectBadge');
+ if (badge) badge.classList.add('is-visible');
}
}
- function simulateProgress() {
+ function isCloudModel() {
+ var select = document.getElementById('modelSelect');
+ return !!select && CLOUD_MODELS.indexOf(select.value) !== -1;
+ }
+
+ function syncConsentVisibility() {
+ var block = document.getElementById('consentBlock');
+ if (block) block.classList.toggle('is-visible', isCloudModel());
+ }
+
+ // Replace the results area content with a kit alert (text set via textContent,
+ // never innerHTML — the server response echoes user input).
+ function showResultAlert(kind, text) {
+ var results = document.getElementById('resultsContent');
+ if (!results) return;
+ results.textContent = '';
+ results.classList.remove('results-empty');
+ var alert = document.createElement('div');
+ alert.className = 'alert alert-' + kind;
+ alert.setAttribute('role', 'alert');
+ var body = document.createElement('p');
+ body.textContent = text;
+ alert.appendChild(body);
+ results.appendChild(alert);
+ alert.style.marginBottom = '1.5rem';
+ }
+
+ function setProgress(percent, labelText) {
var fill = document.getElementById('progressFill');
var label = document.getElementById('progressLabel');
- var steps = [
- [10, 'Uploading data...'],
- [30, 'Preprocessing imaging data...'],
- [55, 'Running ML modules...'],
- [75, 'Querying AI model...'],
- [90, 'Generating report...'],
- [100, 'Done!']
- ];
- var i = 0;
- var tick = setInterval(function () {
- if (i >= steps.length) { clearInterval(tick); return; }
- fill.style.width = steps[i][0] + '%';
- label.textContent = steps[i][1];
- i++;
- }, 800);
+ if (fill) fill.style.width = percent + '%';
+ if (label && labelText) label.textContent = labelText;
+ }
+
+ function submitAnalysis(form) {
+ var startBtn = document.getElementById('startBtn');
+ var consent = document.getElementById('consentCheck');
+ var cloud = isCloudModel();
+
+ if (cloud && (!consent || !consent.checked)) {
+ showResultAlert('danger', form.getAttribute('data-msg-consent'));
+ showStep(3);
+ if (consent) consent.focus();
+ return;
+ }
+
+ var urlInput = document.getElementById('sourceUrl');
+ var fileInput = document.getElementById('fileInput');
+ var source = (urlInput && urlInput.value.trim()) ||
+ (fileInput && fileInput.files && fileInput.files[0] && fileInput.files[0].name) ||
+ 'browser-upload';
+
+ var anatomy = document.getElementById('anatomy');
+ var language = document.getElementById('reportLanguage');
+ var format = document.getElementById('reportFormat');
+
+ var body = {
+ source: source,
+ report_format: format ? format.value : 'json',
+ language: language ? language.value : 'en',
+ allow_cloud_llm: !!(cloud && consent && consent.checked)
+ };
+ if (anatomy && anatomy.value) body.anatomy = anatomy.value;
+
+ if (startBtn) startBtn.disabled = true;
+ setProgress(30, form.getAttribute('data-msg-sending'));
+
+ fetch(form.getAttribute('action'), {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body)
+ })
+ .then(function (resp) {
+ return resp.json().then(function (data) {
+ return { ok: resp.ok, status: resp.status, data: data };
+ });
+ })
+ .then(function (result) {
+ var detail = result.data && result.data.detail;
+ if (typeof detail !== 'string') detail = JSON.stringify(result.data);
+ if (result.ok) {
+ setProgress(100, '');
+ showResultAlert('success', detail);
+ } else {
+ setProgress(0, form.getAttribute('data-msg-waiting'));
+ // 501 = known preview limitation -> warning; anything else -> danger.
+ showResultAlert(result.status === 501 ? 'warning' : 'danger', detail);
+ }
+ })
+ .catch(function (err) {
+ setProgress(0, form.getAttribute('data-msg-waiting'));
+ showResultAlert('danger', String(err));
+ })
+ .then(function () {
+ if (startBtn) startBtn.disabled = false;
+ });
}
document.addEventListener('DOMContentLoaded', function () {
+ // Sticky header — must NOT be sticky at page load; scroll adds the class.
+ var siteHeader = document.getElementById('site-header');
+ if (siteHeader) {
+ var onScroll = function () {
+ siteHeader.classList.toggle('is-sticky', window.scrollY > 0);
+ };
+ window.addEventListener('scroll', onScroll, { passive: true });
+ onScroll();
+ }
+
// Any element with data-goto="N" switches to wizard step N.
document.querySelectorAll('[data-goto]').forEach(function (el) {
el.addEventListener('click', function () {
- showTab(parseInt(el.getAttribute('data-goto'), 10));
+ showStep(parseInt(el.getAttribute('data-goto'), 10));
});
});
@@ -59,6 +157,12 @@
var fileInput = document.getElementById('fileInput');
if (dropzone && fileInput) {
dropzone.addEventListener('click', function () { fileInput.click(); });
+ dropzone.addEventListener('keydown', function (e) {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ fileInput.click();
+ }
+ });
dropzone.addEventListener('dragover', function (e) {
e.preventDefault();
dropzone.classList.add('dragover');
@@ -78,9 +182,18 @@
fileInput.addEventListener('change', function () { fileSelected(fileInput); });
}
+ var modelSelect = document.getElementById('modelSelect');
+ if (modelSelect) {
+ modelSelect.addEventListener('change', syncConsentVisibility);
+ syncConsentVisibility();
+ }
+
var form = document.getElementById('analyzeForm');
if (form) {
- form.addEventListener('submit', function () { simulateProgress(); });
+ form.addEventListener('submit', function (e) {
+ e.preventDefault();
+ submitAnalysis(form);
+ });
}
});
})();
diff --git a/src/medcheck/web/static/favicon.svg b/src/medcheck/web/static/favicon.svg
new file mode 100644
index 0000000..2e66084
--- /dev/null
+++ b/src/medcheck/web/static/favicon.svg
@@ -0,0 +1 @@
+
diff --git a/src/medcheck/web/templates/index.html b/src/medcheck/web/templates/index.html
index 6252501..59d66b6 100644
--- a/src/medcheck/web/templates/index.html
+++ b/src/medcheck/web/templates/index.html
@@ -1,669 +1,1353 @@
-
+
- MedCheck – AI Medical Imaging Analysis
+ {{ t.ui_title }}
+
+
-
-