Spaces:
Sleeping
Sleeping
File size: 17,495 Bytes
b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
"""OphthalmoCapture — Internationalization (i18n)
Centralized UI strings with session-state-based language selection.
All components call t(key) to get translated strings.
"""
import streamlit as st
SUPPORTED_LANGUAGES = {"es": "Español", "en": "English"}
DEFAULT_LANGUAGE = "es"
def _get_lang() -> str:
"""Return the active UI language code from session state."""
return st.session_state.get("ui_language", DEFAULT_LANGUAGE)
_STRINGS = {
"es": {
# App
"app_subtitle": "Sistema de Etiquetado Médico Oftalmológico",
# Sidebar
"settings": "⚙️ Configuración",
"doctor_name": "👨⚕️ Nombre del Doctor",
"whisper_model": "Modelo Whisper",
"dictation_language": "Idioma de dictado",
"current_session": "📊 Sesión Actual",
"db_type": "Base de datos",
"images_loaded": "Imágenes cargadas",
"labeled_count": "Etiquetadas",
"no_images": "No hay imágenes en la sesión.",
"history": "🗄️ Historial",
"search_image": "🔍 Buscar por imagen",
"no_records": "Sin registros.",
"label_header": "Etiqueta",
"doctor_header": "Doctor",
"no_transcription": "Sin transcripción",
"end_session": "� Cerrar sesión",
"undownloaded_warning": "⚠️ Datos no descargados",
"timeout_in": "⏱️ Timeout en",
"confirm_delete": "¿Está seguro? **Se cerrará la sesión y todos los datos se eliminarán permanentemente.**",
"yes_delete": "✅ Sí, cerrar sesión",
"cancel": "❌ Cancelar",
"logout": "🚪 Cerrar sesión",
# Upload
"upload_images": "📤 Subir imágenes médicas",
"upload_help_formats": "Formatos aceptados",
"upload_help_max": "Máx.",
"invalid_files": "archivo(s) no son imágenes válidas y fueron ignorados.",
"duplicate_files": "archivo(s) duplicados fueron omitidos.",
"upload_prompt": "📤 Suba imágenes médicas para comenzar el etiquetado.",
# Gallery
"progress": "Progreso",
"labeled_suffix": "etiquetadas",
"page": "Página",
# Labeler
"labeling": "🏷️ Etiquetado",
"select_label": "— Seleccione una etiqueta —",
"classification": "Clasificación de la imagen",
"unlabeled": "🔴 Sin etiquetar",
"label_set": "🟢 Etiqueta",
"code": "código",
"save_label": "💾 Guardar etiqueta en historial",
"select_before_save": "Seleccione una etiqueta antes de guardar.",
"label_saved": "✅ Etiqueta guardada en la base de datos.",
"save_error": "Error al guardar",
# Recorder
"dictation": "🎙️ Dictado y Transcripción",
"record_audio": "Grabar audio",
"transcribing": "Transcribiendo audio…",
"transcription_editable": "Transcripción (editable)",
"transcription_placeholder": "Grabe un audio o escriba la transcripción manualmente…",
"segments_timestamps": "🕐 Segmentos con timestamps",
"restore_original": "🔄 Restaurar original",
"clear_text": "🗑️ Limpiar texto",
"words": "palabras",
"manually_modified": "✏️ _modificada manualmente_",
"no_transcription_yet": "Sin transcripción aún.",
# Downloader
"download": "📥 Descarga",
"current_image": "Imagen actual",
"label_to_enable": "Etiquete la imagen para habilitar la descarga individual.",
"download_label": "⬇️ Descargar etiquetado",
"full_session": "Toda la sesión",
"images_metric": "Imágenes",
"with_audio": "Con audio",
"labeled_metric": "Etiquetadas",
"with_transcription": "Con transcripción",
"unlabeled_warning": "imagen(es) sin etiquetar. Se incluirán en la descarga pero sin etiqueta.",
"no_images_download": "No hay imágenes para descargar.",
"download_all": "⬇️ Descargar todo el etiquetado (ZIP)",
"ml_formats": "Formatos para ML",
"hf_csv": "📊 CSV (HuggingFace)",
"jsonl_finetune": "📄 JSONL (Fine-tuning)",
# Nav
"previous": "⬅️ Anterior",
"next": "Siguiente ➡️",
"delete_image": "🗑️ Eliminar esta imagen",
# Timeout
"session_expired_data": "⏰ Sesión expirada por inactividad",
"session_expired_clean": "⏰ Sesión expirada por inactividad. Se inició una nueva sesión.",
"download_before_expire": "Descargue sus datos antes de que expire la sesión la próxima vez.",
# Auth
"login_prompt": "👨⚕️ Inicie sesión para acceder al sistema de etiquetado.",
"login_error": "❌ Usuario o contraseña incorrectos.",
# i18n
"ui_language": "🌐 Idioma / Language",
"loading_whisper": "Cargando modelo Whisper '{model}'...",
# Session expiry with placeholders
"session_expired": "⏰ Sesión expirada por inactividad ({minutes} min). Se eliminaron **{total}** imágenes, **{labeled}** etiquetadas, **{with_audio}** con audio. Descargue sus datos antes de que expire la sesión la próxima vez.",
"db_error": "Error crítico de base de datos: {error}",
"history_error": "Error al obtener historial: {error}",
# Labeler
"select_label_hint": "⬇️ Seleccione una etiqueta para esta imagen",
"locs_title": "**Clasificación LOCS III**",
"locs_placeholder": "Seleccionar…",
"locs_progress": "📋 LOCS: {filled}/{total} campos completados",
"locs_complete": "✅ LOCS: {filled}/{total} campos completados",
# Recorder
"re_record": "🎤 Volver a grabar",
"word_count": "{count} palabras",
# Downloader
"single_download": "📥 Descarga individual",
"session_info": "📊 Información de sesión",
"bulk_download": "📦 Descargar todo el etiquetado",
"download_all_zip": "⬇️ Descargar todo el etiquetado (ZIP)",
"download_file": "⬇️ Descargar — {filename}",
"incomplete_fields_msg": "La imagen **{filename}** tiene campos sin completar:",
"missing_categorical": "Etiqueta categórica",
"missing_locs": "LOCS III – {field}",
"missing_voice": "Etiquetado por voz",
"download_anyway": "⬇️ Descargar igualmente",
"go_back_finish": "🔙 Regresar y terminar",
"bulk_incomplete_msg": "**{count} imagen(es)** tienen etiquetado incompleto:",
"col_image": "Imagen",
"col_categorical": "Categórica",
"col_locs": "LOCS III",
"col_voice": "Voz",
"locs_not_required": "No Necesario",
"image_counter": "{current} de {total}",
# Gallery
"gallery_prev": "◀ Ant.",
"gallery_next": "Sig. ▶",
# Uploader
"relabel_dialog_msg": "**{count} imagen(es)** ya fueron etiquetadas anteriormente. Seleccione cuáles desea volver a etiquetar.",
"relabel_new_info": "ℹ️ Las otras **{count}** imagen(es) nuevas se subirán automáticamente.",
"accept_upload": "✅ Aceptar y subir",
"cancel_labeled": "❌ Cancelar etiquetadas",
"duplicates_dialog_msg": "Las siguientes imágenes **ya se encuentran en la sesión actual** y no se volverán a subir:",
"accept": "Aceptar",
# Dialog titles
"dlg_single_incomplete": "⚠️ Etiquetado incompleto",
"dlg_bulk_incomplete": "⚠️ Imágenes con etiquetado incompleto",
"dlg_relabel": "⚠️ Imágenes ya etiquetadas",
"dlg_duplicates": "ℹ️ Imágenes duplicadas en sesión",
# Uploader badge
"times_badge": "{n} vez",
"times_badge_plural": "{n} veces",
},
"en": {
"app_subtitle": "Ophthalmological Medical Labeling System",
"settings": "⚙️ Settings",
"doctor_name": "👨⚕️ Doctor Name",
"whisper_model": "Whisper Model",
"dictation_language": "Dictation Language",
"current_session": "📊 Current Session",
"db_type": "Database",
"images_loaded": "Images loaded",
"labeled_count": "Labeled",
"no_images": "No images in session.",
"history": "🗄️ History",
"search_image": "🔍 Search by image",
"no_records": "No records.",
"label_header": "Label",
"doctor_header": "Doctor",
"no_transcription": "No transcription",
"end_session": "� Log out",
"undownloaded_warning": "⚠️ Undownloaded data",
"timeout_in": "⏱️ Timeout in",
"confirm_delete": "Are you sure? **The session will be closed and all data permanently deleted.**",
"yes_delete": "✅ Yes, log out",
"cancel": "❌ Cancel",
"logout": "🚪 Log out",
"upload_images": "📤 Upload medical images",
"upload_help_formats": "Accepted formats",
"upload_help_max": "Max.",
"invalid_files": "file(s) are not valid images and were ignored.",
"duplicate_files": "duplicate file(s) were skipped.",
"upload_prompt": "📤 Upload medical images to start labeling.",
"progress": "Progress",
"labeled_suffix": "labeled",
"page": "Page",
"labeling": "🏷️ Labeling",
"select_label": "— Select a label —",
"classification": "Image classification",
"unlabeled": "🔴 Unlabeled",
"label_set": "🟢 Label",
"code": "code",
"save_label": "💾 Save label to history",
"select_before_save": "Select a label before saving.",
"label_saved": "✅ Label saved to database.",
"save_error": "Save error",
"dictation": "🎙️ Dictation & Transcription",
"record_audio": "Record audio",
"transcribing": "Transcribing audio…",
"transcription_editable": "Transcription (editable)",
"transcription_placeholder": "Record audio or type the transcription manually…",
"segments_timestamps": "🕐 Segments with timestamps",
"restore_original": "🔄 Restore original",
"clear_text": "🗑️ Clear text",
"words": "words",
"manually_modified": "✏️ _manually modified_",
"no_transcription_yet": "No transcription yet.",
"download": "📥 Download",
"current_image": "Current image",
"label_to_enable": "Label the image to enable individual download.",
"download_label": "⬇️ Download labeling",
"full_session": "Full session",
"images_metric": "Images",
"with_audio": "With audio",
"labeled_metric": "Labeled",
"with_transcription": "With transcription",
"unlabeled_warning": "unlabeled image(s). They will be included in the download without a label.",
"no_images_download": "No images to download.",
"download_all": "⬇️ Download all labeling (ZIP)",
"ml_formats": "ML Formats",
"hf_csv": "📊 CSV (HuggingFace)",
"jsonl_finetune": "📄 JSONL (Fine-tuning)",
"previous": "⬅️ Previous",
"next": "Next ➡️",
"delete_image": "🗑️ Delete this image",
"session_expired_data": "⏰ Session expired due to inactivity",
"session_expired_clean": "⏰ Session expired. A new session has started.",
"download_before_expire": "Download your data before the session expires next time.",
"login_prompt": "👨⚕️ Log in to access the labeling system.",
"login_error": "❌ Wrong username or password.",
"ui_language": "🌐 Language / Idioma",
"loading_whisper": "Loading Whisper model '{model}'...",
"session_expired": "⏰ Session expired due to inactivity ({minutes} min). Removed **{total}** images, **{labeled}** labeled, **{with_audio}** with audio. Download your data before the session expires next time.",
"db_error": "Critical database error: {error}",
"history_error": "Error fetching history: {error}",
"select_label_hint": "⬇️ Select a label for this image",
"locs_title": "**LOCS III Classification**",
"locs_placeholder": "Select…",
"locs_progress": "📋 LOCS: {filled}/{total} fields completed",
"locs_complete": "✅ LOCS: {filled}/{total} fields completed",
"re_record": "🎤 Re-record",
"word_count": "{count} words",
"single_download": "📥 Individual Download",
"session_info": "📊 Session Information",
"bulk_download": "📦 Download All Labeling",
"download_all_zip": "⬇️ Download all labeling (ZIP)",
"download_file": "⬇️ Download — {filename}",
"incomplete_fields_msg": "Image **{filename}** has incomplete fields:",
"missing_categorical": "Categorical label",
"missing_locs": "LOCS III – {field}",
"missing_voice": "Voice labeling",
"download_anyway": "⬇️ Download anyway",
"go_back_finish": "🔙 Go back and finish",
"bulk_incomplete_msg": "**{count} image(s)** have incomplete labeling:",
"col_image": "Image",
"col_categorical": "Categorical",
"col_locs": "LOCS III",
"col_voice": "Voice",
"locs_not_required": "Not Required",
"image_counter": "{current} of {total}",
"gallery_prev": "◀ Prev",
"gallery_next": "Next ▶",
"relabel_dialog_msg": "**{count} image(s)** were previously labeled. Select which ones to re-label.",
"relabel_new_info": "ℹ️ The other **{count}** new image(s) will be uploaded automatically.",
"accept_upload": "✅ Accept and upload",
"cancel_labeled": "❌ Cancel labeled",
"duplicates_dialog_msg": "The following images **are already in the current session** and will not be re-uploaded:",
"accept": "Accept",
"dlg_single_incomplete": "⚠️ Incomplete labeling",
"dlg_bulk_incomplete": "⚠️ Images with incomplete labeling",
"dlg_relabel": "⚠️ Previously labeled images",
"dlg_duplicates": "ℹ️ Duplicate images in session",
"times_badge": "{n} time",
"times_badge_plural": "{n} times",
},
}
def t(key: str, **kwargs) -> str:
"""Return the translated string for *key*, with optional format kwargs."""
lang = _get_lang()
text = _STRINGS.get(lang, _STRINGS["es"]).get(key, key)
if kwargs:
try:
text = text.format(**kwargs)
except (KeyError, IndexError):
pass
return text
# ── Label display translations ───────────────────────────────────────────────
# Labels are stored in English (config.LABEL_OPTIONS["display"]).
# These mappings translate for UI display only.
_LABEL_DISPLAY = {
"es": {
"Normal": "Normal",
"Cataract": "Catarata",
"Bad quality": "Mala calidad",
"Needs dilation": "Necesita dilatación",
},
"en": {
"Normal": "Normal",
"Cataract": "Cataract",
"Bad quality": "Bad quality",
"Needs dilation": "Needs dilation",
},
}
def label_display(english_name: str) -> str:
"""Translate a label's English display name to the active UI language."""
lang = _get_lang()
return _LABEL_DISPLAY.get(lang, _LABEL_DISPLAY["en"]).get(english_name, english_name)
def label_from_display(translated_name: str) -> str | None:
"""Reverse-map a translated label back to its English storage name."""
lang = _get_lang()
mapping = _LABEL_DISPLAY.get(lang, _LABEL_DISPLAY["en"])
reverse = {v: k for k, v in mapping.items()}
return reverse.get(translated_name)
# ── LOCS display translations ────────────────────────────────────────────────
_LOCS_DISPLAY = {
"es": {
"Nuclear Cataract \u2013 Opalescence (NO)": "Catarata Nuclear \u2013 Opalescencia (NO)",
"Nuclear Cataract \u2013 Color (NC)": "Catarata Nuclear \u2013 Color (NC)",
"Cortical Cataract (C)": "Catarata Cortical (C)",
"None / Clear": "Ninguna / Transparente",
"Very mild": "Muy leve",
"Mild": "Leve",
"Mild\u2013moderate": "Leve\u2013moderada",
"Moderate": "Moderada",
"Moderate\u2013severe": "Moderada\u2013severa",
"Severe": "Severa",
"Very mild yellowing": "Amarillamiento muy leve",
"Mild yellowing": "Amarillamiento leve",
"Moderate yellow": "Amarillo moderado",
"Yellow\u2013brown": "Amarillo\u2013marrón",
"Brown": "Marrón",
"Dark brown": "Marrón oscuro",
"None": "Ninguna",
"Peripheral spokes only": "Solo radios periféricos",
"Mild peripheral involvement": "Compromiso periférico leve",
"Moderate spokes approaching center": "Radios moderados acercándose al centro",
"Central involvement": "Compromiso central",
"Severe / dense central spokes": "Severa / radios centrales densos",
},
"en": {},
}
def locs_display(english_text: str) -> str:
"""Translate a LOCS field label or option to the active UI language."""
lang = _get_lang()
if lang == "en":
return english_text
return _LOCS_DISPLAY.get(lang, {}).get(english_text, english_text)
|