{
  "common": {
    "ok": "OK",
    "cancel": "Cancelar",
    "save": "Guardar",
    "delete": "Eliminar",
    "edit": "Editar",
    "back": "Atrás",
    "done": "Hecho",
    "close": "Cerrar",
    "retry": "Reintentar",
    "copy": "Copiar",
    "enabled": "Activado",
    "refresh": "Actualizar",
    "clear": "Limpiar"
  },
  "auth": {
    "signInTitle": "Iniciar sesión",
    "changeServer": "Cambiar",
    "username": "Usuario",
    "password": "Contraseña",
    "signIn": "Iniciar sesión",
    "signingIn": "Iniciando sesión…",
    "subtitle": "Usa tus credenciales de operador.",
    "errorRequired": "El usuario y la contraseña son obligatorios",
    "errorGeneric": "Error al iniciar sesión: {error}",
    "errorFallback": "Error al iniciar sesión"
  },
  "nav": {
    "sessions": "Sessions",
    "memory": "Memoria",
    "notes": "Notas",
    "more": "Más",
    "activity": "Actividad",
    "providers": "Proveedores",
    "channels": "Canales",
    "integrations": "Integraciones",
    "plugins": "Plugins",
    "backups": "Copias de seguridad",
    "settings": "Ajustes",
    "workspace": "Espacio de trabajo",
    "updateAvailable": "Actualización disponible"
  },
  "web": {
    "brand": "opendray",
    "loading": "Cargando…",
    "topbar": {
      "expandSidebar": "Expandir barra lateral",
      "collapseSidebar": "Contraer barra lateral",
      "search": "Buscar",
      "openPalette": "Abrir paleta de comandos",
      "theme": "Tema",
      "themeLabel": "Tema: {mode}",
      "appearance": "Apariencia",
      "themeLight": "Claro",
      "themeDark": "Oscuro",
      "themeSystem": "Sistema",
      "language": "Idioma",
      "languageEnglish": "English",
      "languageChinese": "中文",
      "languageSpanish": "Español",
      "signedInAs": "Sesión iniciada como",
      "tokenExpires": "El token caduca",
      "signOut": "Cerrar sesión"
    },
    "sessions": {
      "list": {
        "title": "Sesiones",
        "countSeparator": "·",
        "newAria": "Crear nueva session",
        "newTooltip": "Nueva session",
        "loading": "Cargando…",
        "emptyTitle": "Aún no hay sesiones.",
        "emptyHint": "Pulsa {kbd} para crear una.",
        "endedHeader": "Finalizadas ({count})",
        "clearAll": "Borrar todas",
        "confirmClearAll": "¿Eliminar las {count} sesiones finalizadas?",
        "confirmTerminate": "¿Terminar y eliminar {name}?",
        "childPromoted": " {count} session de tarea secundaria pasará al nivel superior.",
        "childPromotedPlural": " {count} sesiones de tarea secundaria pasarán al nivel superior.",
        "footer": "{live} activas · {ended} finalizadas",
        "row": {
          "deleteAria": "Eliminar session",
          "titleRemoveHistory": "Quitar del historial",
          "titleTerminate": "Terminar y eliminar",
          "titleRemove": "Eliminar",
          "claudeAccountTitle": "Cuenta de Claude: {label}"
        },
        "deleteFailedToast": "Error al eliminar"
      },
      "tabs": {
        "closeAria": "Cerrar pestaña y eliminar session",
        "closeTitle": "Cerrar pestaña y eliminar session"
      },
      "page": {
        "removedToast": "Session eliminada",
        "removeFailedToast": "Error al eliminar",
        "stoppedToast": "Session detenida",
        "stopFailedToast": "Error al detener",
        "restartedToast": "Session reiniciada",
        "restartFailedToast": "Error al reiniciar",
        "confirmCloseTabTitle": "¿Detener y eliminar \"{name}\"?",
        "confirmCloseTabDescription": "El proceso de la CLI se terminará y la fila se eliminará.",
        "confirmCloseTabConfirm": "Detener y eliminar",
        "confirmRemoveTitle": "¿Eliminar {name}?",
        "confirmRemoveTitleFallback": "¿Eliminar session?",
        "confirmRemoveDescription": "Esto elimina la fila.",
        "confirmRemoveConfirm": "Eliminar"
      },
      "empty": {
        "title": "Ninguna session abierta",
        "hint": "Elige una session de la lista o crea una nueva. Teclado: {kbdN} nueva, {kbdW} cerrar, {kbdRange} cambiar.",
        "spawn": "Crear session"
      },
      "header": {
        "loadingSession": "Cargando session…",
        "showList": "Mostrar lista de sesiones",
        "hideList": "Ocultar lista de sesiones",
        "showInspector": "Mostrar inspector",
        "hideInspector": "Ocultar inspector",
        "attachImage": "Adjuntar imagen",
        "attachImageTooltip": "Adjuntar imagen (o pega / suelta en el terminal)",
        "copyOutput": "Copiar salida",
        "copyOutputTooltip": "Copia la salida del terminal (la selección si hay alguna, si no, todo)",
        "restart": "Reiniciar",
        "restarting": "Reiniciando…",
        "remove": "Eliminar",
        "removing": "Eliminando…",
        "stop": "Detener",
        "stopping": "Deteniendo…",
        "pid": "pid {pid}"
      },
      "terminal": {
        "uploadingToast": "Subiendo imagen…",
        "uploadedToast": "Imagen adjuntada",
        "uploadFailedToast": "Error al subir",
        "uploadInvalidTypeToast": "Solo se pueden adjuntar archivos de imagen",
        "dropToAttach": "Suelta la imagen para adjuntarla",
        "copyButton": "Copiar",
        "copyAllTooltip": "Copia la salida del terminal al portapapeles (la selección si hay alguna, si no, todo)",
        "copySelection": "Copiar",
        "copySelectionTooltip": "Copiar el texto seleccionado",
        "copiedToast": "Copiado al portapapeles",
        "copyEmptyToast": "Aún no hay nada que copiar",
        "copyFailedToast": "No se pudo copiar al portapapeles",
        "urls": {
          "tooltip": "Abre el último enlace detectado en esta session",
          "tapToOpenLatest": "Toca para abrir el último enlace (la URL de OAuth más reciente)",
          "openListTooltip": "Mostrar todos los enlaces",
          "buttonLabel": "{count} enlace",
          "buttonLabel_plural": "{count} enlaces",
          "dialogTitle": "Enlaces detectados",
          "dialogDesc": "URLs impresas en la salida de esta session, las más recientes primero. Toca Abrir para lanzarla en tu navegador predeterminado. Funciona incluso cuando la URL se divide en varias líneas en el terminal.",
          "openButton": "Abrir",
          "copyButton": "Copiar",
          "copiedToast": "URL copiada",
          "copyFailedToast": "No se pudo copiar. Mantén pulsada la URL y cópiala manualmente",
          "noneHint": "Aún no se han detectado enlaces."
        }
      },
      "spawn": {
        "title": "Crear session",
        "description": "Inicia una session de la CLI con un proveedor registrado.",
        "provider": "Proveedor",
        "claudeAccount": "Cuenta de Claude",
        "loadingAccounts": "Cargando cuentas…",
        "noAccounts": "No se han encontrado cuentas de Claude. Crea esta session y ejecuta <1>claude login</1> en el terminal. Las credenciales acaban en <3>~/.claude</3> en el gateway y aparecen automáticamente la próxima vez.",
        "default": "Predeterminada",
        "defaultTooltip": "Usar el keychain del sistema / env",
        "tokenEmptyBadge": "·vacío",
        "tokenMissingTooltip": "No hay token configurado. Configura el token primero en el panel de Proveedores",
        "multiAccountHint": "Hay varias cuentas configuradas. Elige una para esta session.",
        "cwd": "Directorio de trabajo",
        "cwdPlaceholder": "/Users/you/projects/foo",
        "browse": "Examinar",
        "nameLabel": "Nombre (opcional)",
        "namePlaceholder": "claude in pet-tracker",
        "argsLabel": "Argumentos de la CLI (uno por línea)",
        "bypassClaude": "Omitir las confirmaciones de permisos",
        "bypassCodex": "Omitir aprobaciones y sandbox (--dangerously-bypass-approvals-and-sandbox)",
        "bypassGemini": "Modo YOLO (--yolo)",
        "bypassOnHint": "Esta session se ejecutará con autonomía elevada.",
        "bypassOffHint": "Desactivado. Las confirmaciones y los prompts se comportan con normalidad.",
        "errorPickProvider": "Elige un proveedor.",
        "errorCwdRequired": "cwd es obligatorio.",
        "cancel": "Cancelar",
        "submit": "Crear",
        "submitting": "Creando…",
        "spawnedToast": "Session creada",
        "spawnedDescription": "{provider} · pid {pid}",
        "pidFallback": "—"
      },
      "accountSwitcher": {
        "tooltip": "Cambiar de cuenta de Claude (reinicia el proceso de la CLI)",
        "currentDefault": "predeterminada",
        "menuTitle": "Cambiar de cuenta de Claude",
        "defaultName": "Predeterminada",
        "defaultSubtitle": "keychain del sistema / env de la CLI",
        "tokenEmpty": "·vacío",
        "confirmSwitch": "Cambiar de cuenta reiniciará la CLI de Claude. El historial de la conversación se conserva (el transcript se migra y --resume lo reproduce con la nueva cuenta), pero se perderá cualquier ejecución de herramienta en curso o entrada sin enviar. ¿Continuar?",
        "switchedToast": "Cuenta cambiada",
        "switchedDescription": "Ahora usando @{account} · pid {pid}",
        "switchedDefault": "predeterminada",
        "switchFailedToast": "Error al cambiar"
      },
      "inspector": {
        "tabs": {
          "files": "Archivos",
          "git": "Git",
          "search": "Buscar",
          "tasks": "Tareas",
          "history": "Historial",
          "notes": "Notas",
          "memory": "Memoria"
        }
      },
      "ended": {
        "bufferUnavailable": "[búfer no disponible]",
        "readOnlyBanner": "[session finalizada. búfer de solo lectura]"
      },
      "fileBrowser": {
        "title": "Elige el directorio de trabajo",
        "description": "Examina el sistema de archivos del host del gateway y elige una carpeta.",
        "parent": "Directorio superior",
        "home": "Directorio personal",
        "refresh": "Actualizar",
        "pathPlaceholder": "/Users/you/projects",
        "loading": "Cargando…",
        "empty": "Directorio vacío.",
        "newFolder": "Nueva carpeta",
        "newFolderPlaceholder": "nombre-de-la-carpeta",
        "create": "Crear",
        "cancel": "Cancelar",
        "useThisFolder": "Usar esta carpeta",
        "createdToast": "Directorio creado",
        "mkdirFailedToast": "Error al crear el directorio",
        "homeFailedToast": "Error al leer el directorio personal"
      }
    },
    "memory": {
      "title": "Memoria",
      "subtitle": "Explora, busca y edita las memorias que los agentes han almacenado a través del servidor MCP opendray-memory.",
      "navProject": "Proyecto",
      "navArchived": "Archivadas",
      "navWorkers": "Workers",
      "navConfiguration": "Configuración →"
    },
    "journalStale": {
      "title": "Purgar entradas obsoletas",
      "subtitle": "(con más de {days} días, sin conflictos pendientes)",
      "daysLabel": "Con más de (días):",
      "loading": "Escaneando…",
      "empty": "No hay nada obsoleto que purgar.",
      "selectAll": "Seleccionar todo",
      "deselectAll": "Deseleccionar todo",
      "deleteSelected": "Eliminar ({count})",
      "deleted_one": "{count} entrada eliminada",
      "deleted_other": "{count} entradas eliminadas"
    },
    "conflicts": {
      "title": "Conflictos entre capas",
      "subtitle": "Contradicciones que el detector diario encontró entre hechos, plan, objetivo y entradas del diario.",
      "loading": "Cargando conflictos…",
      "empty": "No hay conflictos pendientes. Haz clic en \"Detectar ahora\" para ejecutar un análisis bajo demanda.",
      "pickCwd": "Elige un proyecto para ver sus conflictos.",
      "detectNow": "Detectar ahora",
      "detected": "Se encontraron {count} conflicto(s) nuevo(s)",
      "accept": "Aceptar",
      "dismiss": "Descartar",
      "accepted": "Conflicto aceptado. Recuerda aplicar la corrección",
      "dismissed": "Conflicto descartado",
      "deletedFact": "Hecho eliminado y conflicto aceptado",
      "quickActions": "Corrección:",
      "deleteFact": "Eliminar hecho",
      "deleteFactSide": "Eliminar {side}: {ref}",
      "confirmDelete": {
        "title": "¿Eliminar el hecho {side}?",
        "description": "Esto elimina el hecho de forma permanente y acepta el conflicto. El otro lado se conserva como la afirmación superviviente.",
        "targetLabel": "Se eliminará (lado {side}):",
        "keepLabel": "Se conservará (lado {side}):",
        "nonFactOther": "(entrada de {layer}, abre la pestaña correspondiente para inspeccionar)",
        "evidenceLabel": "Evidencia del detector:",
        "loading": "Cargando texto del hecho…",
        "loadError": "No se pudo cargar el texto del hecho. Inspecciónalo en la página de Memoria.",
        "cancel": "Cancelar",
        "confirm": "Eliminar {side}"
      },
      "openLayer": {
        "plan": "Abrir editor de plan",
        "goal": "Abrir editor de objetivo"
      },
      "severity": {
        "low": "baja",
        "medium": "media",
        "high": "alta"
      }
    },
    "memoryHealth": {
      "title": "Estado de la memoria, últimos {days} días",
      "subtitle": "Señales agregadas de ambos subsistemas de memoria de este proyecto.",
      "loading": "Cargando instantánea de estado…",
      "errorLoading": "No se pudo cargar la instantánea de estado.",
      "pickCwd": "Elige un proyecto para ver el estado de su memoria.",
      "newFacts": "Datos nuevos",
      "newFactsHint": "{total} almacenados en total",
      "captureFires": "Capturas activadas",
      "captureFiresHint": "{stored} almacenadas · {deduped} deduplicadas",
      "newJournal": "Entradas de diario",
      "newJournalHint": "{total} en total",
      "planAge": "Plan actualizado por última vez",
      "planAgeHint": "{count} propuesta(s) de desvío de plan pendiente(s)",
      "planAgeHintNone": "No hay propuestas de desvío de plan pendientes",
      "goalAge": "Objetivo actualizado por última vez",
      "pending": "Propuestas pendientes",
      "pendingHint": "la más antigua, {days}d de antigüedad",
      "topHit": "Más consultado · {hits} recuperaciones",
      "zeroHit": "{count} datos con más de 7d sin ninguna recuperación, candidatos para limpieza.",
      "never": "nunca",
      "today": "hoy",
      "daysAgo_one": "hace {count} día",
      "daysAgo_other": "hace {count} días"
    },
    "memoryConfig": {
      "title": "Configuración de memoria",
      "subtitle": "Todos los ajustes relacionados con la memoria en un solo lugar: providers HTTP, enrutamiento de workers por tarea, disparadores de captura, inyección al iniciar y costes de auditoría.",
      "sections": {
        "providers": "Providers",
        "workers": "Workers",
        "rules": "Reglas de captura",
        "profiles": "Perfiles de inyección",
        "costs": "Coste en tokens"
      },
      "sectionHints": {
        "providers": "Endpoints HTTP registrados (Ollama / LM Studio / Anthropic / OpenAI / Integration) a los que cualquier tarea puede despachar.",
        "workers": "Para cada punto de contacto elige un provider HTTP (barato, local) o un Agent headless de Claude / Gemini (mayor calidad, consume tokens de CLI).",
        "rules": "Cuándo se activa el motor de captura en cada session (tras N mensajes / al estar inactivo / K caracteres / manual). Las reglas sin un provider fijado siguen el ajuste del worker de Captura de arriba.",
        "profiles": "Cómo se inyectan las memorias previas en el prompt del sistema del agente al iniciar la session (recencia, relevancia, híbrido o desactivado).",
        "costs": "Gasto agregado reconstruido a partir de memory_summarizer_calls. Los providers locales (Ollama, LM Studio, Integration) son gratuitos; los providers en la nube muestran el coste real."
      },
      "moveBanner": {
        "title": "La configuración de memoria se ha movido",
        "body": "Todos los ajustes relacionados con la memoria (providers / reglas de captura / perfiles de inyección / coste) ahora conviven con Workers en una sola página para que los ajustes relacionados estén juntos.",
        "openButton": "Abrir Configuración de memoria →"
      }
    },
    "memoryWorkers": {
      "title": "Workers de memoria",
      "loading": "Cargando configuración de workers…",
      "errorTitle": "No se puede acceder al endpoint.",
      "errorDescription": "Las rutas /api/v1/memory/workers son nuevas en M25. Puede que el binario de opendray necesite un reinicio para montarlas y ejecutar la migración 0029.",
      "intro": "Cada punto de contacto del LLM con el sistema de memoria puede atenderse de forma independiente mediante el endpoint local <1>summarizer</1> (LM Studio / compatible con OpenAI) o lanzando un <3>agente Claude / Gemini</3> sin interfaz en modo <5>--print</5>. Las tareas narrativas de alta calidad (gitactivity, transcript) se benefician de los workers de agente; las tareas de alta frecuencia (gatekeeper) permanecen en el endpoint local por diseño.",
      "enabledBadge": "habilitado",
      "disabledBadge": "deshabilitado",
      "summarizerOnlyBadge": "solo-summarizer",
      "callsCount": "{count} llamadas · 24h",
      "avgMs": "media {ms}ms",
      "errorsCount": "{count} errores",
      "workerLabel": "Worker",
      "summarizerHttp": "Summarizer (HTTP)",
      "agentCliPrint": "Agente (CLI --print)",
      "summarizerProviderLabel": "Proveedor del summarizer",
      "registryDefault": "Predeterminado del registro",
      "cliLabel": "CLI",
      "selectPlaceholder": "Seleccionar",
      "cliClaude": "Claude",
      "cliGemini": "Gemini",
      "claudeAccountLabel": "Cuenta de Claude",
      "claudeAccountDefault": "Predeterminada",
      "agentWarning": "El modo agente lanza un CLI sin interfaz por cada llamada. La latencia sube de <1>~1s</1> (summarizer) a <3>~5-15s</3>; el coste pasa de la CPU a tu quota de Claude/Gemini.",
      "enabledCheckbox": "Habilitado",
      "testButton": "Probar",
      "saveButton": "Guardar",
      "recentCalls": "Llamadas recientes ({count})",
      "tableWhen": "cuándo",
      "tableWorker": "worker",
      "tableMs": "ms",
      "tableOk": "ok",
      "savedToast": "{label} actualizado",
      "saveFailedToast": "Error al guardar",
      "testOkToast": "{label} OK. {ms}ms",
      "testFailedToast": "{label} falló",
      "testCallFailedToast": "La llamada de prueba falló",
      "unknownError": "error desconocido",
      "tasks": {
        "gatekeeper": {
          "label": "Gatekeeper",
          "description": "Filtro previo a la escritura en cada memory_store. Alta frecuencia (objetivo <500ms), solo-summarizer."
        },
        "cleaner": {
          "label": "Bibliotecario de limpieza",
          "description": "Bibliotecario LLM periódico. Evalúa los recuerdos antiguos como conservar / obsoleto / duplicado."
        },
        "gitactivity": {
          "label": "Resumidor de actividad de git",
          "description": "git log → narrativa de 2-3 párrafos cada 24h. Encaja de forma natural con un worker de agente."
        },
        "transcript": {
          "label": "Resumidor de transcript de sesión",
          "description": "Resumen al final de la sesión sobre \"qué hizo el agente\". Encaja de forma natural con un worker de agente."
        },
        "plan_drift": {
          "label": "Detector de desviación del plan",
          "description": "Al terminar cada sesión, comprueba si el plan del proyecto necesita actualizarse y presenta una propuesta. Encaja con un worker de agente para un razonamiento más completo."
        },
        "conflict_detector": {
          "label": "Detector de conflictos entre capas",
          "description": "Escaneo diario que encuentra contradicciones entre hechos / plan / objetivo / diario. Un modelo de mayor calidad = menos falsos positivos."
        },
        "capture": {
          "label": "Motor de captura",
          "description": "Extracción de hechos por cada trigger a partir de los transcripts de sesión. El modo agente ofrece hechos notablemente mejores en sesiones largas; el modo summarizer es barato y local."
        }
      }
    },
    "archived": {
      "loading": "Cargando…",
      "emptyTitle": "Nada archivado",
      "emptyDescription": "No hay memorias archivadas en ningún proyecto. El limpiador automático archiva aquí los hechos obsoletos y duplicados (restaurables durante 30 días); todavía no se ha eliminado nada.",
      "title": "Memorias archivadas",
      "subtitle": "Memorias que el limpiador automático y el barrido de ciclo de vida archivaron en todos los proyectos. Se excluyen de la recuperación pero son restaurables hasta que la ventana de gracia de 30 días las purgue. Restaura cualquier falso positivo abajo.",
      "globalScope": "(global)",
      "openProject": "Abrir proyecto",
      "archivedAtPrefix": "Archivado",
      "restoreButton": "Restaurar",
      "restoredToast": "Restaurado",
      "restoreFailedToast": "Error al restaurar"
    },
    "project": {
      "picker": {
        "title": "Elige un proyecto",
        "subtitle": "La memoria del proyecto se delimita por el directorio de trabajo. Elige uno para gestionar su objetivo, plan, diario y cola de limpieza.",
        "pathPlaceholder": "/path/to/your/project",
        "browse": "Examinar",
        "browseTooltip": "Examina el sistema de archivos del host del gateway",
        "open": "Abrir",
        "recentLabel": "Proyectos recientes (desde la memoria almacenada):",
        "orphanTooltip": "Parece un scope_key truncado (antiguo error de importación del mirror). Puede que no tenga documentos del proyecto.",
        "orphanBadge": "huérfano"
      },
      "noCwd": "Elige un proyecto para gestionar su memoria.",
      "header": {
        "docsCount_one": "{count} documento",
        "docsCount_other": "{count} documentos",
        "journalEntries_one": "{count} entrada del diario",
        "journalEntries_other": "{count} entradas del diario",
        "pendingProposals_one": "{count} propuesta pendiente",
        "pendingProposals_other": "{count} propuestas pendientes",
        "archivedCount": "{count} archivadas"
      },
      "tabs": {
        "health": "Estado",
        "goal": "Objetivo",
        "plan": "Plan",
        "tech": "Tecnología",
        "activity": "Actividad",
        "journal": "Diario",
        "inbox": "Bandeja de entrada",
        "conflicts": "Conflictos",
        "archived": "Archivadas"
      },
      "docLabel": {
        "goal": "Objetivo",
        "plan": "Plan",
        "tech_stack": "Stack tecnológico",
        "recent_activity": "Actividad reciente"
      },
      "editor": {
        "updatedBy": "Actualizado por",
        "noDocSet": "Aún no se ha definido ningún {label}.",
        "save": "Guardar",
        "saveFailedToast": "Error al guardar",
        "savedToast": "{label} guardado",
        "goalPlaceholder": "¿Qué estamos construyendo? Un párrafo. Lo lee cada agente al iniciarse.",
        "planPlaceholder": "Plan activo: qué estamos haciendo ahora mismo y qué viene después. Se actualiza a medida que avanza el trabajo."
      },
      "readonly": {
        "tech_stack": {
          "label": "Stack tecnológico y estructura",
          "empty": "Ejecuta una session de Claude en este proyecto. El escáner se actualiza en cada inicio."
        },
        "recent_activity": {
          "label": "Actividad reciente (git → LLM)",
          "empty": "El resumidor de actividad de git se ejecuta cada 24 h; vuelve a comprobarlo tras el siguiente ciclo del planificador."
        },
        "noneCaptured": "Aún no se ha capturado ningún {label}.",
        "generatedBy": "Generado por",
        "lastRefresh": "última actualización"
      },
      "journal": {
        "loading": "Cargando…",
        "empty": "Aún no hay entradas en el diario. Cada fin de session añade una automáticamente."
      },
      "inbox": {
        "loading": "Cargando…",
        "emptyTitle": "Bandeja de entrada vacía.",
        "emptyHint": "Los agentes presentan propuestas aquí mediante las herramientas MCP `project_goal_set` / `project_plan_set`.",
        "approvedToast": "{label} actualizado",
        "approveFailedToast": "Error al aprobar",
        "rejectedToast": "Rechazado",
        "rejectFailedToast": "Error al rechazar",
        "sessionPrefix": "ses",
        "warning": "Aprobar REEMPLAZARÁ por completo el {label} actual.",
        "warningSuffix": "Revisa el diff de abajo; esto no es una fusión.",
        "current": "Actual",
        "proposed": "Propuesto",
        "emptyBody": "(vacío)",
        "approve": "Aprobar",
        "reject": "Rechazar",
        "confirmDialogTitle": "¿Reemplazar {label}?",
        "confirmDialogDescription": "El {label} actual se sobrescribirá con el contenido propuesto. Esto no se puede deshacer desde esta interfaz (puedes volver a editarlo manualmente).",
        "confirmCancel": "Cancelar",
        "confirmReplace": "Confirmar reemplazo"
      },
      "archived": {
        "hint": "Memorias que el limpiador automático archivó para este proyecto. Se excluyen de la recuperación pero son restaurables hasta que la ventana de gracia de 30 días las purgue.",
        "empty": "Nada archivado para este proyecto. El limpiador archiva aquí los hechos obsoletos y duplicados automáticamente; todavía ninguno.",
        "archivedAtPrefix": "Archivado",
        "restoreButton": "Restaurar",
        "restoredToast": "Restaurado",
        "restoreFailedToast": "Error al restaurar"
      },
      "reset": {
        "button": "Restablecer",
        "dialogTitle": "¿Restablecer la memoria del proyecto?",
        "dialogDescription": "Elimina todo el contexto de proyecto almacenado para este cwd. Esto no se puede deshacer.",
        "alwaysDeleted": "Siempre se elimina: objetivo, plan, propuestas, diario, decisiones de limpieza.",
        "alsoDeleteScannerLabel": "Eliminar también los documentos del escáner",
        "alsoDeleteScannerSuffix": "(tech_stack + recent_activity).",
        "alsoDeleteScannerHint": "De todos modos se reconstruyen automáticamente en el siguiente inicio; dejarlo sin marcar suele estar bien.",
        "alsoDeleteMemoriesLabel": "Eliminar también las memorias de pgvector",
        "alsoDeleteMemoriesSuffix": "para este scope_key.",
        "alsoDeleteMemoriesHint": "Hechos a largo plazo que el agente almacenó (preferencias del usuario, datos del proyecto). No se pueden recuperar.",
        "cancel": "Cancelar",
        "deleteForever": "Eliminar para siempre",
        "successToast": "Restablecido: se eliminó {summary}",
        "summary": {
          "docs_one": "{count} documento",
          "docs_other": "{count} documentos",
          "journal": "{count} diario",
          "proposals_one": "{count} propuesta",
          "proposals_other": "{count} propuestas",
          "cleanup": "{count} limpieza",
          "memories": "{count} memorias"
        },
        "failedToast": "Error al restablecer"
      }
    },
    "memoryInspector": {
      "status": {
        "label": "Embedder activo",
        "unavailable": "no disponible",
        "probing": "sondeando…",
        "dimensions": "{dim}-dim · {state}",
        "enabled": "habilitado",
        "disabled": "deshabilitado",
        "floorNoModel": "Solo recuperación por palabras clave (BM25) — no hay modelo de embedding configurado. Añade un endpoint denso [memory.http] en Settings para habilitar la memoria semántica.",
        "denseConfiguredPendingRestart": "Configurado {model} (denso) — reinicia el gateway para activar la memoria semántica y re-embeber las memorias existentes.",
        "denseUnreachableFloor": "Configurado {model} (denso) pero el endpoint está inalcanzable — se usa el piso de palabras clave hasta que responda (se actualiza al reiniciar).",
        "denseDegraded": "Embedder denso activo pero su endpoint está inalcanzable ahora — los vectores existentes se conservan; las nuevas escrituras y la búsqueda por similitud se pausan hasta que responda."
      },
      "scope": {
        "label": "Scope",
        "scopeKey": "Clave de scope",
        "scopeKeyIgnored": "(ignorado para global)",
        "scopeKeyCwd": "(cwd del proyecto)",
        "placeholderProject": "/path/to/project (cwd)",
        "syncMd": "Sincronizar .md",
        "syncTooltip": "Reimportar los archivos <cwd>/.claude/memory/*.md de Claude a pgvector",
        "values": {
          "project": "proyecto",
          "global": "global"
        }
      },
      "search": {
        "placeholder": "Consulta de búsqueda semántica (Enter para ejecutar; vacío = explorar)",
        "run": "Buscar",
        "clear": "Limpiar",
        "failedToast": "La búsqueda falló"
      },
      "records": {
        "noMemories": "Aún no hay memorias",
        "matches_one": "{count} coincidencia",
        "matches_other": "{count} coincidencias",
        "memories_one": "{count} memoria",
        "memories_other": "{count} memorias",
        "scopeGlobalSuffix": " (global)",
        "scopeInSuffix": " en {scope}: ",
        "addButton": "Añadir memoria",
        "addTooltip": "Crear manualmente una memoria en este scope",
        "deleteAll": "Eliminar todo",
        "deleteAllTooltip": "Eliminar todas las memorias de este scope/scope_key",
        "loading": "Cargando…",
        "enterScopeKeyHint": "Introduce una clave de scope para explorar las memorias.",
        "noMatchesForQuery": "No hay coincidencias para \"{query}\"",
        "noMemoriesInScope": "Aún no hay memorias en este scope."
      },
      "row": {
        "simBadge": "sim {value}",
        "rankBadge": "rango {value}",
        "rankTooltip": "efectivo {effective} = sim {similarity} × antigüedad {age} ({days}d) × hits {hits} × conf {confidence}",
        "hits_one": "{count} hit",
        "hits_other": "{count} hits",
        "lastHitTooltip": "Último hit {relative}",
        "editPlaceholder": "Texto de la memoria. Cmd/Ctrl+Enter para guardar · Esc para cancelar",
        "saveTooltip": "Guardar (Cmd/Ctrl+Enter)",
        "cancelTooltip": "Cancelar (Esc)",
        "editTooltip": "Editar esta memoria",
        "deleteTooltip": "Eliminar esta memoria",
        "emptyError": "El texto de la memoria no puede estar vacío",
        "deleteConfirm": "¿Eliminar la memoria {id}? Esto es permanente."
      },
      "toasts": {
        "deleted": "Memoria eliminada",
        "deleteFailed": "La eliminación falló",
        "bulkDeleted_one": "Se eliminó {count} memoria de este scope",
        "bulkDeleted_other": "Se eliminaron {count} memorias de este scope",
        "bulkDeleteFailed": "La eliminación masiva falló",
        "created": "Memoria creada",
        "createFailed": "La creación falló",
        "updated": "Memoria actualizada",
        "updateFailed": "La actualización falló",
        "migrated": "Se migraron {reembed}/{examined} memorias a {to}",
        "migrationFailed": "La migración falló",
        "syncIngested_one": "Se importó {count} nuevo archivo de memoria",
        "syncIngested_other": "Se importaron {count} nuevos archivos de memoria",
        "syncEmpty": "No hay nuevos archivos .md que sincronizar",
        "syncEmptyDescription": "Ya está sincronizado, o no hay directorio de memoria de Claude para este cwd.",
        "syncFailed": "La sincronización falló"
      },
      "bulkDelete": {
        "title": "¿Eliminar todas las memorias de este scope?",
        "description": "Esto es una única operación SQL: todas las memorias del scope especificado se eliminan de forma atómica. Las memorias que se importaron a través del mirror de Claude reaparecen en la siguiente ejecución de <1>Sincronizar .md</1>; todo lo demás se pierde para siempre.",
        "scope": "Scope",
        "scopeKey": "Clave de scope",
        "currentlyVisible": "Visibles actualmente",
        "items_one": "{count} elemento de memoria",
        "items_other": "{count} elementos de memoria",
        "cancel": "Cancelar",
        "deleteAll": "Eliminar todo"
      },
      "addMem": {
        "title": "Añadir memoria",
        "description": "Crea manualmente una memoria. Los agentes las crean automáticamente mediante la herramienta MCP <1>memory_store</1>. Este formulario es para los casos en que el operador quiere insertar un hecho sin pasar por un agente.",
        "textLabel": "Texto",
        "textPlaceholder": "Prosa simple. El embedder lo convierte en un vector en el momento de almacenarlo; los agentes lo recuperarán mediante memory_search.",
        "cancel": "Cancelar",
        "create": "Crear"
      },
      "picker": {
        "button": "Elegir",
        "buttonTooltip": "Elige entre las claves de scope guardadas o las sessions activas",
        "loading": "Cargando…",
        "empty": "No hay claves guardadas ni sessions activas para {scope}.",
        "savedHeader": "Memorias guardadas",
        "activeHeader": "Sessions activas"
      },
      "migrationBanner": {
        "headline_one": "{count} memoria no aparecerá en las búsquedas",
        "headline_other": "{count} memorias no aparecerán en las búsquedas",
        "subtitle": "{summary}. El embedder actual es <1>{current}</1>. pgvector particiona su índice de similitud por embedder, así que las entradas más antiguas permanecen silenciosas hasta que se vuelven a embeber.",
        "summaryItem": "{count} en {name}",
        "migrateButton": "Migrar"
      },
      "reembed": {
        "title": "Volver a embeber memorias",
        "description": "Recalcula los vectores de las memorias almacenadas con un embedder diferente para que vuelvan a ser buscables.",
        "targetEmbedder": "Embedder de destino",
        "onName": "en",
        "totalToReembed": "Total a volver a embeber",
        "explainer": "El texto de cada memoria se vuelve a enviar al embedder actual; el nuevo vector reemplaza al antiguo en su lugar. Se conservan el ID, el scope, el scope_key, los metadatos y las marcas de tiempo. Los resultados de búsqueda surten efecto de inmediato, sin necesidad de reiniciar.",
        "reportExamined": "Examinadas",
        "reportReembedded": "Vueltas a embeber",
        "reportFailed": "Fallidas",
        "reportFrom": "Desde",
        "errors_one": "{count} error",
        "errors_other": "{count} errores",
        "done": "Hecho",
        "cancel": "Cancelar",
        "reembedding": "Volviendo a embeber…",
        "reembedTotal": "Volver a embeber {total}"
      }
    },
    "notes": {
      "title": "Notas",
      "header": {
        "outline": "Esquema",
        "showOutline": "Mostrar esquema",
        "hideOutline": "Ocultar esquema",
        "today": "Hoy",
        "todayTooltip": "Abre o crea la nota diaria de hoy",
        "new": "Nueva"
      },
      "left": {
        "tree": "Árbol",
        "tags": "Etiquetas",
        "filterNotes": "Filtrar notas…",
        "filterTags": "Filtrar etiquetas…",
        "filteredBy": "filtrado por",
        "clearTagTooltip": "Limpiar filtro de etiquetas",
        "expandAll": "Expandir todo",
        "expandAllTooltip": "Expandir todas las carpetas",
        "collapseAll": "Contraer todo",
        "collapseAllTooltip": "Contraer todas las carpetas",
        "loading": "Cargando…",
        "footer": "{visible} / {total} notas"
      },
      "tags": {
        "emptyVault": "Aún no hay etiquetas en el vault.",
        "noMatches": "No hay coincidencias para \"{query}\"."
      },
      "tree": {
        "empty": "El vault está vacío."
      },
      "outline": {
        "label": "Esquema",
        "empty": "No hay encabezados en esta nota. Añade líneas <1>## Título</1> para rellenar el esquema."
      },
      "newNote": {
        "prompt": "Ruta de la nueva nota (relativa al vault, debe terminar en .md)",
        "defaultPath": "library/notes-{date}.md",
        "errorMustEndMd": "La ruta debe terminar en .md",
        "createdToast": "Nota creada",
        "createFailedToast": "Error al crear"
      },
      "empty": {
        "title": "Ninguna nota seleccionada",
        "hint": "Elige una nota del árbol de la izquierda, ve directo al registro diario de hoy o crea una nueva. Los docs de proyecto escritos por la IA viven en <1>projects/</1>; tus borradores personales en <3>personal/</3>.",
        "today": "Nota diaria de hoy",
        "new": "Nueva nota"
      },
      "picker": {
        "browseAria": "Explorar carpetas",
        "matches_one": "{count} coincidencia",
        "matches_other": "{count} coincidencias",
        "foldersInVault": "{count} carpetas en el vault",
        "noMatch": "Ninguna carpeta existente coincide. Guarda igualmente para usar <1>{value}</1> (se crea de forma diferida en la primera escritura)."
      },
      "vaultSync": {
        "title": "Sincronización del vault",
        "description": "Haz commit, pull y push del vault de notas como un repositorio git. La autenticación usa las credenciales de git del host de tu gateway (agente SSH / asistente de credenciales).",
        "reading": "Leyendo el estado del vault…",
        "init": {
          "title": "El vault aún no es un repo git",
          "body": "Al inicializarlo se ejecutará <1>git init -b main</1> en la raíz de tu vault y se añadirá un <3>.gitignore</3> sensato. Después podrás hacer commit de tus notas y configurar un remoto (GitHub / Gitea / GitLab) para la sincronización entre máquinas.",
          "button": "Inicializar el vault como repo git",
          "initToast": "Vault inicializado como repo git",
          "initFailedToast": "Error al inicializar"
        },
        "branch": {
          "clean": "limpio",
          "staged": "{count} en stage",
          "modified": "{count} modificados",
          "untracked": "{count} sin seguimiento"
        },
        "action": {
          "pull": "Pull",
          "push": "Push",
          "pullTitleNoRemote": "Configura primero un remoto",
          "pullTitleHasUpstream": "git pull --rebase --autostash",
          "pullTitleNoUpstream": "Hace pull del HEAD de origin; configura el seguimiento de forma implícita",
          "pushTitleNoRemote": "Configura primero un remoto",
          "pushTitleHasUpstream": "git push -u origin HEAD",
          "pushTitleNoUpstream": "Primer push: configurará el upstream a origin/HEAD",
          "noRemote": "Sin remoto configurado: pull/push deshabilitados",
          "noUpstream": "Aún no hay seguimiento de upstream: el primer push lo configurará.",
          "pulledToast": "Pull realizado",
          "pullFailedToast": "Error en el pull",
          "pushedToast": "Push realizado",
          "pushFailedToast": "Error en el push"
        },
        "commit": {
          "title": "Commit",
          "placeholder": "Notas: {date} (predeterminado)",
          "commitAll": "Hacer commit de todo",
          "hint": "Pone en stage todos los cambios (<1>git add .</1>) y luego hace commit con este mensaje. Un mensaje vacío usa de forma predeterminada un asunto con marca de tiempo.",
          "committedToast": "Commit {hash} realizado",
          "commitFailedToast": "Error en el commit"
        },
        "fileList": {
          "title": "Árbol de trabajo · {count}",
          "moreSuffix": "+{count} más"
        },
        "remote": {
          "title": "Remoto (origin)",
          "cancel": "Cancelar",
          "change": "Cambiar",
          "configure": "Configurar",
          "empty": "No hay remoto configurado. Añade una URL HTTPS o SSH (p. ej. <1>git@github.com:you/notes.git</1> o <3>https://gitea.example.com/you/notes.git</3>) para habilitar push / pull.",
          "urlLabel": "URL (HTTPS o SSH)",
          "urlPlaceholder": "git@host:owner/notes.git",
          "save": "Guardar",
          "savedToast": "Remoto guardado",
          "saveFailedToast": "Error al configurar el remoto"
        },
        "history": {
          "title": "Commits recientes",
          "loading": "Cargando…",
          "empty": "Aún no hay commits."
        },
        "conflict": {
          "kinds": {
            "rebase": "rebase",
            "merge": "merge",
            "cherryPick": "cherry-pick",
            "operation": "operación"
          },
          "headline": "El vault tiene un {kind} en pausa con conflictos sin resolver",
          "explainer": "Pull, push y commit están bloqueados hasta que termine el {kind}. Puedes hacer <1>abort</1> (restaurar el árbol de trabajo a su estado anterior al {kind}, conserva tus commits locales y descarta los remotos) o <3>forzar reset al remoto</3> (descartar TODOS los commits locales y los cambios sin confirmar; el vault se convierte en una copia exacta de origin).",
          "conflictedHeader": "Archivos en conflicto · {count}",
          "abort": "Abortar {kind}",
          "abortTitle": "git {kind} --abort",
          "forceReset": "Forzar reset al remoto",
          "forceResetTitle": "git fetch && git reset --hard origin/<branch> && git clean -fd",
          "forceResetConfirm": "DESTRUCTIVO: esto va a\n  • abortar el {kind} en curso\n  • ejecutar git fetch origin\n  • hacer reset --hard a origin/<branch>\n  • ejecutar clean -fd (descartar archivos sin seguimiento)\n\nCualquier commit local no enviado a origin Y cualquier edición sin confirmar se PERDERÁ DE FORMA PERMANENTE.\n\n¿Continuar?",
          "abortedToast": "{kind} abortado",
          "abortedDescription": "Árbol de trabajo restaurado al estado anterior a la operación.",
          "abortFailedToast": "Error al abortar",
          "resetToast": "Reset a {branch}",
          "resetDescription": "Cambios locales descartados; el vault coincide con el remoto.",
          "resetFailedToast": "Error en el reset"
        },
        "auth": {
          "title": "Autenticación",
          "httpsTokenOk": "Usará el token guardado para <1>{host}</1> en Plugins → Hosts de git. ✓",
          "httpsTokenMissing": "Remoto HTTPS en <1>{host}</1> sin ningún token de opendray configurado. Es probable que push / pull fallen en repos privados hasta que añadas uno.",
          "ssh": "Remoto SSH en <1>{host}</1>. La autenticación usa el <3>~/.ssh/</3> del host del gateway (ssh-agent, archivo de identidad, configuración de host). Verifícalo con <5>ssh -T git@{host}</5> desde la shell del host.",
          "configureTokenLink": "→ Configurar token de host de git"
        },
        "autoSync": {
          "loading": "Cargando ajustes de sincronización automática…",
          "title": "Sincronización automática",
          "on": "activada",
          "runNow": "Ejecutar ahora",
          "runNowTooltip": "Despierta el bucle de sincronización ahora (omite la espera y luego ejecuta los pasos pendientes)",
          "configure": "Configurar",
          "hide": "Ocultar",
          "enabled": "Habilitada",
          "enabledTooltipNoRemote": "Configura primero un remoto para habilitar la sincronización automática",
          "noRemoteHint": "Sin remoto: se omitirán push/pull.",
          "commitEvery": "Hacer commit cada",
          "commitEveryExamples": "Ejemplos: <1>30s</1>, <3>10m</3>, <5>2h</5>. Mínimo 30s.",
          "pullEvery": "Hacer pull cada",
          "pullEveryHint": "Solo se usa cuando Pull está habilitado.",
          "pushAfterCommit": "Hacer push tras el commit",
          "pullPeriodically": "Hacer pull periódicamente",
          "commitTemplateLabel": "Plantilla del mensaje de commit",
          "commitTemplatePlaceholder": "Sincronización automática: {date}  (predeterminado si está vacío)",
          "saveSettings": "Guardar ajustes",
          "discard": "Descartar",
          "lastCommit": "último commit",
          "lastPush": "último push",
          "lastPull": "último pull",
          "never": "nunca",
          "savedToast": "Ajustes de sincronización automática guardados",
          "saveFailedToast": "Error al guardar",
          "triggeredToast": "Sincronización automática iniciada",
          "runFailedToast": "Error en la ejecución"
        }
      },
      "syncBadge": {
        "loading": "Cargando…",
        "syncLabel": "Sincronizar",
        "initLabel": "Init",
        "initTooltip": "El vault aún no es un repo git",
        "conflictLabel": "Conflicto",
        "conflictTooltip": "El vault tiene conflictos sin resolver: haz clic para recuperar",
        "syncFallback": "sincronizar",
        "tooltip": "rama {branch} · {files} cambios · {ahead} por delante · {behind} por detrás",
        "tooltipAutoOn": " · sincronización automática activada",
        "tooltipLastError": " · último error: {error}",
        "branchPlaceholder": "—"
      }
    },
    "activity": {
      "title": "Actividad",
      "subtitle": "Auditoría por llamada de las solicitudes API realizadas por las integraciones registradas. Incluye tanto las llamadas entrantes (una app de terceros que llama a opendray con su clave de API) como las llamadas salientes a través del proxy (admin → proxy de opendray → integración). Las llamadas hechas directamente por esta UI de administración no se registran.",
      "refresh": "Actualizar",
      "refreshTooltip": "Actualizar",
      "filters": {
        "integration": "Integración",
        "direction": "Dirección",
        "status": "Estado",
        "allIntegrations": "Todas las integraciones",
        "all": "Todas",
        "inbound": "Entrante",
        "outbound": "Saliente",
        "allStatuses": "Todos los estados",
        "status2": "2xx correcto",
        "status3": "3xx redirección",
        "status4": "4xx error de cliente",
        "status5": "5xx error de servidor"
      },
      "callsCount_one": "{count} llamada",
      "callsCount_other": "{count} llamadas",
      "loading": "Cargando…",
      "table": {
        "time": "Hora",
        "integration": "Integración",
        "directionTitle": "Dirección",
        "method": "Método",
        "path": "Ruta",
        "status": "Estado",
        "duration": "Duración",
        "inboundAria": "entrante",
        "outboundAria": "saliente"
      },
      "empty": {
        "filtered": "Ninguna llamada coincide con estos filtros.",
        "title": "Aún no se ha registrado ninguna llamada API",
        "description": "Cuando una app de terceros llama a opendray con su clave de API de integración, cada solicitud se registra aquí.",
        "stepWithIntegrations": "Usa la clave de API de una integración existente en tu app de terceros",
        "stepRegister": "Registra una integración en Integraciones → Nueva",
        "stepCallEndpoint": "Llama a cualquier endpoint, p. ej. <1>POST /api/v1/sessions</1>",
        "stepAppears": "Las llamadas aparecen aquí en cuestión de segundos",
        "footnote": "Las llamadas que haces desde esta UI de administración no se registran; solo se registra el tráfico atribuido a integraciones."
      },
      "events": {
        "loading": "Cargando eventos…",
        "empty": "Aún no hay eventos.",
        "emptyFiltered": "No hay eventos coincidentes.",
        "loadOlder": "Cargar eventos anteriores",
        "today": "Hoy",
        "yesterday": "Ayer"
      }
    },
    "providers": {
      "list": {
        "title": "Proveedores",
        "loading": "Cargando…",
        "disabledBadge": "deshabilitado",
        "noneSelected": "Ningún proveedor seleccionado."
      },
      "detail": {
        "enabled": "Habilitado",
        "disabled": "Deshabilitado",
        "toggleAria": "Alternar {name}",
        "configuration": "Configuración",
        "noConfig": "Este proveedor no tiene campos configurables por el usuario.",
        "executable": "executable:",
        "manifestHash": "manifest_hash:",
        "reset": "Restablecer",
        "save": "Guardar cambios",
        "saving": "Guardando…",
        "savedToast": "Configuración del proveedor guardada",
        "saveFailedToast": "Error al guardar",
        "toggleFailedToast": "Error al alternar",
        "caps": {
          "resume": "resume",
          "stream": "stream",
          "images": "images",
          "mcp": "mcp"
        },
        "notInstalled": "no instalado",
        "updateAvailable": "actualización disponible → {version}",
        "upToDate": "actualizado",
        "update": "Actualizar a {version}",
        "updating": "Actualizando…",
        "updatedToast": "Actualizado {from} → {to}",
        "alreadyLatestToast": "Ya está actualizado",
        "updateFailedToast": "Error al actualizar",
        "updateUnavailable": "La actualización dentro de la app no está disponible aquí"
      },
      "configForm": {
        "selectPlaceholder": "Selecciona…",
        "defaultOption": "(predeterminado)",
        "switchOn": "Activado",
        "switchOff": "Desactivado",
        "showSecret": "Mostrar secreto",
        "hideSecret": "Ocultar secreto"
      },
      "claudeAccounts": {
        "title": "Cuentas de Claude",
        "importLocal": "Importar local",
        "importLocalTooltip": "Escanea ~/.claude-accounts/ en el host del gateway y registra cualquier directorio nuevo. El botón solo funciona en el host del gateway.",
        "importedNothingToast": "Nada que importar, las cuentas ya están sincronizadas.",
        "importedToast_one": "Se importó {count} cuenta desde ~/.claude-accounts",
        "importedToast_other": "Se importaron {count} cuentas desde ~/.claude-accounts",
        "importFailedToast": "Error al importar",
        "addingTitle": "Añadiendo una cuenta nueva.",
        "addingBodyPrefix": "Ejecuta en el host del gateway:",
        "addingBodySuffix": "el monitor del sistema de archivos de opendray registrará el directorio nuevo automáticamente, o haz clic en <1>Importar local</1> para escanear de inmediato.",
        "architectureLink": "Arquitectura y guía completa →",
        "loading": "Cargando…",
        "empty": "Aún no hay cuentas de Claude. La forma más sencilla: abre Sessions, inicia una session de Claude y ejecuta <1>claude login</1> en la terminal. Tus credenciales de OAuth se guardan en <3>~/.claude</3> en el gateway y aparecen aquí automáticamente. Los usuarios avanzados que gestionan varias identidades pueden usar el flujo de shell anterior en su lugar.",
        "noTokenYet": "aún no hay token",
        "configDir": "config_dir:",
        "tokenPath": "token_path:",
        "toggleFailedToast": "Error al alternar",
        "removeConfirm": "¿Quitar la cuenta \"{name}\"?",
        "removedToast": "Cuenta eliminada",
        "removeFailedToast": "Error al eliminar",
        "toggleAria": "Alternar {name}",
        "removeAria": "Quitar {name}",
        "identityAcceptedToast": "Nueva identidad registrada",
        "identityAcceptFailedToast": "No se pudo aceptar la identidad"
      },
      "models": {
        "title": "Modelos",
        "help": "Modelos ofrecidos para este proveedor. El predeterminado se pasa a cada session mediante el flag de modelo; las sessions aún pueden sobrescribirlo.",
        "empty": "Aún no hay modelos configurados.",
        "add": "Añadir",
        "addPlaceholder": "id del modelo (p. ej. sonnet)",
        "suggested": "Sugeridos ({count})",
        "default": "predeterminado",
        "makeDefault": "establecer como predeterminado",
        "setDefault": "Usar como modelo predeterminado",
        "remove": "Quitar {model}"
      }
    },
    "channels": {
      "title": "Canales",
      "subtitle": "Integraciones de mensajería bidireccional. Cada canal habilitado y no silenciado recibe notificaciones de sesión.",
      "newButton": "Nuevo canal",
      "loading": "Cargando…",
      "empty": {
        "title": "Aún no hay canales",
        "description": "Tipos incluidos: Telegram · Slack · Discord · Feishu · DingTalk · WeCom. Elige uno y pega las credenciales, o usa <1>bridge</1> para una plataforma personalizada vía WebSocket."
      },
      "card": {
        "running": "en ejecución",
        "starting": "iniciando…",
        "disabled": "desactivado",
        "muted": "silenciado",
        "tokenLabel": "token:",
        "chatIdLabel": "chat_id:",
        "channelIdLabel": "channel_id:",
        "webhookLabel": "webhook:",
        "copyWebhookTooltip": "Copiar la URL del webhook",
        "webhookCopiedToast": "URL del webhook copiada",
        "setup": "Configuración",
        "setupTooltip": "Mostrar los detalles de conexión del adaptador y código de ejemplo",
        "test": "Probar",
        "testNotRunningTooltip": "El canal debe estar en ejecución",
        "testBridgeTooltip": "Los canales bridge no se pueden probar desde el panel de administración, conecta primero un adaptador",
        "editAria": "Editar canal",
        "editTooltip": "Editar la configuración del canal",
        "deleteAria": "Eliminar canal",
        "muteAria": "Silenciar o reactivar el canal",
        "muteTooltip": "Silenciar notificaciones (el chat bidireccional sigue funcionando)",
        "unmuteTooltip": "Reactivar notificaciones",
        "bridgeSuffix": "(bridge)"
      },
      "toasts": {
        "testSent": "Mensaje de prueba enviado",
        "testFailed": "La prueba falló",
        "deleteConfirm": "¿Eliminar el canal {id}?",
        "deleted": "Canal eliminado",
        "created": "Canal creado",
        "updated": "Canal actualizado",
        "muted": "Canal silenciado",
        "unmuted": "Canal reactivado"
      },
      "dialog": {
        "editTitle": "Editar canal",
        "createTitle": "Registrar canal",
        "descriptionBridge": "Un adaptador externo (Python/Node/...) se conecta vía WebSocket y presenta este token.",
        "descriptionDefault": "Configura la integración de mensajería.",
        "kindLabel": "Tipo",
        "kindImmutable": "(inmutable, elimina y vuelve a crear para cambiar el tipo)",
        "enabledLabel": "Activado",
        "enabledBridgeHint": " (acepta conexiones de adaptadores de inmediato)",
        "enabledWebhookHint": " (empieza a recibir webhooks de inmediato)",
        "enabledDefaultHint": " (empieza de inmediato)",
        "cancel": "Cancelar",
        "save": "Guardar",
        "saving": "Guardando…",
        "create": "Crear",
        "creating": "Creando…",
        "unknownKind": "Tipo desconocido: {kind}",
        "nameRequired": "el nombre es obligatorio",
        "tokenRequired": "el token es obligatorio",
        "topicIdsNumeric": "Los ID de tema deben ser numéricos (se recibió {value})",
        "fieldRequired": "{label} es obligatorio",
        "cooldownInvalid": "El cooldown debe ser un número de segundos no negativo",
        "snippetCapInvalid": "El límite del fragmento debe ser un número no negativo"
      },
      "notifications": {
        "sectionTitle": "Notificaciones de session",
        "repeatPolicyLabel": "Política de repetición",
        "cooldownLabel": "Duración del cooldown",
        "onceReplyHint": "Responder con texto que no sea un comando en este chat restablece la supresión, opendray reenvía tu respuesta al stdin de la session y rearma el notificador.",
        "terminalSnippetLabel": "Fragmento de terminal",
        "embedSnippetLabel": "Incrustar la pantalla reciente del terminal en las notificaciones de inactividad",
        "snippetExplainer": "Cuando está activado, la tarjeta de inactividad incluye un fragmento en bloque de código de lo que el usuario vería en el terminal web en vivo, los elementos de la interfaz del TUI de Claude (indicador de estado, aviso de \"bypass permissions\", líneas separadoras) se filtran automáticamente.",
        "modes": {
          "onceLabel": "Una vez por session (recomendado)",
          "onceHint": "Se dispara una vez cuando una session queda inactiva, luego permanece en silencio hasta que la session termine o respondas por este canal.",
          "cooldownLabel": "Cooldown por ventana de tiempo",
          "cooldownHint": "Suprime las repeticiones del mismo par (session, evento) dentro de la ventana elegida.",
          "everyLabel": "Cada evento (ruidoso)",
          "everyHint": "Sin supresión. Úsalo solo para canales de baja frecuencia."
        },
        "cooldowns": {
          "60": "1 minuto",
          "300": "5 minutos",
          "900": "15 minutos",
          "1800": "30 minutos",
          "3600": "1 hora"
        },
        "snippetCaps": {
          "0": "Sin límite, dividir en varios mensajes (predeterminado)",
          "1000": "1000 caracteres (conciso)",
          "3000": "3000 caracteres",
          "6000": "6000 caracteres",
          "12000": "12000 caracteres"
        }
      },
      "bridge": {
        "nameLabel": "Nombre del bridge",
        "namePlaceholder": "wechat / discord-custom / whatsapp...",
        "nameHint": "Etiqueta legible para el adaptador. Se muestra en la lista de canales.",
        "tokenLabel": "Token del adaptador",
        "regenerateTooltip": "Regenerar",
        "copyTooltip": "Copiar",
        "tokenCopiedToast": "Token copiado",
        "tokenHint": "El adaptador se autentica enviándolo en el frame de registro de WS (o como cabecera <1>X-Bridge-Token</1>).",
        "capsLabel": "Aceptar capacidades (lista blanca opcional)",
        "capsHint": "Vacío = aceptar lo que declare el adaptador. Seleccionado = permitir solo estas capacidades aunque el adaptador ofrezca más.",
        "afterCreate": "Tras <1>Crear</1>, el diálogo de configuración del adaptador se abre automáticamente con la URL del WebSocket y código de inicio en Python / Node / wscat listo para copiar y pegar."
      },
      "setup": {
        "title": "Configuración del adaptador, {name}",
        "description": "Ejecuta un adaptador (en cualquier lenguaje) que se conecte a opendray vía WebSocket usando estas credenciales. opendray enrutará las notificaciones de session y las acciones de los comandos a través de él.",
        "wsUrlLabel": "URL del WebSocket",
        "tokenLabel": "Token del adaptador",
        "authInfo": "<1>Auth:</1> envía el token como cabecera <3>X-Bridge-Token</3>, parámetro de consulta <5>?token=</5> o <7>Authorization: Bearer …</7>. El primer frame de WS debe ser <9>{frame}</9>. Especificación completa: <11>docs/bridge-protocol.md</11> en el repo.",
        "pythonInstall": "Instalar: <1>pip install websockets</1>. Ejecutar: <3>python adapter.py</3>.",
        "nodeInstall": "Instalar: <1>npm i ws</1>. Ejecutar: <3>node adapter.mjs</3>.",
        "wscatInstall": "Instalar: <1>npm i -g wscat</1>. Una vez conectado, pega la línea JSON mostrada arriba para registrarte, luego envía más frames manualmente.",
        "close": "Cerrar",
        "copyHide": "Ocultar",
        "copyShow": "Mostrar",
        "copyLabelToast": "{label} copiado",
        "copyCode": "Copiar",
        "copied": "Copiado",
        "codeCopiedToast": "Código copiado"
      }
    },
    "integrations": {
      "title": "Integraciones",
      "subtitle": "Aplicaciones externas que consumen opendray. Reenvían mediante reverse-proxy a través de <1>/api/v1/proxy/&lt;prefix&gt;/…</1> y se suscriben a eventos a través del endpoint WS.",
      "register": "Registrar",
      "loading": "Cargando…",
      "tabs": {
        "registered": "Registradas",
        "console": "Reverse proxy"
      },
      "empty": {
        "title": "Aún no hay integraciones",
        "description": "Registra una aplicación externa para darle una API key con alcance limitado. Su código se queda fuera de este repositorio.",
        "register": "Registrar integración"
      },
      "groupSystem": "Sistema (gestionado por opendray)",
      "groupOperator": "Registradas por el operador",
      "card": {
        "managedBadge": "gestionada",
        "managedTooltip": "opendray gestiona esta integración. Editar o rotar su key dejaría huérfanas las sessions en ejecución cuyo mcp.json contiene el bearer anterior.",
        "consumerBadge": "consumidora",
        "consumerTooltip": "Integración solo consumidora. No hay servicio HTTP que sondear",
        "disabledBadge": "deshabilitada",
        "consumerOnlyHint": "Consume la API de opendray. No tiene reverse proxy montado.",
        "lastProbed": "último sondeo {relative}",
        "rotated": "rotada {relative}",
        "managedReadOnly": "solo lectura. opendray incrusta su key en el mcp.json de cada spawn",
        "managedReadOnlyTooltip": "opendray gestiona esta fila. Para restablecerla: borra ~/.opendray/memory.key y reinicia, o borra esta fila directamente mediante SQL (se volverá a inicializar en el siguiente arranque).",
        "editAria": "Editar integración",
        "editTooltip": "Editar scopes / URL base / versión",
        "rotateKey": "Rotar key",
        "deleteAria": "Eliminar integración",
        "rotateConfirm": "¿Rotar la API key de \"{name}\"? La key actual dejará de funcionar de inmediato.",
        "deleteConfirm": "¿Eliminar la integración {name}?",
        "removedToast": "Integración eliminada"
      },
      "register_dialog": {
        "title": "Registrar integración",
        "description": "Emite una API key de un solo uso. Cópiala antes de cerrar: opendray nunca vuelve a mostrar el texto en claro.",
        "nameLabel": "Nombre",
        "namePlaceholder": "PetTracker",
        "modeHint": "Deja en blanco los dos campos siguientes para una integración <1>solo consumidora</1> (aplicación de terceros que llama a la API de opendray pero no expone su propio servicio). Rellena ambos para una integración con <3>reverse-proxy</3>.",
        "baseUrlLabel": "URL base",
        "optionalSuffix": "(opcional)",
        "baseUrlPlaceholder": "http://192.168.1.10:8080",
        "routePrefixLabel": "Prefijo de ruta",
        "routePrefixPlaceholder": "pet-tracker",
        "routePrefixHint": "Accesible en <1>/api/v1/proxy/{prefix}/*</1>.",
        "routePrefixPlaceholderToken": "<prefix>",
        "versionLabel": "Versión (opcional)",
        "versionPlaceholder": "0.1.0",
        "scopesLabel": "Scopes",
        "scopesIntro": "Elige la superficie de API que esta integración tiene permitido llamar. Cada interruptor se corresponde con un claim del Bearer-token: opendray rechaza las peticiones que tocan endpoints fuera del conjunto concedido.",
        "errorNameRequired": "El nombre es obligatorio.",
        "errorBothOrNeither": "base_url y route_prefix van juntos. Configura ambos para una integración con reverse-proxy, o deja ambos en blanco para una integración solo consumidora.",
        "cancel": "Cancelar",
        "submit": "Registrar",
        "submitting": "Registrando…"
      },
      "reveal": {
        "titleIssued": "API key emitida",
        "titleRotated": "API key rotada",
        "description": "Esta es la única vez que se mostrará la key en texto en claro. Cópiala ahora y actualiza todas las aplicaciones consumidoras: la key anterior (si la había) ya no autentica.",
        "discardAria": "Descartar la nueva key",
        "discardTooltip": "Descartar la nueva key (la rotación ya ha ocurrido, la key antigua también desapareció)",
        "discardConfirm": "¿Descartar la nueva key? La rotación ya ha invalidado la key antigua: descartarla significa que NO tendrás ninguna key funcional para esta integración hasta que vuelvas a rotar.",
        "copy": "Copiar",
        "copied": "Copiada",
        "updateHint": "<1>Actualiza todas las aplicaciones consumidoras con esta nueva key.</1> La key anterior se ha invalidado en el servidor y devolverá <3>401 unauthorized</3> en la siguiente petición.",
        "acknowledge": "He copiado la key y actualizaré mis aplicaciones consumidoras. Entiendo que opendray no la volverá a mostrar.",
        "discard": "Descartar",
        "done": "Hecho"
      },
      "edit_dialog": {
        "title": "Editar integración · {name}",
        "description": "Cambia los scopes, la versión o la URL base. El nombre y el prefijo de ruta son inmutables: elimina y vuelve a registrar si necesitas cambiarlos.",
        "nameLabel": "Nombre",
        "routePrefixLabel": "Prefijo de ruta",
        "consumerOnlyLabel": "(solo consumidora)",
        "baseUrlLabel": "URL base",
        "baseUrlConsumerSuffix": "(solo consumidora, deja en blanco)",
        "baseUrlProxySuffix": "(destino del reverse-proxy)",
        "baseUrlConsumerPlaceholder": "(en blanco: esta integración consume la API de opendray)",
        "baseUrlProxyPlaceholder": "http://127.0.0.1:8080",
        "consumerHint": "Esta es una integración solo consumidora. Cambiar la URL base aquí también requeriría un prefijo de ruta; hazlo eliminando y volviendo a registrar.",
        "versionLabel": "Versión",
        "versionPlaceholder": "0.1.0",
        "scopesLabel": "Scopes",
        "scopesIntro": "Reduce o amplía la superficie de API que autoriza la API key de esta integración. Los tokens activos no se ven afectados: el nuevo conjunto de scopes surte efecto en la siguiente petición.",
        "errorModeSwitch": "Cambiar entre modo solo consumidora y reverse-proxy requiere eliminar la integración y volver a registrarla: el nombre y route_prefix no pueden cambiarse sobre la marcha.",
        "updatedToast": "Integración actualizada",
        "cancel": "Cancelar",
        "save": "Guardar cambios"
      },
      "proxy": {
        "emptyTitle": "No hay integraciones registradas",
        "emptyDescription": "Registra primero una integración; la consola hace de proxy a través de /api/v1/proxy/{prefix}/* usando el token de administrador.",
        "targetLabel": "Destino",
        "selectPlaceholder": "Selecciona una integración…",
        "baseLabel": "base:",
        "history": "Historial",
        "historyEmpty": "no hay peticiones anteriores para esta integración",
        "send": "Enviar",
        "sending": "Enviando…",
        "extraHeadersLabel": "Headers adicionales (uno por línea, Nombre: Valor)",
        "bodyLabel": "Body",
        "headers": "Headers",
        "body": "Body",
        "emptyBody": "(vacío)",
        "requestFailed": "la petición falló",
        "stubText": "Envía una petición para ver la respuesta del upstream.",
        "stubInjects": "opendray inyecta <1>X-Integration-ID</1> y elimina tu header <3>Authorization</3>.",
        "prefixPlaceholder": "<prefix>"
      }
    },
    "plugins": {
      "title": "Plugins del Inspector",
      "subtitle": "Configura las fuentes de datos que se muestran en el panel Inspector de la derecha cuando hay una session abierta. Cada plugin es solo para administradores y se comparte entre todas las sessions. Haz clic en el encabezado de una sección para contraerla.",
      "common": {
        "loading": "Cargando…",
        "cancel": "Cancelar",
        "edit": "Editar",
        "add": "Añadir",
        "save": "Guardar",
        "create": "Crear"
      },
      "mcp": {
        "title": "Servidores MCP",
        "description": "Servidores Model Context Protocol inyectados en cada spawn (claude / codex). Las entradas del vault están en <1>~/.opendray/vault/mcp/&lt;id&gt;/mcp.json</1>; los secretos (referenciados como <3>${KEY}</3> en env / headers) provienen de la sección <5>secretos MCP</5> de abajo.",
        "newServer": "Nuevo servidor",
        "empty": "Aún no hay servidores MCP. Añade uno para exponer herramientas adicionales a tus sessions de agente.",
        "columns": {
          "name": "Nombre",
          "transport": "Transport",
          "spec": "Spec",
          "enabled": "Habilitado"
        },
        "noUrl": "sin url",
        "noCommand": "sin comando",
        "deleteConfirm": "¿Eliminar el servidor MCP \"{id}\"?",
        "removedToast": "Servidor MCP eliminado",
        "deleteFailedToast": "Error al eliminar",
        "toggleFailedToast": "Error al alternar",
        "codexUnsupportedBadge": "Codex: no compatible",
        "codexUnsupportedTooltip": "El CLI de codex solo admite el transport stdio. Este servidor se omitirá en las sessions de codex; claude y gemini lo seguirán usando.",
        "editor": {
          "createTitle": "Nuevo servidor MCP",
          "editTitle": "Editar MCP: {id}",
          "description": "Forma del JSON: <1>command</1>+<3>args</3>+<5>env</5> para stdio (predeterminado), o <7>transport</7> +<9> url</9>+<11>headers</11> para sse / http. Referencia los secretos como <13>${API_KEY}</13>, se sustituyen en el momento del spawn desde el archivo de secretos.",
          "idLabel": "ID",
          "idPlaceholder": "filesystem",
          "idHint": "Minúsculas / dígitos / guion / guion bajo. Se convierte tanto en el nombre del directorio como en el <1>name</1> predeterminado.",
          "bodyLabel": "mcp.json",
          "invalidJson": "JSON no válido: {error}",
          "createdToast": "Servidor MCP creado",
          "savedToast": "Servidor MCP guardado",
          "createFailedToast": "Error al crear",
          "saveFailedToast": "Error al guardar",
          "transportLabel": "Transport",
          "transportHint": "Cambiar el transport reemplaza la plantilla JSON por una forma inicial adecuada para el nuevo transport.",
          "transportStdio": "stdio (subproceso local)",
          "transportSse": "sse (servidor remoto)",
          "transportHttp": "http (servidor remoto)"
        },
        "test": {
          "button": "Probar",
          "title": "Validar este servidor MCP desde el daemon",
          "connected": "conectado · {count} herramientas",
          "reachable": "accesible",
          "failed": "prueba fallida"
        }
      },
      "mcpSecrets": {
        "title": "Secretos MCP",
        "encryptedBadge": "cifrado",
        "plaintextBadge": "texto plano",
        "encryptedTooltip": "Cifrado AES-GCM en disco; la clave se almacena en el llavero del SO",
        "plaintextTooltip": "Llavero del SO no disponible. El archivo está en texto plano en disco. Revisa el log del gateway.",
        "description": "Los valores referenciados desde los marcadores <1>${KEY}</1> en cualquier <3>mcp.json</3> se sustituyen en el momento del spawn. <5>Los valores guardados nunca se devuelven a través de la API</5>, puedes sobrescribirlos o eliminarlos, pero no volver a leerlos.",
        "descriptionStored": " Almacenado en <1>{path}</1>.",
        "addSecret": "Añadir secreto",
        "empty": "No hay secretos almacenados. Añade uno para empezar a referenciarlo como <1>${KEY}</1> en las configuraciones de tus servidores MCP.",
        "columns": {
          "key": "Clave",
          "value": "Valor"
        },
        "editTooltip": "Sobrescribir el valor almacenado",
        "deleteConfirm": "¿Eliminar el secreto \"{key}\"? Cualquier mcp.json que referencie ${key} recurrirá al marcador literal hasta que establezcas un nuevo valor.",
        "removedToast": "Secreto eliminado",
        "deleteFailedToast": "Error al eliminar",
        "editor": {
          "addTitle": "Añadir secreto",
          "updateTitle": "Actualizar {key}",
          "addDescription": "Se almacena cifrado en disco si el llavero del SO está disponible. Referéncialo desde el env / headers / args / url de cualquier mcp.json con ${KEY}.",
          "editDescription": "Introduce el nuevo valor para sobrescribir. El valor anterior no se puede recuperar.",
          "keyLabel": "Clave",
          "keyPlaceholder": "BRAVE_API_KEY",
          "keyPattern": "Debe coincidir con <1>[A-Za-z_][A-Za-z0-9_]*</1>",
          "keyCollision": "Ya existe. Usa Editar en su lugar, o elige un nombre diferente.",
          "valueLabel": "Valor",
          "valueHint": "Oculto mientras escribes. El valor guardado nunca se devuelve a través de la API.",
          "addedToast": "Secreto añadido",
          "updatedToast": "Secreto actualizado",
          "saveFailedToast": "Error al guardar"
        }
      },
      "skills": {
        "title": "Habilidades del agente",
        "description": "Capacidades reutilizables inyectadas en las sessions de Claude como un índice de Tier 1, el agente carga el SKILL.md completo bajo demanda mediante <1>opendray skill describe &lt;id&gt;</1>. Las integradas vienen en el binario pero se pueden <3>personalizar</3>, tus ediciones se guardan en <5>~/.opendray/vault/skills/&lt;id&gt;/SKILL.md</5> y anulan la versión incorporada. Usa Restablecer para revertir.",
        "newSkill": "Nueva habilidad",
        "empty": "No se encontraron habilidades.",
        "columns": {
          "id": "ID",
          "description": "Descripción",
          "source": "Origen"
        },
        "noDescription": "sin descripción",
        "builtinBadge": "integrada",
        "builtinTooltip": "Incorporada en el binario de opendray, haz clic en Personalizar para anularla en tu vault",
        "vaultBadge": "vault",
        "overridesBuiltin": "anula la integrada",
        "overridesBuiltinTooltip": "Esta habilidad del vault anula la versión integrada del mismo id",
        "customize": "Personalizar",
        "customizeTooltip": "Abre el SKILL.md y guarda los cambios como una anulación del vault",
        "editTooltip": "Editar esta habilidad del vault",
        "resetTooltip": "Eliminar la anulación del vault y volver a la versión integrada",
        "reset": "Restablecer",
        "resetConfirm": "¿Restablecer \"{id}\" a la versión integrada? Esto elimina tu SKILL.md del vault y vuelve a la copia incorporada.",
        "deleteConfirm": "¿Eliminar la habilidad \"{id}\" de tu vault? Esto elimina el archivo SKILL.md.",
        "removedToast": "Habilidad eliminada",
        "deleteFailedToast": "Error al eliminar",
        "dropHint": "O suelta un SKILL.md aquí para instalarlo.",
        "dropToInstall": "Suelta el SKILL.md para instalar",
        "uploading": "Instalando habilidad…",
        "uploadedToast": "Habilidad \"{id}\" instalada",
        "uploadFailedToast": "Error al subir la habilidad",
        "uploadInvalidTypeToast": "Solo se pueden instalar archivos SKILL.md por arrastre",
        "editor": {
          "createTitle": "Nueva habilidad",
          "customizeTitle": "Personalizar la integrada: {id}",
          "editTitle": "Editar habilidad: {id}",
          "customizeDescription": "Estás viendo una habilidad integrada incorporada en opendray. Al guardar se creará una anulación del vault con el mismo id, tus ediciones se guardan en ~/.opendray/vault/skills/<id>/SKILL.md y ocultan la integrada hasta que la Restablezcas.",
          "editDescription": "Formato SKILL.md: frontmatter con name + description, luego instrucciones en markdown. La descripción aparece en el índice de Tier 1 del agente.",
          "idLabel": "ID",
          "idPlaceholder": "my-helper",
          "idHint": "Minúsculas / dígitos / guion / guion bajo. Se convierte en el nombre del directorio bajo <1>~/.opendray/vault/skills/&lt;id&gt;/</1>.",
          "bodyLabel": "SKILL.md",
          "createdToast": "Habilidad creada",
          "savedToast": "Habilidad guardada",
          "savedOverrideToast": "Guardada como anulación del vault",
          "createFailedToast": "Error al crear",
          "saveFailedToast": "Error al guardar",
          "saveAsOverride": "Guardar como anulación del vault"
        }
      },
      "customTasks": {
        "title": "Tareas personalizadas",
        "description": "Atajos de ejecución con un clic que se muestran en la pestaña Tareas. Deja cwd en blanco para tareas globales visibles en todas las sessions, o fíjalo a una ruta absoluta para acotarlo.",
        "addTask": "Añadir tarea",
        "empty": "Aún no hay tareas personalizadas.",
        "columns": {
          "name": "Nombre",
          "command": "Comando",
          "scope": "Ámbito"
        },
        "globalScope": "global",
        "deleteConfirm": "¿Eliminar la tarea personalizada \"{name}\"?",
        "removedToast": "Tarea eliminada",
        "deleteFailedToast": "Error al eliminar",
        "dialog": {
          "addTitle": "Añadir tarea personalizada",
          "editTitle": "Editar {name}",
          "description": "El comando se envía textualmente a la terminal de la session. Es lo mismo que escribirlo en el prompt y pulsar Enter.",
          "nameLabel": "Nombre",
          "namePlaceholder": "dev",
          "commandLabel": "Comando",
          "commandPlaceholder": "docker compose up --build",
          "descLabel": "Descripción (opcional)",
          "descPlaceholder": "Arranca la infraestructura de desarrollo y sigue los logs",
          "cwdLabel": "Ámbito de cwd (opcional)",
          "cwdPlaceholder": "/Users/me/projects/foo  (en blanco = global)",
          "cwdHint": "En blanco = visible en todas las sessions. De lo contrario, la tarea solo se muestra cuando el cwd de la session coincide con esta ruta absoluta.",
          "addedToast": "Tarea añadida",
          "updatedToast": "Tarea actualizada",
          "addFailedToast": "Error al añadir",
          "updateFailedToast": "Error al actualizar"
        }
      },
      "gitHosts": {
        "title": "Hosts de git",
        "description": "Un token por host, usado por la pestaña Git para obtener los pull requests <1>y por la sincronización del vault de Notas</1> cuando su remoto usa HTTPS hacia un repo privado en el mismo host. Se admiten GitHub.com, GitHub Enterprise autoalojado, Gitea y GitLab.",
        "addHost": "Añadir host",
        "empty": "No hay hosts de git configurados.\nAñade uno para habilitar la lista de PR en la pestaña Git del inspector.",
        "columns": {
          "host": "Host",
          "kind": "Tipo",
          "token": "Token",
          "enabled": "Habilitado"
        },
        "statusEnabled": "habilitado",
        "statusDisabled": "deshabilitado",
        "deleteConfirm": "¿Eliminar el host de git {host}? Las consultas de PR contra este host dejarán de funcionar.",
        "removedToast": "Host de git eliminado",
        "deleteFailedToast": "Error al eliminar",
        "dialog": {
          "addTitle": "Añadir host de git",
          "editTitle": "Editar {host}",
          "description": "El token se almacena en el gateway. Se usa solo para llamadas de solo lectura a la API (listar PR, etc.).",
          "kindLabel": "Tipo",
          "kindGitHub": "GitHub",
          "kindGitea": "Gitea",
          "kindGitLab": "GitLab",
          "hostLabel": "Host",
          "hostPlaceholder": "github.com",
          "displayNameLabel": "Nombre visible (opcional)",
          "displayNamePlaceholder": "Personal",
          "tokenLabel": "Token",
          "newTokenLabel": "Nuevo token (déjalo en blanco para conservarlo)",
          "tokenPlaceholder": "ghp_… / gho_… / glpat-…",
          "tokenPlaceholderEdit": "…",
          "tokenHint": "GitHub: PAT con scope <1>repo</1>. Gitea: token con <3>read:repository</3>. GitLab: PAT con <5>read_api</5>.",
          "enabledLabel": "Habilitado",
          "addedToast": "Host de git añadido",
          "updatedToast": "Host de git actualizado",
          "addFailedToast": "Error al añadir",
          "updateFailedToast": "Error al actualizar"
        }
      }
    },
    "backups": {
      "title": "Copias de seguridad",
      "subtitle": "Volcados cifrados de PostgreSQL escritos en un destino conectable. Configura programaciones y retención, o lanza copias puntuales para tener una red de seguridad rápida.",
      "exportData": "Exportar datos",
      "loading": "Cargando…",
      "loadStatusFailedToast": "No se pudo cargar el estado de la copia de seguridad",
      "tabs": {
        "backups": "Copias de seguridad",
        "schedules": "Programaciones",
        "targets": "Destinos"
      },
      "inventory": {
        "title": "¿Qué hay en una copia de seguridad?",
        "summary": "{rows} filas en {tables} tablas",
        "description": "Cada copia de seguridad es un <1>pg_dump --format=custom</1> de cada tabla de abajo, más <3>manifest.json</3> y (opcionalmente) <5>config.toml</5>. Los recuentos son en vivo; el paquete captura lo que haya en el momento de la copia.",
        "loadFailedToast": "No se pudo cargar el inventario",
        "rowsLabel": "filas"
      },
      "restart": {
        "title": "Reinicia opendray para activar las copias de seguridad",
        "description": "Tu frase de contraseña está guardada. El gateway solo la carga al iniciarse, así que la función permanece desactivada hasta que reinicies el proceso.",
        "keyFile": "Archivo de clave:",
        "configuredVia": "Configurado mediante:",
        "envVar": "variable de entorno OPENDRAY_BACKUP_KEY",
        "checkAgain": "Comprobar de nuevo"
      },
      "setup": {
        "title": "Configurar copias de seguridad",
        "description": "Elige una frase de contraseña maestra. opendray la usa para cifrar cada blob de copia de seguridad. <1>Si la pierdes, tus copias de seguridad serán irrecuperables</1>, así que guárdala en un gestor de contraseñas (Vaultwarden, 1Password, …) antes de continuar.",
        "generate": "Generar",
        "pasteOwn": "Pegar la mía",
        "generateTitle": "Clave aleatoria de 256 bits",
        "generateHint": "El servidor genera una frase de contraseña criptográficamente aleatoria y la muestra una sola vez. Debes copiarla antes de continuar, no hay forma de recuperarla.",
        "pasteLabel": "Tu frase de contraseña",
        "pastePlaceholder": "Al menos 20 caracteres",
        "pasteHint": "Recomendado: más de 40 caracteres desde un gestor de contraseñas.",
        "savesTo": "Se guarda en:",
        "saving": "Guardando…",
        "generateAndSave": "Generar y guardar",
        "save": "Guardar"
      },
      "generated": {
        "title": "Guarda esta frase de contraseña AHORA",
        "description": "Esto se muestra <1>una sola vez</1>. No se podrá recuperar desde opendray ni desde ningún otro sitio. Cópiala en un gestor de contraseñas antes de continuar.",
        "copy": "Copiar",
        "copiedToast": "Frase de contraseña copiada al portapapeles",
        "copyFailedToast": "Error al copiar, selecciónala y cópiala manualmente",
        "savedTo": "Guardada en:",
        "ack": "He guardado esta frase de contraseña en mi gestor de contraseñas",
        "continue": "Continuar"
      },
      "status": {
        "keyFingerprint": "Huella de la clave:",
        "pgDump": "pg_dump:",
        "pgDumpUnavailable": "no disponible",
        "pgDumpHint": "Las copias de seguridad no pueden ejecutarse hasta que pg_dump esté en PATH (o se haya definido su ruta absoluta en <1>backup.pg_dump_path</1>). Instala <3>postgresql-client</3> de la misma versión mayor que tu servidor y reinicia."
      },
      "backupsTab": {
        "backupNow": "Hacer copia ahora",
        "triggering": "Lanzando…",
        "includeConfig": "incluir config.toml",
        "restoreFromFile": "Restaurar desde archivo",
        "refresh": "Actualizar",
        "queuedToast": "Copia de seguridad en cola",
        "triggerFailedToast": "Error al lanzar",
        "listFailedToast": "No se pudieron listar las copias de seguridad",
        "deleteConfirm": "¿Eliminar la copia de seguridad {id}? El blob se elimina de su destino.",
        "deletedToast": "Copia de seguridad eliminada",
        "deleteFailedToast": "Error al eliminar",
        "empty": "Aún no hay copias de seguridad. Haz clic en \"Hacer copia ahora\" arriba para crear la primera.",
        "columns": {
          "id": "ID",
          "target": "Destino",
          "status": "Estado",
          "started": "Iniciada",
          "size": "Tamaño",
          "actions": "Acciones"
        },
        "downloadTooltip": "Descargar",
        "deleteTooltip": "Eliminar"
      },
      "restore": {
        "title": "Restaurar desde un paquete de copia de seguridad",
        "bundleLabel": "Paquete cifrado (.tar.gz.enc)",
        "targetDsnLabel": "DSN de la base de datos de destino",
        "targetDsnHint": "(en blanco = la propia base de datos de opendray, PELIGROSO)",
        "targetDsnPlaceholder": "postgres://user:pass@host:5432/dbname",
        "cleanLabel": "--clean --if-exists (eliminar primero el esquema existente; obligatorio al restaurar sobre una base de datos con datos)",
        "auditNoteLabel": "Nota de auditoría (opcional)",
        "auditNotePlaceholder": "Motivo de la restauración, aparece en el slog",
        "ownDbWarning": "Estás restaurando en <1>la propia base de datos de opendray</1>. Con \"--clean\" activado, esto elimina todas las tablas y reproduce la copia de seguridad tal cual, es irreversible. Escribe <3>I understand</3> para continuar.",
        "confirmPlaceholder": "I understand",
        "confirmSentinel": "I understand",
        "pgRestoreOutput": "Salida de pg_restore (últimos 8 KiB)",
        "noPgRestoreOutput": "(sin salida de pg_restore)",
        "pickFileToast": "Elige primero un archivo de paquete",
        "succeededToast": "Restauración correcta",
        "replayedDescription": "{bytes} reproducidos desde el manifest {id}",
        "failedToast": "Error en la restauración",
        "restoring": "Restaurando…",
        "restore": "Restaurar"
      },
      "schedulesTab": {
        "description": "Copias de seguridad periódicas. El programador consulta cada 30 s y ejecuta la programación pendiente más antigua.",
        "newSchedule": "Nueva programación",
        "loadFailedToast": "No se pudieron cargar las programaciones",
        "deleteConfirm": "¿Eliminar la programación {id}?",
        "deletedToast": "Programación eliminada",
        "deleteFailedToast": "Error al eliminar",
        "toggleFailedToast": "Error al alternar",
        "empty": "No hay programaciones. Añade una para hacer copias de seguridad periódicas automáticas.",
        "columns": {
          "id": "ID",
          "target": "Destino",
          "interval": "Intervalo",
          "keep": "Conservar",
          "nextRun": "Próxima ejecución",
          "enabled": "Habilitada",
          "actions": "Acciones"
        },
        "keepCount": "{count} copias de seguridad",
        "deleteTooltip": "Eliminar"
      },
      "newSchedule": {
        "title": "Nueva programación de copia de seguridad",
        "targetLabel": "Destino",
        "everyHoursLabel": "Cada (horas)",
        "keepLastNLabel": "Conservar las últimas N",
        "enableImmediately": "Habilitar inmediatamente",
        "createdToast": "Programación creada",
        "createFailedToast": "Error al crear",
        "creating": "Creando…",
        "create": "Crear"
      },
      "targetsTab": {
        "description": "Destinos de almacenamiento. v1 admite <1>local</1> (disco en el host de opendray) y <3>smb</3> (cualquier recurso compartido SMB / CIFS, p. ej. UNAS o Synology).",
        "newTarget": "Nuevo destino",
        "listFailedToast": "No se pudieron listar los destinos",
        "deleteConfirm": "¿Eliminar el destino {id}? Las programaciones que lo referencien bloquearán la eliminación.",
        "deletedToast": "Destino eliminado",
        "deleteFailedToast": "Error al eliminar",
        "connectionOkToast": "Conexión correcta",
        "connectionFailedToast": "Error de conexión",
        "testFailedToast": "Error en la prueba",
        "columns": {
          "id": "ID",
          "kind": "Tipo",
          "config": "Config",
          "enabled": "Habilitado",
          "actions": "Acciones"
        },
        "on": "activado",
        "off": "desactivado",
        "test": "Probar",
        "testing": "Probando…",
        "deleteTooltip": "Eliminar"
      },
      "targetEditor": {
        "title": "Nuevo destino de copia de seguridad",
        "kindPicker": "¿Dónde quieres hacer la copia de seguridad?",
        "idLabel": "ID (opcional)",
        "idPlaceholder": "se genera automáticamente si se deja en blanco, p. ej. tgt_xxx",
        "createdToast": "Destino creado",
        "createFailedToast": "Error al crear",
        "creating": "Creando…",
        "create": "Crear destino",
        "enableImmediately": "Habilitar inmediatamente (de lo contrario se guarda como deshabilitado, útil para \"configurar ahora, activar más tarde\")",
        "local": {
          "rootLabel": "Directorio raíz",
          "rootHint": "Vacío = cfg.backup.local_dir (~/.opendray/backups)",
          "rootPlaceholder": "~/backups/opendray  o  /mnt/external-hdd/opendray"
        },
        "smb": {
          "hostLabel": "Host",
          "hostPlaceholder": "192.168.1.20",
          "portLabel": "Puerto",
          "shareLabel": "Recurso compartido",
          "shareHint": "Nombre del recurso compartido de nivel superior en el servidor SMB",
          "sharePlaceholder": "Claude_Workspace",
          "userLabel": "Usuario",
          "passwordLabel": "Contraseña",
          "pathPrefixLabel": "Prefijo de ruta",
          "pathPrefixHint": "Subcarpeta bajo la raíz del recurso compartido (opcional)",
          "pathPrefixPlaceholder": "opendray/backups"
        },
        "s3": {
          "endpointLabel": "Endpoint",
          "endpointHint": "Host (sin protocolo). AWS: s3.amazonaws.com · R2: <accountid>.r2.cloudflarestorage.com · MinIO: minio.local:9000",
          "endpointPlaceholder": "s3.amazonaws.com",
          "regionLabel": "Región",
          "regionHint": "Solo AWS; en R2 usa 'auto'",
          "regionPlaceholder": "us-east-1 / auto",
          "bucketLabel": "Bucket",
          "bucketPlaceholder": "opendray-backups",
          "accessKeyLabel": "Clave de acceso",
          "secretKeyLabel": "Clave secreta",
          "secretKeyHint": "Se almacena cifrada con AES-256-GCM; nunca se devuelve",
          "pathPrefixLabel": "Prefijo de ruta",
          "pathPrefixHint": "Prefijo de clave de objeto (opcional)",
          "pathPrefixPlaceholder": "opendray/backups",
          "useHttps": "Usar HTTPS",
          "pathStyle": "Direccionamiento de tipo ruta (heredado / MinIO)"
        },
        "webdav": {
          "baseUrlLabel": "URL base",
          "baseUrlHint": "URL completa incluyendo cualquier ruta. Ejemplos: https://cloud.example.com/remote.php/dav/files/me/ (Nextcloud), https://nas.local:5006/ (Synology), https://dav.jianguoyun.com/dav/ (Jianguoyun / 坚果云)",
          "baseUrlPlaceholder": "https://cloud.example.com/remote.php/dav/files/<user>/",
          "userLabel": "Usuario",
          "passwordLabel": "Contraseña",
          "pathPrefixLabel": "Prefijo de ruta",
          "pathPrefixHint": "Subcarpeta bajo la URL base (opcional)",
          "pathPrefixPlaceholder": "opendray/backups"
        },
        "sftp": {
          "hostLabel": "Host",
          "hostPlaceholder": "vps.example.com",
          "portLabel": "Puerto",
          "userLabel": "Usuario",
          "passwordLabel": "Contraseña",
          "passwordHint": "Se requiere contraseña O clave privada. Si se indican ambas, la contraseña se trata como la frase de contraseña de la clave.",
          "privateKeyLabel": "Clave privada (PEM)",
          "privateKeyHint": "Pega el contenido de una clave privada OpenSSH/PEM (p. ej. ~/.ssh/id_ed25519). Déjalo en blanco para autenticación solo con contraseña.",
          "privateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----...",
          "hostKeyLabel": "Clave de host (fijación)",
          "hostKeyHint": "Clave pública del servidor en formato OpenSSH (ejecuta `ssh-keyscan host` para obtenerla). Déjalo en blanco para desactivar la fijación (NO recomendado fuera de la LAN).",
          "hostKeyPlaceholder": "ssh-ed25519 AAAA...",
          "pathPrefixLabel": "Prefijo de ruta",
          "pathPrefixHint": "Absoluta o relativa al directorio personal del usuario (opcional)",
          "pathPrefixPlaceholder": "/var/backups/opendray  o  opendray-backups"
        },
        "rclone": {
          "rcloneHint": "Requiere tener instalada la CLI de <1>rclone</1> en el host de opendray. Primero configura tu remoto con <3>rclone config</3>, luego usa el nombre del remoto de abajo. opendray invoca <5>rclone rcat / cat / lsd</5> internamente.",
          "remoteLabel": "Nombre del remoto",
          "remoteHint": "Nombre de `rclone config` (sin dos puntos). Ejemplo: gdrive, onedrive, dropbox-personal, baidu-pan",
          "remotePlaceholder": "gdrive",
          "pathPrefixLabel": "Prefijo de ruta",
          "pathPrefixHint": "Subcarpeta bajo la raíz del remoto (opcional)",
          "pathPrefixPlaceholder": "opendray/backups",
          "binaryPathLabel": "Ruta del binario",
          "binaryPathHint": "Anula `which rclone`. Si está vacío, usa la búsqueda en PATH.",
          "binaryPathPlaceholder": "/opt/homebrew/bin/rclone",
          "configPathLabel": "Ruta de configuración",
          "configPathHint": "Anula --config (por defecto ~/.config/rclone/rclone.conf o ~/.rclone.conf)",
          "configPathPlaceholder": "déjalo en blanco para el valor por defecto de rclone"
        }
      }
    },
    "serverSettings": {
      "sections": {
        "general": {
          "title": "General",
          "desc": "Dirección de escucha, cuenta de operador, TTL del token."
        },
        "logging": {
          "title": "Registro",
          "desc": "Verbosidad, formato y seguimiento en vivo."
        },
        "sessions": {
          "title": "Sesiones",
          "desc": "Umbrales de detección de inactividad."
        },
        "vault": {
          "title": "Vault",
          "desc": "Notas, skills y raíz versionada con git."
        },
        "mcp": {
          "title": "Registro de MCP",
          "desc": "Registro de servidores + secretos."
        },
        "memory": {
          "title": "Memory",
          "desc": "Subsistema de memoria persistente entre CLI."
        },
        "backup": {
          "title": "Backup",
          "desc": "Copias de seguridad cifradas de la DB, restauración y exportaciones de datos de administración."
        },
        "claude": {
          "title": "Almacenamiento · Claude",
          "desc": "Dónde se guardan los transcripts de Claude en disco."
        },
        "codex": {
          "title": "Almacenamiento · Codex",
          "desc": "Raíz de sesiones de Codex."
        },
        "gemini": {
          "title": "Almacenamiento · Gemini",
          "desc": "Carpeta tmp por proyecto de Gemini + projects.json."
        }
      },
      "loading": "Cargando ajustes del servidor…",
      "loadFailed": "Error al cargar: {message}",
      "noConfigFlag": "opendray se inició sin la opción -config. Los ajustes se cargan únicamente desde variables de entorno y no pueden editarse aquí.",
      "resetButton": "Restablecer",
      "resetButtonTitle": "Descartar los cambios sin guardar de esta sección",
      "resetConfirm": "¿Restablecer \"{section}\" a los últimos valores guardados?",
      "badgeRestartRequired": "requiere reinicio",
      "badgeUnsaved": "sin guardar",
      "saveButton": "Guardar cambios",
      "saveToastTitle": "Ajustes guardados",
      "saveToastDesc": "Haz clic en Reiniciar para aplicarlos.",
      "saveErrorTitle": "Error al guardar",
      "dangerousConfirm": "Has cambiado la dirección de escucha, el usuario de administración o la contraseña de administración. Tras reiniciar puede que necesites volver a autenticarte o usar la nueva dirección. ¿Continuar?",
      "unsavedHint": "Tienes cambios sin guardar",
      "savedHint": "Todos los cambios guardados",
      "searchPlaceholder": "Filtrar campos…",
      "restart": {
        "button": "Reiniciar servidor",
        "buttonTitle": "Auto-ejecutar el proceso del gateway",
        "dirtyConfirm": "Tienes cambios sin guardar. El reinicio usará la ÚLTIMA configuración GUARDADA. ¿Continuar?",
        "confirm": "¿Reiniciar el gateway de opendray? Todas las sesiones de terminal abiertas se reconectarán automáticamente.",
        "overlay": "Reiniciando servidor…",
        "waiting": "Esperando a /health · {tick}s",
        "timedOutTitle": "Se agotó el tiempo del reinicio",
        "timedOutDesc": "El endpoint de salud nunca respondió. Revisa los logs del servidor.",
        "successToast": "Servidor reiniciado"
      },
      "formGroups": {
        "network": "Red",
        "operatorAccount": "Cuenta de operador",
        "memoryConfiguration": "Configuración",
        "memoryHttp": "Backend HTTP (se usa cuando backend=http)",
        "memoryLocal": "ONNX local (se usa cuando backend=local)",
        "backupStatus": "Estado",
        "backupWhere": "Dónde van las copias de seguridad",
        "backupSchedules": "Programaciones",
        "backupWhatsInside": "¿Qué contiene una copia de seguridad?"
      },
      "fields": {
        "listenAddress": {
          "label": "Dirección de escucha",
          "hint": "El host:port al que se vincula el servidor HTTP. Ejemplo: 0.0.0.0:8770."
        },
        "username": {
          "label": "Nombre de usuario",
          "hint": "Nombre de inicio de sesión usado en el formulario de acceso. Cambiarlo obliga a volver a iniciar sesión en la siguiente solicitud."
        },
        "password": {
          "label": "Contraseña",
          "hint": "Déjalo en blanco para mantener la contraseña actual. Enviar un valor la sobrescribe.",
          "hideTitle": "Ocultar",
          "revealTitle": "Mostrar"
        },
        "tokenTTL": {
          "label": "TTL del token",
          "hint": "Tiempo de vida del bearer-token como duración de Go, p. ej. \"24h\", \"30m\". Vacío = nunca expira."
        },
        "logLevel": {
          "label": "Nivel de log",
          "hint": "Las líneas por debajo de este nivel se descartan."
        },
        "logFormat": {
          "label": "Formato",
          "hint": "\"text\" es legible para humanos; \"json\" es analizable por máquinas."
        },
        "logFile": {
          "label": "Archivo de log",
          "hint": "Ruta de archivo opcional. Rota automáticamente a los 10 MB, conserva 5 copias. Vacío = solo stderr."
        },
        "idleThreshold": {
          "label": "Umbral de inactividad",
          "hint": "Una session permanece en silencio este tiempo antes de que se dispare session.idle. Vacío = 30s."
        },
        "idlePollInterval": {
          "label": "Intervalo de sondeo de inactividad",
          "hint": "Con qué frecuencia se activa el detector de inactividad. Más bajo = menor latencia, más activaciones. Vacío = 5s."
        },
        "vaultRoot": {
          "label": "Raíz del Vault",
          "hint": "Directorio de nivel superior para notas, skills y el registro de MCP."
        },
        "notesDirectory": {
          "label": "Directorio de notas",
          "hint": "Anula la ubicación de las notas. Por defecto <vault root>/notes."
        },
        "skillsDirectory": {
          "label": "Directorio de skills",
          "hint": "Anula la ubicación de los skills. Por defecto <vault root>/skills."
        },
        "gitRoot": {
          "label": "Raíz de git",
          "hint": "Árbol de trabajo al que confirma la función Vault Sync."
        },
        "personalPrefix": {
          "label": "Prefijo personal",
          "hint": "Nombre de carpeta usado para las notas personales al derivar rutas automáticamente. Por defecto \"personal\"."
        },
        "projectsPrefix": {
          "label": "Prefijo de proyectos",
          "hint": "Nombre de carpeta usado para las notas de proyecto. Por defecto \"projects\"."
        },
        "registryRoot": {
          "label": "Raíz del registro",
          "hint": "Directorio que contiene las definiciones JSON de los servidores MCP. Por defecto <vault>/mcp."
        },
        "secretsFile": {
          "label": "Archivo de secretos",
          "hint": "Archivo key=value que se sustituye en los comandos del servidor MCP en el momento del arranque."
        },
        "memoryBackend": {
          "label": "Backend del embedder",
          "hint": "\"auto\" / \"bm25\" usan la ruta de palabras clave en Go puro, sin cgo. \"http\" llama a cualquier /v1/embeddings compatible con OpenAI (ollama / OpenAI / LocalAI). \"local\" ejecuta un sentence-transformer ONNX en el proceso, requiere un binario compilado con `-tags local_onnx`."
        },
        "memoryStore": {
          "label": "Almacén",
          "hint": "\"pgvector\" reutiliza la PG existente de opendray con la extensión vector; es la única opción en la v1."
        },
        "memoryTopK": {
          "label": "Top-K por defecto",
          "hint": "Cuántos resultados devuelve memory_search cuando el agente no lo especifica. Vacío = 5."
        },
        "memoryThreshold": {
          "label": "Umbral de similitud",
          "hint": "Los resultados por debajo de esta puntuación se descartan. Vacío = 0.1 (permisivo, los vectores dispersos de BM25 rara vez superan 0.5)."
        },
        "memoryScope": {
          "label": "Ámbito por defecto",
          "hint": "Lo que usa memory_store cuando el agente no lo especifica. \"project\" (recomendado) agrupa por cwd; \"global\" comparte entre cwds."
        },
        "memoryBaseUrl": {
          "label": "URL base",
          "hint": "p. ej. \"http://localhost:11434/v1\" para ollama, \"https://api.openai.com/v1\" para OpenAI."
        },
        "memoryModel": {
          "label": "Modelo",
          "hint": "p. ej. \"nomic-embed-text\" para ollama, \"text-embedding-3-small\" para OpenAI."
        },
        "memoryApiKey": {
          "label": "API key",
          "hint": "Vacío para ollama / servidores locales. Obligatorio para OpenAI / Voyage / servicios alojados."
        },
        "memoryLocalModel": {
          "label": "Nombre del modelo",
          "hint": "Cosmético, aparece en los logs / el Inspector. p. ej. \"bge-m3\", \"bge-small-en-v1.5\"."
        },
        "memoryLibraryPath": {
          "label": "Ruta de la biblioteca",
          "hint": "Directorio que contiene libonnxruntime.dylib (macOS) / libonnxruntime.so (Linux). Tras `brew install onnxruntime`, es /opt/homebrew/opt/onnxruntime/lib."
        },
        "memoryModelPath": {
          "label": "Ruta del modelo",
          "hint": "Ruta absoluta a los pesos .onnx. Descárgalos de HuggingFace, p. ej. Xenova/bge-m3 o Xenova/bge-small-en-v1.5."
        },
        "memoryTokenizerPath": {
          "label": "Ruta del tokenizador",
          "hint": "Ruta absoluta a tokenizer.json (formato estándar de HuggingFace), normalmente justo al lado del modelo."
        },
        "memoryMaxSeqLen": {
          "label": "Longitud máxima de secuencia",
          "hint": "Los tokens que superan este límite se truncan. El valor por defecto de bge-m3 es 512. Vacío = 512."
        },
        "claudeHistoryRoots": {
          "label": "Raíces de historial",
          "hint": "Directorios que se escanean en busca de los transcripts JSONL por proyecto de Claude. Vacío = escanear ~/.claude/projects + cada ~/.claude-accounts/*/projects."
        },
        "claudeAccountsDir": {
          "label": "Directorio de cuentas",
          "hint": "Raíz usada para los ConfigDirs de las cuentas de Claude gestionadas por opendray. Por defecto ~/.claude-accounts."
        },
        "codexSessionsRoot": {
          "label": "Raíz de sesiones",
          "hint": "Directorio que se recorre en busca de los archivos JSONL de rollout de Codex. Por defecto ~/.codex/sessions."
        },
        "geminiTmpRoot": {
          "label": "Directorio tmp",
          "hint": "Raíz que contiene las carpetas tmp por proyecto de Gemini. Por defecto ~/.gemini/tmp."
        },
        "geminiProjectsFile": {
          "label": "projects.json",
          "hint": "Ruta al archivo de mapeo cwd→nombre-corto de Gemini. Por defecto ~/.gemini/projects.json."
        },
        "backupLocalDir": {
          "label": "Directorio local de copias de seguridad",
          "hint": "Raíz por defecto para el destino `local` creado automáticamente. Vacío = ~/.opendray/backups. Requiere reinicio."
        },
        "backupExportDir": {
          "label": "Directorio de exportación",
          "hint": "Dónde se preparan en disco los zips de exportación puntual. Vacío = ~/.opendray/exports. Los paquetes expiran automáticamente tras 24h. Requiere reinicio."
        },
        "backupPgDumpPath": {
          "label": "Ruta de pg_dump",
          "hint": "Ruta absoluta a pg_dump. La versión mayor debe ser ≥ la del servidor. Vacío = el primer pg_dump en el PATH."
        },
        "backupPgRestorePath": {
          "label": "Ruta de pg_restore",
          "hint": "Ruta absoluta a pg_restore para el flujo /backups/restore. Misma regla de versión mayor."
        }
      },
      "liveTail": {
        "heading": "Seguimiento en vivo",
        "description": "Búfer circular en memoria (los últimos ~2.000 registros). Se reinicia al reiniciar."
      },
      "memoryInspectorCard": {
        "heading": "Inspector",
        "description": "Explora, busca y edita las memorias almacenadas en la página dedicada.",
        "openButton": "Abrir Memory →"
      },
      "localOnnxBanner": "Requiere que el binario se compile con <1>-tags local_onnx</1>. La compilación estándar devuelve un error de stub claro cuando se selecciona este backend. Consulta el tutorial <3>Memory → ONNX local</3> para los pasos de configuración.",
      "stringList": {
        "noneDefault": "(ninguno, usando los valores por defecto integrados)",
        "addPath": "Añadir ruta",
        "removeTitle": "Eliminar"
      },
      "httpHelpers": {
        "autoDetected": "Detectado automáticamente al arrancar",
        "modelCount": "{count} modelo(s), haz clic para usar",
        "presets": "Preajustes:",
        "testConnection": "Probar conexión",
        "presetTip": {
          "ollama": "Daemon local de ollama",
          "lmStudio": "Servidor local de LM Studio",
          "openai": "Nube de OpenAI (necesita API key)"
        }
      },
      "probe": {
        "unreachable": "✗ inaccesible: {error}",
        "connectionFailed": "conexión fallida",
        "reachable": "✓ accesible {detected}· {total} modelo(s) en total · {embedding} embedding",
        "modelMissing": "⚠ El modelo configurado {model} no está en la lista. Elige uno de los modelos de embedding de abajo o corrige el nombre.",
        "embeddingModelsLabel": "modelos de embedding:",
        "moreModels": "+{count} más",
        "noEmbeddingFound": "⚠ Ningún nombre de modelo contiene \"embed\". Puede que el endpoint no tenga cargado un modelo de embedding, revisa tu servidor local.",
        "configuredTitle": "Configurado actualmente",
        "applyTitle": "Haz clic para aplicar"
      },
      "backup": {
        "featureDisabledTitle": "Función deshabilitada",
        "featureDisabledHint": "Define <1>OPENDRAY_BACKUP_ENABLED=1</1> + <3>OPENDRAY_BACKUP_KEY=&lt;passphrase&gt;</3> en el entorno de opendray y luego reinicia. La passphrase maestra es solo de entorno, nunca toca config.toml.",
        "statusRowLabel": "Estado",
        "enabledHealthy": "habilitado · correcto",
        "enabledDegraded": "habilitado · degradado",
        "keyFingerprintLabel": "Huella de la clave",
        "keyFingerprintHint": "guárdala en Vaultwarden, perderla bloquea todas las copias de seguridad anteriores",
        "pgDumpLabel": "pg_dump",
        "pgDumpUnavailable": "no disponible",
        "pgRestoreLabel": "pg_restore",
        "pgRestoreNotResolved": "(no resuelto)",
        "openBackups": "Abrir la página de Backups →",
        "openExport": "Abrir Exportar / Importar →",
        "whereDesc": "Cada destino es un lugar donde puede escribirse un blob de copia de seguridad. opendray admite <1>disco local</1>, <3>SMB/CIFS</3> (Windows / NAS), <5>compatible con S3</5> (AWS, R2, B2, MinIO, Alibaba Cloud OSS, Tencent Cloud COS, ...), <7>WebDAV</7> (Nextcloud, Synology, Jianguoyun), <9>SFTP</9>, además de un puente <11>rclone</11> que conecta con más de 70 backends adicionales (Google Drive, OneDrive, Dropbox, Baidu Pan, Aliyun Drive, ...).",
        "loading": "Cargando…",
        "noTargets": "Aún no hay destinos. Añade uno para empezar a hacer copias de seguridad.",
        "addTarget": "Añadir destino",
        "noSchedulesHint": "No hay programaciones recurrentes. Añade una en <1>/backups → Programaciones</1> para hacer copias de seguridad automáticamente.",
        "scheduleHeaders": {
          "schedule": "Programación",
          "target": "Destino",
          "cadence": "Cadencia",
          "keep": "Conservar",
          "state": "Estado"
        },
        "every": "cada {interval}",
        "backupsKeep": "{count} copias de seguridad",
        "stateEnabled": "habilitado",
        "statePaused": "pausado",
        "manageSchedules": "Gestionar en /backups → Programaciones →",
        "whatsInsideDesc": "Cada copia de seguridad es un <1>pg_dump --format=custom</1> de cada tabla de opendray (sessions, integraciones, memorias, audit_log, etc.) más un <3>manifest.json</3> y (opcionalmente) el <5>config.toml</5> en vivo. Abre el panel \"¿Qué contiene una copia de seguridad?\" en la <7>página de Backups</7> para ver el inventario en vivo con el número de filas.",
        "advancedToggle": "Avanzado (rutas y binarios cliente), requiere reinicio"
      },
      "targetRow": {
        "on": "activado",
        "off": "desactivado",
        "test": "Probar",
        "testing": "Probando…",
        "delete": "Eliminar",
        "connectionOk": "{id}: conexión correcta",
        "connectionFailedTitle": "Conexión fallida",
        "testFailedTitle": "Prueba fallida",
        "deleteConfirm": "¿Eliminar el destino \"{id}\"? Las programaciones que lo referencien bloquearán la eliminación.",
        "deleteSuccess": "Destino eliminado",
        "deleteFailedTitle": "Error al eliminar",
        "unknownError": "Error desconocido"
      }
    },
    "settings": {
      "title": "Ajustes",
      "subtitle": "Configuración del espacio de trabajo, la cuenta y el gateway.",
      "groups": {
        "workspace": "Espacio de trabajo",
        "server": "Servidor",
        "system": "Sistema"
      },
      "items": {
        "appearance": "Apariencia",
        "font": "Tamaño de fuente",
        "account": "Cuenta",
        "status": "Estado",
        "about": "Acerca de"
      },
      "health": {
        "connecting": "conectando…",
        "dbOk": "db ok",
        "dbDown": "db caída"
      },
      "breadcrumb": {
        "server": "Servidor"
      },
      "appearance": {
        "title": "Apariencia",
        "description": "Elige el aspecto de opendray.",
        "options": {
          "light": "Claro",
          "lightDesc": "Siempre claro",
          "dark": "Oscuro",
          "darkDesc": "Siempre oscuro",
          "system": "Sistema",
          "systemDesc": "Seguir la configuración del SO"
        }
      },
      "font": {
        "title": "Tamaño de fuente",
        "description": "Escala toda la interfaz. Se guarda por navegador.",
        "options": {
          "compact": "Compacto",
          "default": "Predeterminado",
          "comfy": "Cómodo",
          "large": "Grande"
        }
      },
      "account": {
        "title": "Cuenta",
        "description": "Operador y token de portador actual.",
        "username": "Nombre de usuario",
        "tokenExpires": "El token caduca",
        "changeCredentials": "Cambiar credenciales"
      },
      "changeCredentials": {
        "title": "Cambiar credenciales",
        "description": "Verifica tu contraseña actual y luego elige nuevas credenciales. Se revocarán todas las demás sesiones con sesión iniciada.",
        "currentPassword": "Contraseña actual",
        "newUsername": "Nuevo nombre de usuario",
        "newPassword": "Nueva contraseña",
        "newPasswordHint": "Al menos 8 caracteres.",
        "confirm": "Confirmar nueva contraseña",
        "errorTooShort": "La nueva contraseña debe tener al menos 8 caracteres.",
        "errorMismatch": "La nueva contraseña y la confirmación no coinciden.",
        "errorWrongPassword": "La contraseña actual es incorrecta.",
        "cancel": "Cancelar",
        "update": "Actualizar",
        "saving": "Guardando…"
      },
      "system": {
        "title": "Estado del sistema",
        "description": "Estado en vivo desde el endpoint /health del gateway.",
        "status": "Estado",
        "version": "Versión",
        "uptime": "Tiempo de actividad",
        "database": "Base de datos",
        "reachable": "accesible",
        "unreachable": "no accesible"
      },
      "about": {
        "title": "Acerca de",
        "description": "opendray v2: el multiplexor + gateway de integración para CLIs de agentes de IA. Código bajo Apache 2.0.",
        "version": "Versión",
        "commit": "Commit",
        "updateAvailable": "Actualización disponible: {version}",
        "releaseNotes": "Notas de la versión ↗",
        "updateNow": "Actualizar ahora",
        "upgradingShort": "Actualizando…",
        "confirmRestart": "Esto reinicia el servicio; las sesiones en ejecución se reconectan.",
        "confirmUpgrade": "Actualizar y reiniciar",
        "upgrading": "Actualizando a {version}…",
        "upgraded": "Actualizado a {version}.",
        "upgradeSlow": "La actualización está tardando un poco. Revisa los logs del servicio si no vuelve.",
        "guidedHint": "La actualización dentro de la app no está disponible aquí. Ejecuta en el servidor:",
        "checkFailed": "No se pudieron comprobar las actualizaciones (sin conexión o con límite de frecuencia).",
        "upToDate": "Tienes la última versión.",
        "checkUpdates": "Comprobar actualizaciones",
        "checking": "Comprobando…",
        "reinstall": "Reinstalar"
      }
    },
    "logViewer": {
      "filterPlaceholder": "Filtrar…",
      "debugTooltip": "Recuento de debug",
      "infoTooltip": "Recuento de info",
      "warnTooltip": "Recuento de advertencias",
      "errorTooltip": "Recuento de errores",
      "streaming": "Transmitiendo",
      "disconnected": "Desconectado",
      "live": "en directo",
      "offline": "sin conexión",
      "pauseTooltip": "Pausar el desplazamiento automático",
      "resumeTooltip": "Reanudar el desplazamiento automático",
      "clearTooltip": "Limpiar la vista local (el ring del servidor no se toca)",
      "downloadTooltip": "Descargar el ring completo como archivo .log",
      "emptyWaiting": "Esperando registros de log…",
      "emptyFiltered": "Ningún registro coincide con \"{query}\""
    },
    "pathInput": {
      "testButton": "Probar",
      "testTooltip": "Resolver y comprobar esta ruta",
      "notFound": "no encontrado ·",
      "childrenSuffix": "elementos",
      "expectedDirectory": "· se esperaba un directorio"
    },
    "memoryAmbient": {
      "header": {
        "title": "Memoria ambiental: captura e inyección automáticas",
        "body": "opendray sondea cada session de agente activa cada 10 segundos, extrae hechos duraderos mediante un LLM configurable y los deduplica antes de almacenarlos en el pool de memoria compartida. Configura qué LLM realiza la extracción (Proveedor), cuándo se activa la extracción (Regla de captura) y qué (si es que algo) se antepone al system prompt del agente al arrancar (Perfil de inyección)."
      },
      "loading": "Cargando…",
      "providers": {
        "title": "Proveedores de resumen",
        "addButton": "Añadir proveedor",
        "intro": "Se requiere al menos un proveedor habilitado para que la captura se active realmente. Las opciones locales (Ollama, LM Studio, Integración) mantienen tus transcripts fuera de redes externas.",
        "empty": "Aún no hay proveedores configurados.",
        "row": {
          "defaultBadge": "★ predeterminado",
          "makeDefault": "Hacer predeterminado",
          "test": "Probar",
          "testing": "Probando…",
          "delete": "Eliminar",
          "testOk": "{name}: conexión correcta",
          "testFailedToast": "La prueba falló",
          "deleteConfirm": "¿Eliminar el proveedor \"{name}\"?",
          "deletedToast": "Proveedor eliminado",
          "deleteFailedToast": "La eliminación falló",
          "updateFailedToast": "La actualización falló",
          "madeDefaultToast": "{name} ahora es el predeterminado"
        },
        "dialog": {
          "title": "Añadir proveedor de resumen",
          "kindLabel": "Tipo",
          "nameLabel": "Nombre",
          "namePlaceholder": "p. ej. lmstudio-qwen",
          "modelLabel": "Modelo",
          "baseUrlLabel": "URL base",
          "integrationNote": "Los proveedores de integración resuelven su URL base a partir de una integración registrada. Configúrala primero en Integraciones; el cableado avanzado (extra_config) es solo de DB en esta versión.",
          "apiKeyLabel": "Clave de API",
          "apiKeyHint": "Se almacena cifrada (AES-GCM con la frase de contraseña maestra de copia de seguridad). Nunca se devuelve; tras guardar solo se muestra la huella digital.",
          "makeDefaultLabel": "Hacer de este el proveedor predeterminado",
          "create": "Crear",
          "nameRequiredToast": "El nombre es obligatorio",
          "createdToast": "Proveedor {name} creado",
          "createFailedToast": "La creación falló"
        }
      },
      "rules": {
        "title": "Reglas de captura",
        "addButton": "Añadir regla",
        "intro": "Cada regla dice \"cuando se active este disparador, resume los nuevos mensajes del transcript y almacena los hechos duraderos.\" Las reglas por session prevalecen sobre el valor predeterminado global. La v1 incluye 4 tipos de disparador.",
        "empty": "Aún no hay reglas de captura. Añade una para habilitar la captura automática.",
        "row": {
          "globalDefault": "predeterminado global",
          "scopeLabel": "ámbito:",
          "dedupLabel": "dedup:",
          "runNow": "Ejecutar ahora",
          "running": "Ejecutando…",
          "delete": "Eliminar",
          "firedToast": "La regla se activó en {sessions} session(es)",
          "runNowFailedToast": "La ejecución inmediata falló",
          "deleteConfirm": "¿Eliminar la regla \"{name}\"?",
          "deletedToast": "Regla eliminada",
          "deleteFailedToast": "La eliminación falló",
          "summary": {
            "afterMessages": "cada {n} mensajes",
            "onIdle": "inactivo ≥ {seconds}s",
            "kChars": "≥ {k} caracteres",
            "manual": "solo manual"
          }
        },
        "dialog": {
          "title": "Añadir regla de captura",
          "nameLabel": "Nombre",
          "triggerLabel": "Disparador",
          "nLabel": "N (mensajes)",
          "idleLabel": "Segundos de inactividad",
          "kLabel": "K (caracteres)",
          "scopeLabel": "Ámbito objetivo",
          "scopeProject": "proyecto (recomendado)",
          "scopeGlobal": "global",
          "dedupLabel": "Umbral de dedup (0.0 a 1.0)",
          "dedupHint": "Más alto = deduplicación más estricta. 0.85 es el punto óptimo recomendado.",
          "create": "Crear",
          "nameRequiredToast": "El nombre es obligatorio",
          "createdToast": "Regla {name} creada",
          "createFailedToast": "La creación falló"
        }
      },
      "profiles": {
        "title": "Perfiles de inyección",
        "addButton": "Añadir perfil",
        "intro": "Al arrancar, opendray antepone un banner en markdown con las memorias recientes del proyecto al system prompt del agente, SI hay un perfil configurado. Sin un perfil, el modelo sigue usando memory_search bajo demanda.",
        "empty": "No hay perfil de inyección. Las memorias no se inyectan automáticamente al arrancar; el modelo sigue usando memory_search.",
        "row": {
          "globalDefault": "predeterminado global",
          "delete": "Eliminar",
          "deleteConfirm": "¿Eliminar este perfil de inyección?",
          "deletedToast": "Perfil eliminado",
          "deleteFailedToast": "La eliminación falló"
        },
        "dialog": {
          "title": "Añadir perfil de inyección",
          "strategyLabel": "Estrategia",
          "kLabel": "K (principales memorias a inyectar)",
          "hint": "Un perfil por session_id (o predeterminado global). Los perfiles por session se pueden añadir más tarde mediante API; la UI actualmente solo gestiona el predeterminado global.",
          "create": "Crear",
          "createdToast": "Perfil creado",
          "createFailedToast": "La creación falló"
        }
      },
      "cost": {
        "title": "Coste de tokens (histórico total)",
        "intro": "Resumen por proveedor agregado a partir de <1>memory_summarizer_calls</1>. Los proveedores locales (Ollama, LM Studio, Integración) tienen precio de $0: el operador asume el coste del hardware.",
        "empty": "No hay proveedores habilitados: no hay datos de coste.",
        "columns": {
          "provider": "Proveedor",
          "calls": "Llamadas",
          "inTokens": "Tokens de entrada",
          "outTokens": "Tokens de salida",
          "usdEst": "USD est."
        }
      }
    },
    "noteEditor": {
      "loading": "Cargando…",
      "source": "Origen",
      "preview": "Vista previa",
      "tagTitle": "etiqueta #{tag}",
      "emptyNote": "Nota vacía. Cambia a Origen para empezar a escribir.",
      "saveFailedToast": "Error al guardar",
      "status": {
        "saveFailed": "error al guardar",
        "saving": "guardando…",
        "unsaved": "sin guardar",
        "newNote": "nota nueva",
        "saved": "guardada"
      }
    },
    "export": {
      "title": "Exportar datos",
      "subtitle": "Genera un paquete zip puntual de las entidades lógicas seleccionadas. Los paquetes se conservan en el servidor durante 24 horas y luego se eliminan automáticamente.",
      "backToBackups": "← Backups",
      "sections": {
        "export": "Exportar",
        "import": "Importar"
      },
      "form": {
        "scope": "Alcance",
        "memories": "Memorias",
        "memoriesHint": "Filas de memoria persistente entre CLI (texto + alcance + metadatos). Los vectores de embedding se omiten; el importador vuelve a generarlos.",
        "integrations": "Integraciones",
        "customTasks": "Tareas personalizadas",
        "customTasksHint": "Tareas definidas por el operador que se muestran en la pestaña Tareas del Inspector.",
        "integrationOptions": {
          "none": "Ninguna",
          "noneHint": "Omitir por completo la tabla de integraciones.",
          "metadata": "Solo metadatos (recomendado)",
          "metadataHint": "ID, nombre, prefijo de ruta, alcances. Sin material de API key.",
          "plaintext": "Incluir las API keys en texto plano",
          "plaintextHint": "v1 solo con bcrypt: no existe texto plano recuperable. El manifiesto lo documenta; no se filtra nada."
        },
        "confirmWarning": "Escribe <1>Lo entiendo</1> para confirmar. opendray actualmente almacena solo hashes bcrypt, así que seleccionar texto plano NO exporta ningún texto plano (la función está reservada para una versión futura que mantenga cachés de texto plano).",
        "confirmPlaceholder": "Lo entiendo",
        "confirmSentinel": "lo entiendo",
        "footnote": "Los logs de auditoría y los transcripts de session quedan fuera del alcance; en su lugar los cubre /backups (volcado del operador).",
        "building": "Generando…",
        "create": "Crear exportación",
        "readyToast": "Exportación lista",
        "readyDescription": "{bytes} bytes",
        "failedToast": "Falló la exportación"
      },
      "history": {
        "loading": "Cargando…",
        "empty": "Aún no hay exportaciones. Usa el formulario de arriba para crear una.",
        "title": "Historial",
        "columns": {
          "id": "ID",
          "status": "Estado",
          "scope": "Alcance",
          "size": "Tamaño",
          "expires": "Caduca",
          "actions": "Acciones"
        },
        "download": "Descargar",
        "deleteTooltip": "Eliminar",
        "listFailedToast": "No se pudieron listar las exportaciones",
        "downloadFailedToast": "Falló la descarga",
        "noTokenToast": "Sin token de descarga (¿caducado?)",
        "deleteConfirm": "¿Eliminar la exportación {id}?",
        "deletedToast": "Exportación eliminada",
        "deleteFailedToast": "Falló la eliminación",
        "scopeEmpty": "(vacío)"
      },
      "import": {
        "intro": "Reproduce un paquete de exportación (zip) en la base de datos en vivo. Los conflictos (id coincidente, o route_prefix único para integraciones) se <1>omiten</1> de forma predeterminada. Las memorias se etiquetan con <3>embedder=imported_v1</3> y necesitan una pasada de re-embedding antes de que la búsqueda las devuelva; activa el re-embedding en <5>Memory → Maintenance</5>. Las integraciones se importan con <7>enabled=false</7> y una clave de marcador de posición sin bcrypt; el operador debe rotarla antes de usarla.",
        "memoryLink": "Memory → Maintenance",
        "bundleLabel": "Paquete (.zip)",
        "memoriesLabel": "Memorias",
        "integrationsLabel": "Integraciones (solo metadatos, las claves nunca se importan)",
        "customTasksLabel": "Tareas personalizadas",
        "importing": "Importando…",
        "importBundle": "Importar paquete",
        "pickFileToast": "Selecciona primero un archivo de paquete",
        "doneToast": "Importación completada",
        "finishedWithErrors": "La importación terminó con errores",
        "failedToast": "Falló la importación",
        "summaryCard": {
          "memories": "Memorias",
          "integrations": "Integraciones",
          "customTasks": "Tareas personalizadas",
          "created": "creadas",
          "skipped": "omitidas",
          "failed": "fallidas"
        }
      },
      "imports": {
        "loading": "Cargando…",
        "empty": "Aún no hay importaciones.",
        "title": "Historial",
        "columns": {
          "id": "ID",
          "status": "Estado",
          "source": "Origen",
          "counts": "Recuentos",
          "when": "Cuándo"
        },
        "noneCounts": "(ninguno)",
        "listFailedToast": "No se pudieron listar las importaciones"
      }
    }
  },
  "more": {
    "title": "Más",
    "identity": {
      "signedInAs": "Sesión iniciada como",
      "server": "Servidor",
      "tokenExpires": "El token caduca"
    },
    "sections": {
      "gateway": "Gateway",
      "memory": "Memoria",
      "system": "Sistema"
    },
    "items": {
      "integrations": {
        "title": "Integraciones",
        "subtitle": "Llamadores de la API: actividad reciente y tasas de error"
      },
      "channels": {
        "title": "Canales",
        "subtitle": "Destinos de notificaciones"
      },
      "providers": {
        "title": "Proveedores",
        "subtitle": "Estado de los CLI de Claude / Codex / Gemini"
      },
      "mcp": {
        "title": "MCP",
        "subtitle": "Servidores y secretos de Model Context Protocol"
      },
      "skills": {
        "title": "Skills",
        "subtitle": "Biblioteca de SKILL.md del agente (integrados + vault)"
      },
      "gitHosts": {
        "title": "Hosts de Git",
        "subtitle": "Credenciales PAT para GitHub / GitLab / etc."
      },
      "customTasks": {
        "title": "Tareas personalizadas",
        "subtitle": "Comandos slash que se muestran en el selector de tareas de la session"
      },
      "projectMemory": {
        "title": "Objetivo / plan / diario del proyecto",
        "subtitle": "Capas de memoria 2-4 por cwd + propuestas del agente"
      },
      "archived": {
        "title": "Memorias archivadas",
        "subtitle": "Restaura memorias que el limpiador automático archivó (gracia de 30 días)"
      },
      "backups": {
        "title": "Copias de seguridad",
        "subtitle": "Estado de la última copia de seguridad y ejecución inmediata"
      },
      "dataExport": {
        "title": "Exportación e importación de datos",
        "subtitle": "Paquetes de datos a nivel de usuario (memorias / integraciones / tareas personalizadas)"
      },
      "settings": {
        "title": "Ajustes",
        "subtitle": "Idioma, apariencia, cuenta"
      },
      "about": {
        "title": "Acerca de",
        "subtitle": "Versión de compilación e información del servidor"
      }
    },
    "signOut": "Cerrar sesión"
  },
  "sessions": {
    "title": "Sesiones",
    "refresh": "Actualizar",
    "actions": "Acciones",
    "spawn": "Crear",
    "filters": {
      "all": "Todas",
      "running": "En ejecución",
      "idle": "Inactivas",
      "ended": "Finalizadas"
    },
    "card": {
      "startedRelative": "{provider} · iniciada {when}"
    },
    "empty": {
      "titleAll": "Aún no hay sesiones",
      "titleFiltered": "Ninguna sesión coincide con el filtro \"{filter}\".",
      "subtitleAll": "Toca el botón Crear para crear una.",
      "subtitleFiltered": "Prueba con otro filtro o desliza para actualizar."
    },
    "errorTitle": "No se pudieron cargar las sesiones",
    "relative": {
      "secondsAgo": "hace {n}s",
      "minutesAgo": "hace {n}m",
      "hoursAgo": "hace {n}h",
      "daysAgo": "hace {n}d"
    },
    "detail": {
      "fallbackTitle": "Sesión",
      "refreshMetadata": "Actualizar metadatos",
      "inspector": "Inspector (Archivos / Git / Tareas / Historial / Notas)",
      "projectMemory": "Memoria del proyecto (objetivo / plan / diario / bandeja de entrada)",
      "actions": "Acciones",
      "started": "iniciada {when}",
      "startedEnded": "iniciada {started}  ·  finalizada {ended}",
      "idPrefix": "id: {id}",
      "errorTitle": "No se pudo cargar la sesión",
      "accountSwitcher": {
        "tooltip": "Cambiar de cuenta de Claude",
        "sheetTitle": "Cambiar de cuenta de Claude",
        "current": "Actual: {account}",
        "defaultName": "Predeterminada (credencial del sistema)",
        "defaultSubtitle": "Usa el propio inicio de sesión del CLI, sin cuenta específica",
        "defaultShort": "predeterminada",
        "tokenEmpty": "sin token",
        "confirmTitle": "¿Cambiar de cuenta?",
        "confirmBody": "Esto reinicia el CLI con la nueva cuenta — se pierde el contexto de conversación actual dentro del CLI (la pestaña de la sesión se conserva).",
        "confirmAction": "Cambiar",
        "cancel": "Cancelar",
        "switchedSnack": "Cambiado a {account}",
        "switchFailed": "Cambio fallido: {error}",
        "noneHint": "No hay cuentas de Claude configuradas. Agrégalas en Más → Providers → Claude."
      }
    },
    "terminal": {
      "snackbar": {
        "imagePickerFailed": "Falló el selector de imágenes: {error}",
        "uploadingImage": "Subiendo imagen…",
        "imageAttached": "Imagen adjuntada: {path}",
        "uploadFailed": "Falló la subida ({status}): {message}",
        "uploadFailedGeneric": "Falló la subida: {error}"
      },
      "imageSource": {
        "photoLibrary": "Biblioteca de fotos",
        "takePhoto": "Tomar una foto"
      },
      "keyboard": {
        "copyBuffer": "Copiar búfer",
        "paste": "Pegar",
        "attachImage": "Adjuntar imagen",
        "enter": "Intro"
      },
      "connection": {
        "connecting": "Conectando…",
        "connected": "Conectado",
        "reconnecting": "Reconectando…",
        "reconnectingWithError": "Reconectando ({error})…",
        "disconnected": "Desconectado",
        "disconnectedWithError": "Desconectado ({error})",
        "ended": "Sesión finalizada"
      }
    },
    "action": {
      "stop": "Detener",
      "stopping": "Deteniendo…",
      "stopDescription": "Envía SIGTERM, conserva el historial",
      "restart": "Reiniciar",
      "restarting": "Reiniciando…",
      "restartDescription": "Vuelve a crear el proceso del CLI",
      "delete": "Eliminar",
      "deleteDescription": "Elimina la session y su historial",
      "deleteConfirm": "¿Eliminar esta session de forma permanente? Su ring buffer y su historial desaparecerán.",
      "errors": {
        "stop": "Falló al detener: {error}",
        "start": "Falló al reiniciar: {error}",
        "delete": "Falló al eliminar: {error}"
      }
    },
    "dirPicker": {
      "parent": "Superior",
      "newFolder": "Nueva carpeta",
      "useThisFolder": "Usar esta carpeta",
      "loading": "Cargando…",
      "empty": "No hay subcarpetas aquí.\nElige esta carpeta o crea una nueva.",
      "createdSnack": "Creada {path}",
      "mkdirFailedSnack": "Falló mkdir: {error}",
      "dialog": {
        "title": "Nueva carpeta",
        "hint": "Nombre de la carpeta",
        "create": "Crear"
      }
    },
    "inspector": {
      "shell": {
        "title": "Inspector",
        "loadError": "No se pudo cargar la sesión: {error}",
        "tabs": {
          "files": "Archivos",
          "git": "Git",
          "tasks": "Tareas",
          "history": "Historial",
          "notes": "Notas"
        }
      },
      "shared": {
        "refresh": "Actualizar",
        "inserted": "Insertado: {text}",
        "insertFailedApi": "Falló la inserción ({status}): {message}",
        "insertFailedGeneric": "Falló la inserción: {error}",
        "insertFailedShort": "Falló la inserción: {error}"
      },
      "history": {
        "insertIntoTerminal": "Insertar en el terminal",
        "searchHint": "Buscar prompts…"
      },
      "files": {
        "insertAtRef": "Insertar como @referencia",
        "insertPath": "Insertar ruta",
        "insertPathSubtitle": "Pega la ruta absoluta tal cual",
        "readContent": "Leer contenido",
        "readContentSubtitle": "Hasta 256 KiB de texto plano",
        "readFailedApi": "Falló la lectura ({status}): {message}",
        "readFailedGeneric": "Falló la lectura: {error}",
        "parent": "Superior",
        "backToCwd": "Volver al cwd de la session"
      },
      "git": {
        "insertAtRef": "Insertar como @referencia",
        "insertPath": "Insertar ruta",
        "showDiff": "Mostrar diff",
        "diffFailedApi": "Falló el diff ({status}): {message}",
        "diffFailedGeneric": "Falló el diff: {error}",
        "insertHash": "Insertar hash",
        "showFullPatch": "Mostrar el parche completo",
        "showFailedApi": "Falló al mostrar ({status}): {message}",
        "showFailedGeneric": "Falló al mostrar: {error}",
        "tabStatus": "Estado",
        "tabLog": "Log"
      },
      "tasks": {
        "runCommand": "Ejecutar comando",
        "runCommandSubtitle": "Se ejecuta en una nueva session de shell y cambia a ella",
        "filterHint": "Filtrar tareas…",
        "noMatch": "Ninguna tarea coincide con \"{query}\"",
        "emptyTitle": "No hay tareas en esta carpeta",
        "emptyHint": "Buscando package.json, Makefile, Taskfile, justfile, Cargo.toml, go.mod, pyproject.toml o scripts de shell"
      },
      "notes": {
        "insertedAt": "Insertado: @{path}",
        "myNotes": "Mis notas",
        "projectDocs": "Documentos del proyecto",
        "insertAtRefTooltip": "Insertar como @referencia",
        "insertAtRefShort": "Insertar @referencia",
        "draftHint": "# {project}\n\nIdeas, tareas pendientes, contexto para el agente…",
        "createFailed": "Falló al crear: {error}",
        "saveFailed": "Falló al guardar: {error}",
        "changeLocationTooltip": "Cambiar la ubicación de los documentos del proyecto",
        "filenameHint": "nombre de archivo (p. ej. spec o design.md)",
        "create": "Crear",
        "filterHint": "Filtrar…",
        "locationDialogTitle": "Ubicación de los documentos del proyecto",
        "loadFailedApi": "Falló la carga: {error}",
        "loadFailedGeneric": "Falló la carga: {error}",
        "saveFailedApi": "Falló al guardar: {error}",
        "saveFailedGeneric": "Falló al guardar: {error}",
        "insertFailedApi": "Falló la inserción: {error}",
        "insertFailedGeneric": "Falló la inserción: {error}",
        "createFailedApi": "Falló al crear: {error}",
        "createFailedGeneric": "Falló al crear: {error}",
        "personalHint": "Bloc de notas personal. Se guarda automáticamente mientras escribes. Los agentes de IA no escriben aquí.",
        "projectDocsHint": "Arquitectura / spec / decisiones / plan / retrospectivas. Normalmente redactados o mantenidos por un agente.",
        "mappingCleared": "Asignación borrada. Usando el valor predeterminado",
        "mappedTo": "Asignado a {path}",
        "cancelTooltip": "Cancelar",
        "newDocTooltip": "Nuevo documento",
        "noProjectMapping": "No se pudo resolver una asignación de proyecto para esta session. Comprueba que el gateway tenga configurado un almacén de notas y que el cwd de la session esté establecido.",
        "emptyProjectDocs": "Aún no hay documentos del proyecto. Toca + para crear uno o deja que un agente de IA lo genere a partir de un prompt.",
        "emptyFilterMatch": "No hay coincidencias para \"{query}\".",
        "locationDialogHelp": "Fija el cwd de esta session a una carpeta específica dentro de tu almacén de notas. Déjalo en blanco para restablecer.",
        "sessionCwd": "cwd de la session",
        "projectDocsPath": "Ruta de los documentos del proyecto relativa al almacén",
        "locationStoredHint": "Almacenado en <vault>/.opendray-projects.json. Se sincroniza con git junto con el resto del almacén.",
        "pinnedHint": "Fijado a {path}/ (anula {defaultPath}). Los agentes de IA también redactan documentos aquí.",
        "noProjectMapping2": "(sin asignación de proyecto)",
        "clearOverride": "Borrar anulación",
        "save": "Guardar"
      }
    },
    "spawnSheet": {
      "title": "Nueva session",
      "errorRequired": "El proveedor y el directorio de trabajo son obligatorios",
      "errorGeneric": "No se pudo crear la session: {error}",
      "cancel": "Cancelar",
      "spawn": "Crear",
      "providerLabel": "Proveedor",
      "disabledSuffix": " (desactivado)",
      "cwdLabel": "Directorio de trabajo",
      "cwdHint": "/Users/you/projects/foo",
      "cwdHelper": "Ruta absoluta en el host del gateway.",
      "browse": "Examinar",
      "nameLabel": "Nombre (opcional)",
      "nameHint": "p. ej. backend-refactor",
      "argsLabel": "Argumentos adicionales (opcional)",
      "argsHint": "--continue --verbose",
      "argsHelper": "Separados por espacios; en blanco usa los valores predeterminados del proveedor.",
      "bypass": {
        "labelClaude": "Omitir permisos",
        "labelCodex": "Omitir aprobaciones y sandbox",
        "labelGemini": "Modo YOLO",
        "subtitleOn": "Esta session se ejecutará con autonomía elevada.",
        "subtitleOff": "Desactivado. Las confirmaciones y los prompts se comportan de forma normal."
      },
      "noProviders": {
        "title": "No hay proveedores configurados",
        "message": "El gateway no tiene proveedores de CLI habilitados. Configura uno en Proveedores (admin web) o en [providers] en config.toml, y luego toca Recargar.",
        "reload": "Recargar"
      },
      "providerLoadError": {
        "title": "No se pudieron cargar los proveedores",
        "networkError": "Error de red",
        "serverPrefix": "Servidor {code}",
        "format": "{prefix}: {message}"
      },
      "claudeAccount": {
        "label": "Cuenta de Claude",
        "helperMulti": "Hay varias cuentas configuradas. Elige una para esta session.",
        "helperSingle": "Elige una cuenta configurada o usa la predeterminada (env / sistema).",
        "default": "Predeterminada (env / sistema)",
        "disabledSuffix": " (desactivada)",
        "noTokenSuffix": " (sin token)",
        "noneHint": "No hay cuentas de Claude configuradas. El gateway usará la ANTHROPIC_API_KEY del sistema. Añade cuentas en Ajustes → Cuentas en el admin web.",
        "errorHint": "No se pudieron cargar las cuentas de Claude ({error}). La session se creará con el valor predeterminado del gateway."
      }
    }
  },
  "mcp": {
    "title": "MCP",
    "newServer": "Nuevo servidor",
    "addSecret": "Añadir secreto",
    "editConfig": "Editar configuración",
    "viewRawConfig": "Ver configuración en bruto",
    "copyId": "Copiar id",
    "copiedSnack": "Copiado {id}",
    "deleteServerTitle": "¿Eliminar servidor MCP?",
    "deleteSecretTitle": "¿Eliminar secreto?",
    "errorPrefix": {
      "delete": "Error al eliminar",
      "add": "Error al añadir",
      "update": "Error al actualizar",
      "toggle": "Error al alternar"
    },
    "errorWithMessage": "{prefix}: {error}",
    "editor": {
      "nameHint": "my-mcp-server",
      "jsonHint": "Configuración JSON, nombre, transport: stdio, command, args…",
      "descriptionPlaceholder": "Descripción opcional de una línea",
      "validateJsonObject": "El cuerpo debe ser un objeto JSON",
      "validateJsonInvalid": "JSON no válido: {error}",
      "appBarEdit": "Editar servidor MCP",
      "appBarNew": "Nuevo servidor MCP",
      "idLockedHint": "Bloqueado en modo edición, elimínalo y vuelve a crearlo para cambiarlo.",
      "jsonLabel": "JSON del servidor",
      "jsonSchemaHelp": "Esquema: transport debe ser stdio, http o sse. Para stdio define command + args. Para http/sse define url + headers. Usa \\$secret:KEY para referenciar secretos del vault.",
      "idLabel": "id (segmento de URL, alfanumérico en minúsculas / guion / guion bajo)",
      "idRequired": "el id es obligatorio",
      "saving": "Guardando…",
      "save": "Guardar",
      "create": "Crear"
    },
    "secret": {
      "keyLabel": "Clave",
      "keyHint": "GITHUB_TOKEN, OPENAI_KEY, …",
      "valueLabel": "Valor",
      "keyRequired": "La clave es obligatoria.",
      "keyInvalid": "La clave debe coincidir con [A-Za-z_][A-Za-z0-9_]*, las mismas reglas que una variable de entorno de shell.",
      "valueRequired": "El valor es obligatorio.",
      "replaceTitle": "Reemplazar valor del secreto",
      "addTitle": "Añadir secreto",
      "saveButton": "Guardar",
      "addButton": "Añadir",
      "helpRules": "Reglas de variable de entorno de shell: empieza por una letra o _, después solo letras / dígitos / _.",
      "replaceHint": "Pega el nuevo valor (el anterior se borra)",
      "addHint": "Pega el valor del secreto",
      "addedSnack": "Secreto {key} añadido.",
      "updatedSnack": "Secreto {key} actualizado.",
      "deletedSnack": "Eliminado {key}.",
      "deleteBody": "Elimina el valor del vault cifrado. Cualquier servidor MCP que lo referencie fallará hasta que se restaure."
    },
    "popup": {
      "editConfigSubtitle": "Editor JSON completo, solo servidores respaldados por el vault",
      "viewRawSubtitle": "Inspector de solo lectura para el JSON del servidor",
      "deleteLabel": "Eliminar"
    },
    "kv": {
      "transport": "Transport",
      "description": "Descripción",
      "command": "Command",
      "args": "Args",
      "headers": "Headers"
    },
    "deleteServerBody": "Elimina el directorio del vault para {id}. Las sesiones que referencian este servidor dejan de poder iniciarlo.",
    "deleteServerSnack": "Eliminado {id}.",
    "serversCount": "Servidores ({count})",
    "secretsCount": "Secretos ({count})",
    "emptyServers": "No hay servidores MCP registrados. Toca \"Nuevo servidor\" para añadir uno.",
    "emptySecrets": "No hay secretos almacenados. Añade uno para pasar variables de entorno / headers sensibles a los servidores MCP sin ponerlos en el JSON.",
    "noVaultFileYet": "Aún no hay archivo de vault, los secretos añadidos lo crean.",
    "tapToReplaceHint": "Toca para reemplazar · mantén pulsado / papelera para eliminar",
    "failedToLoad": "No se pudo cargar el estado de MCP",
    "serverCreatedSnack": "Servidor MCP creado.",
    "serverUpdatedSnack": "Servidor MCP actualizado.",
    "envHeading": "Env",
    "encryptionAes": "Cifrado AES-GCM (clave en el keychain del SO)",
    "encryptionPlaintext": "PLAINTEXT, keychain no disponible",
    "toggleEnabledSnack": "{name} activado.",
    "toggleDisabledSnack": "{name} desactivado."
  },
  "providers": {
    "title": "Proveedores",
    "configSaved": "Configuración del proveedor actualizada.",
    "saveFailedApi": "Error al guardar: {error}",
    "saveFailedGeneric": "Error al guardar: {error}",
    "reload": "Recargar",
    "errorPrefix": {
      "toggle": "Error al alternar",
      "rename": "Error al renombrar",
      "delete": "Error al eliminar"
    },
    "errorWithMessage": "{prefix}: {error}",
    "updateCheck": {
      "sectionTitle": "Versión del CLI",
      "checking": "Buscando actualizaciones…",
      "checkFailed": "No se pudo buscar actualizaciones",
      "notInstalled": "No instalado en el host del gateway",
      "installed": "Instalado: {version}",
      "upToDate": "Actualizado",
      "updateAvailable": "Actualización disponible: {version}",
      "latest": "última {version}",
      "updateButton": "Actualizar CLI",
      "updating": "Actualizando…",
      "updatedSnack": "Actualizado a {version}.",
      "noChangeSnack": "Ya está en la última versión.",
      "updateFailed": "Actualización fallida: {error}",
      "notAvailableHere": "La actualización en la app no está disponible en este host: {reason}",
      "activeSessionsWarning": "{n} sesión(es) activa(s) usan este CLI — actualizar no las interrumpe, pero mantienen la versión anterior hasta reiniciarse."
    },
    "accounts": {
      "rename": "Renombrar",
      "renameTitle": "Renombrar {name}",
      "displayNameLabel": "Nombre visible",
      "displayNameHint": "Cuenta de trabajo",
      "deleteTitle": "¿Eliminar la cuenta?",
      "importFailedApi": "Error al importar: {error}",
      "importFailedGeneric": "Error al importar: {error}",
      "enable": "Activar",
      "disable": "Desactivar",
      "deleteLabel": "Eliminar",
      "deleteBody": "Elimina la cuenta y su token OAuth almacenado. Las sessions que ya usan esta cuenta siguen funcionando, pero la reautenticación fallará.",
      "deletedSnack": "{name} eliminada.",
      "importSyncedSnack": "Ya está sincronizado, el gateway no tiene cuentas nuevas.",
      "importedSnackOne": "Se importó {n} cuenta.",
      "importedSnackOther": "Se importaron {n} cuentas.",
      "importing": "Sincronizando…",
      "importLocal": "Importar local",
      "addHint": "Añadir una cuenta nueva solo se puede hacer en el host del gateway.",
      "addBody": "El nuevo directorio aparece aquí automáticamente. Consulta la documentación para los pasos del flujo OAuth.",
      "loadFailed": "Error al cargar las cuentas: {error}",
      "intro": "Las sessions creadas con el proveedor Claude eligen entre estas cuentas (o recurren a las variables de entorno).",
      "enabledSnack": "{name} activada.",
      "disabledSnack": "{name} desactivada.",
      "renamedSnack": "Renombrada a {name}.",
      "activeSessions": "{count} activas",
      "usedAgo": "usada {when}",
      "identityChanged": "La identidad cambió",
      "identityWas": "era {email}",
      "acceptIdentity": "Aceptar",
      "identityAcceptedSnack": "Cambio de identidad aceptado",
      "identityAcceptFailed": "Error al aceptar"
    },
    "configFallbackTitle": "Configuración del proveedor",
    "saving": "Guardando…",
    "save": "Guardar",
    "configLoadFailed": "Error al cargar el proveedor",
    "argsHelper": "Argumentos de CLI separados por espacios.",
    "listEmptyHeadline": "No hay proveedores cargados.",
    "listEmptyBody": "El gateway resuelve los proveedores desde su directorio de plugins al arrancar. Revisa los logs si esperabas alguno.",
    "listLoadFailed": "Error al cargar los proveedores",
    "cliSectionHeader": "Proveedores de CLI",
    "enabledSnack": "{name} activada.",
    "disabledSnack": "{name} desactivada."
  },
  "integrations": {
    "title": "Integraciones",
    "register": "Registrar",
    "registerDialogTitle": "Registrar integración",
    "edit": "Editar",
    "editTitle": "Editar {name}",
    "enabledLabel": "Habilitada",
    "iSavedIt": "Ya la he guardado",
    "apiKeyForName": "API key de {name}",
    "apiKeySubtitleRegister": "Entrégasela a la integración para que pueda autenticarse contra /api/v1/{routePrefix}/…",
    "copiedRequestId": "request_id {id} copiado",
    "updateOk": "Integración actualizada.",
    "registerFailedApi": "Error al registrar: {error}",
    "registerFailedGeneric": "Error al registrar: {error}",
    "updateFailedApi": "Error al actualizar: {error}",
    "updateFailedGeneric": "Error al actualizar: {error}",
    "deleteTitle": "¿Eliminar integración?",
    "deletedSnack": "{name} eliminada.",
    "deleteFailedApi": "Error al eliminar: {error}",
    "deleteFailedGeneric": "Error al eliminar: {error}",
    "rotateKey": "Rotar key",
    "rotateConfirmTitle": "¿Rotar la API key?",
    "rotate": "Rotar",
    "newApiKeyTitle": "Nueva API key de {name}",
    "newApiKeySubtitle": "Entrégasela a la integración. La key anterior acaba de quedar invalidada.",
    "rotateFailedApi": "Error al rotar: {error}",
    "rotateFailedGeneric": "Error al rotar: {error}",
    "deleteBody": "Elimina el registro y revoca la API key. Las solicitudes en curso que usen la key antigua empezarán a fallar.",
    "rotateBody": "Genera una nueva API key para {name} e invalida la antigua de inmediato.",
    "appBarFallback": "Integración",
    "tooltipMore": "Más",
    "tooltipReadOnly": "Integración del sistema (solo lectura)",
    "kvRoutePrefix": "Prefijo de ruta",
    "kvBaseUrl": "URL base",
    "kvScopes": "Ámbitos",
    "kvVersion": "Versión",
    "kvLastHealthPing": "Último ping de estado",
    "kvCreated": "Creada",
    "kvKeyRotated": "Key rotada",
    "detailLoadFailed": "Error al cargar la integración: {error}",
    "callsLoadFailed": "Error al cargar las llamadas",
    "noMatchingCalls": "Aún no hay llamadas coincidentes en el registro.",
    "directionAll": "Todas",
    "directionInbound": "Entrantes",
    "directionOutbound": "Salientes",
    "form": {
      "validateRequired": "El nombre, la URL base y el prefijo de ruta son obligatorios.",
      "fieldName": "Nombre",
      "fieldNameHint": "Mi Bot",
      "fieldBaseUrl": "URL base",
      "fieldRoutePrefix": "Prefijo de ruta",
      "routePrefixHelper": "Accesible como /api/v1/<prefix>/...",
      "fieldScopes": "Ámbitos (opcional)",
      "scopesHelper": "Separados por comas. Vacío = valores por defecto del servidor.",
      "fieldVersion": "Versión (opcional)",
      "validateBaseUrl": "La URL base es obligatoria.",
      "editFieldScopes": "Ámbitos",
      "editScopesHelper": "Separados por comas.",
      "editFieldVersion": "Versión",
      "apiKeyWarn": "No volverás a ver esta key.",
      "copyCopied": "Copiado",
      "copyCopy": "Copiar"
    },
    "emptyState": "Regístrala desde el admin web: Integraciones → Nueva.",
    "sectionRegistered": "Registradas",
    "sectionSystem": "Sistema",
    "listLoadFailed": "Error al cargar las integraciones"
  },
  "memoryWorkers": {
    "title": "Workers de memoria",
    "savedSnack": "{label} guardado",
    "saveFailed": "Error al guardar: {error}",
    "testFailed": "La llamada de prueba falló: {error}",
    "workerLabel": "Worker",
    "summarizerHttp": "Resumidor (HTTP)",
    "agentCliPrint": "Agente (CLI --print)",
    "cliLabel": "CLI",
    "cliClaude": "Claude",
    "cliGemini": "Gemini",
    "claudeAccountLabel": "Cuenta de Claude",
    "claudeAccountDefault": "Predeterminada",
    "test": "Probar",
    "intro": "Cada punto de contacto con el LLM del sistema de memoria puede atenderse de forma independiente mediante el endpoint del resumidor local (LM Studio / compatible con OpenAI) o lanzando un agente headless de Claude / Gemini en modo --print. Las tareas narrativas de alta calidad (gitactivity, transcript) se benefician de los workers de agente; las tareas de alta frecuencia (gatekeeper) permanecen en el endpoint local por diseño.",
    "errorTitle": "Endpoint no accesible",
    "errorDetail": "Las rutas /api/v1/memory/workers son nuevas en M25. Puede que el binario de opendray necesite un reinicio para montarlas y ejecutar la migración 0029.",
    "summarizerOnlyBadge": "solo resumidor",
    "summarizerProviderLabel": "Proveedor de resumidor",
    "registryDefault": "Predeterminado del registro",
    "agentWarning": "El modo agente lanza un CLI headless por cada llamada. Latencia ~5-15s (frente a ~1s del resumidor); el coste pasa de la CPU a tu quota de Claude/Gemini.",
    "noCalls24h": "Sin llamadas en las últimas 24h.",
    "testOkSnack": "{label} OK, {duration}ms",
    "testFailedReturnedSnack": "{label} falló: {error}",
    "unknownError": "desconocido",
    "tasks": {
      "gatekeeper": {
        "label": "Gatekeeper",
        "description": "Filtro previo a la escritura en cada memory_store. Alta frecuencia (objetivo <500ms): solo resumidor."
      },
      "cleaner": {
        "label": "Bibliotecario de limpieza",
        "description": "Bibliotecario LLM periódico. Juzga las memorias antiguas como conservar / obsoleta / duplicada."
      },
      "gitactivity": {
        "label": "Resumidor de actividad de git",
        "description": "git log a narrativa de 2-3 párrafos cada 24h. Encaja de forma natural con un worker de agente."
      },
      "transcript": {
        "label": "Resumidor de transcript de sesión",
        "description": "Resumen al final de la sesión de 'qué hizo el agente'. Encaja de forma natural con un worker de agente."
      },
      "planDrift": {
        "label": "Detector de deriva del plan",
        "description": "Después de que termina cada sesión, comprueba si el plan del proyecto necesita actualizarse y presenta una propuesta. Encaja con un worker de agente para un razonamiento más rico."
      },
      "conflictDetector": {
        "label": "Detector de conflictos entre capas",
        "description": "Escaneo diario que encuentra contradicciones entre hechos / plan / objetivo / journal. Un modelo de mayor calidad implica menos falsos positivos."
      },
      "capture": {
        "label": "Motor de captura",
        "description": "Extracción de hechos por cada trigger desde las transcripciones de sesión. El modo agente da hechos notablemente mejores en sesiones largas; el modo resumidor es barato y local."
      }
    }
  },
  "memoryArchived": {
    "title": "Memorias archivadas",
    "loadFailed": "Error al cargar: {error}",
    "restoreFailed": "Error al restaurar: {error}",
    "emptyTitle": "Nada archivado",
    "emptyBody": "No hay memorias archivadas en ningún proyecto. El limpiador automático archiva aquí los hechos obsoletos y duplicados (restaurables durante 30 días); todavía no se ha eliminado nada.",
    "globalScope": "(global)",
    "countBadge": "{count} archivadas",
    "restore": "Restaurar"
  },
  "project": {
    "title": "Proyecto",
    "pickFirst": "Elige primero un proyecto.",
    "health": {
      "title": "Salud de la memoria, últimos {days} días",
      "subtitle": "Señales agregadas de ambos subsistemas de memoria para este proyecto.",
      "newFacts": "Hechos nuevos",
      "newFactsHint": "{total} almacenados en total",
      "captureFires": "Capturas disparadas",
      "captureFiresHint": "{stored} almacenados · {deduped} deduplicados",
      "newJournal": "Entradas de diario",
      "newJournalHint": "{total} en total",
      "planAge": "Última actualización del plan",
      "planAgeHint": "{count} propuesta(s) de desvío del plan pendiente(s)",
      "planAgeHintNone": "No hay propuestas de desvío del plan pendientes",
      "goalAge": "Última actualización del objetivo",
      "pending": "Propuestas pendientes",
      "pendingHint": "la más antigua tiene {days}d",
      "topHit": "Más consultado · {hits} recuperaciones",
      "zeroHit": "{count} hechos con más de 7d y cero recuperaciones, candidatos para limpieza.",
      "never": "nunca",
      "today": "hoy",
      "daysAgo": "hace {count}d"
    },
    "conflicts": {
      "subtitle": "Contradicciones que el detector diario encontró entre hechos, plan, objetivo y entradas de diario.",
      "empty": "No hay conflictos pendientes. Pulsa Detectar ahora para un barrido bajo demanda.",
      "detectNow": "Detectar ahora",
      "detected": "{count} conflicto(s) nuevo(s) encontrado(s)",
      "accept": "Aceptar",
      "dismiss": "Descartar",
      "deleteFact": "Eliminar hecho {side}",
      "deleteConfirmTitle": "¿Eliminar hecho {side}?",
      "deleteConfirmBody": "Esto elimina el hecho de forma permanente y acepta el conflicto. El otro lado permanece como la afirmación superviviente.",
      "deleteWillDelete": "Se eliminará (lado {side}):",
      "deleteWillKeep": "Se conservará (lado {side}):",
      "deleteNonFactOther": "(entrada de {layer}, abre la pestaña correspondiente para inspeccionar)",
      "deleteLoading": "Cargando el texto del hecho…",
      "deleteFactLabel": "Eliminar {side}",
      "deletedFact": "Hecho eliminado y conflicto aceptado",
      "openPlanEditor": "Abrir el editor del plan",
      "openGoalEditor": "Abrir el editor del objetivo",
      "severity": {
        "low": "baja",
        "medium": "media",
        "high": "alta"
      }
    },
    "journalPrune": {
      "title": "Purgar entradas de diario obsoletas",
      "subtitle": "Con más de {days} días, sin conflictos pendientes.",
      "daysLabel": "Con más de (días):",
      "empty": "No hay nada obsoleto que purgar.",
      "selectAll": "Seleccionar todo",
      "deselectAll": "Deseleccionar todo",
      "deleteSelected": "Eliminar ({count})",
      "deleted": "{count} entrada(s) eliminada(s)"
    },
    "loadFailed": "Error al cargar: {error}",
    "projectsLoadFailed": "Error al cargar los proyectos: {error}",
    "projectLabel": "Proyecto",
    "resetTooltip": "Restablecer la memoria del proyecto",
    "append": "Añadir",
    "appendDialogTitle": "Añadir entrada de diario",
    "titleFieldLabel": "Título (opcional)",
    "contentFieldLabel": "Contenido (markdown)",
    "appendFailed": "Error: {error}",
    "approveFailed": "Error al aprobar: {error}",
    "rejectFailed": "Error al rechazar: {error}",
    "resetConfirmTitle": "¿Restablecer la memoria del proyecto?",
    "alsoDeleteScanner": "Eliminar también los documentos del scanner",
    "alsoDeletePgvector": "Eliminar también las memorias de pgvector",
    "deleteForever": "Eliminar para siempre",
    "resetDoneSnack": "Restablecido: {parts}",
    "resetFailed": "Error al restablecer: {error}",
    "docSavedSnack": "{kind} guardado",
    "docSaveFailed": "Error al guardar: {error}",
    "docHintTemplate": "Escribe el {kind} como markdown…",
    "deleteEntryTooltip": "Eliminar entrada",
    "agentReason": "Motivo del agente",
    "reject": "Rechazar",
    "approve": "Aprobar",
    "replaceConfirmTitle": "¿Reemplazar el {kind} actual?",
    "replaceKind": "Reemplazar {kind}",
    "archived": {
      "emptyTitle": "Nada archivado",
      "emptyBody": "No hay memorias archivadas para este proyecto. El limpiador automático archiva aquí los hechos obsoletos y duplicados automáticamente; todavía ninguno.",
      "restoreFailed": "Error al restaurar: {error}",
      "restore": "Restaurar"
    }
  },
  "backups": {
    "title": "Copias de seguridad",
    "runConfirmTitle": "¿Ejecutar copia de seguridad ahora?",
    "runConfirmBody": "Lanza un nuevo volcado contra el destino local. El trabajo se ejecuta en el servidor; esta lista se actualizará a medida que avance.",
    "run": "Ejecutar",
    "runNow": "Ejecutar ahora",
    "queueing": "Encolando…",
    "queuedSnack": "Copia de seguridad encolada ({id}). Esperando el progreso…",
    "runFailedApi": "Error al ejecutar: {error}",
    "runFailedGeneric": "Error al ejecutar: {error}",
    "rowSucceededSnack": "Copia de seguridad completada ({bytes}).",
    "rowFailedSnack": "Error en la copia de seguridad: {error}",
    "unknownError": "error desconocido",
    "detailTitle": "Detalle de la copia de seguridad",
    "deleteTitle": "¿Eliminar copia de seguridad?",
    "deleteBody": "Elimina el blob de {target} y marca la fila como eliminada en el índice.",
    "deletedSnack": "Eliminado {id}.",
    "deleteFailedApi": "Error al eliminar: {error}",
    "deleteFailedGeneric": "Error al eliminar: {error}",
    "menuSchedules": "Programaciones",
    "menuTargets": "Destinos",
    "kv": {
      "status": "Estado",
      "target": "Destino",
      "triggeredBy": "Lanzado por",
      "started": "Iniciado",
      "finished": "Finalizado",
      "size": "Tamaño",
      "encrypted": "Cifrado",
      "targetPath": "Ruta de destino",
      "error": "Error",
      "yes": "sí",
      "no": "no"
    },
    "emptyMissingDeps": {
      "headline": "Las copias de seguridad aún no pueden ejecutarse",
      "body": "Instala postgresql-client y reinicia opendray."
    },
    "emptyNoTargets": {
      "headline": "No hay destinos de copia de seguridad configurados",
      "body": "Abre el menú Más → Destinos para añadir un destino (local / S3 / SMB / SFTP / WebDAV / rclone). Luego vuelve y toca \"Ejecutar ahora\"."
    },
    "emptyNoBackups": {
      "headline": "Aún no hay copias de seguridad",
      "body": "Toca \"Ejecutar ahora\" para tomar una nueva instantánea, o abre Programaciones para configurar ejecuciones periódicas."
    },
    "restartToActivate": "Reinicia opendray para activar las copias de seguridad",
    "passphraseSaved": "Tu passphrase está guardada. El gateway solo la carga al iniciarse, así que los cambios solo surten efecto tras un reinicio.",
    "keyFileLabel": "Archivo de clave",
    "configuredViaLabel": "Configurado mediante",
    "wizard": {
      "title": "Configurar copias de seguridad",
      "intro": "Elige una passphrase maestra. opendray la usa para cifrar cada blob de copia de seguridad con AES-256-GCM. Si pierdes la passphrase, pierdes los datos: no hay forma de recuperarlos.",
      "saving": "Guardando…",
      "generateAndSave": "Generar y guardar",
      "savePassphrase": "Guardar passphrase",
      "generateHint": "El servidor genera una passphrase criptográficamente aleatoria, tú la copias a un gestor de contraseñas y luego confirmas.",
      "helperRecommended": "Recomendado: más de 40 caracteres desde un gestor de contraseñas",
      "saveNowHeader": "Guarda esta passphrase AHORA",
      "saveNowBody": "Se muestra UNA SOLA VEZ. Después no podrás recuperarla desde opendray."
    },
    "statusReady": "Copias de seguridad listas",
    "statusCannot": "Las copias de seguridad no pueden ejecutarse",
    "overviewTargets": "Destinos",
    "overviewSchedules": "Programaciones",
    "overviewBackups": "Copias de seguridad",
    "failedToLoad": "Error al cargar las copias de seguridad",
    "envVarConfigured": "variable de entorno OPENDRAY_BACKUP_KEY",
    "savedConfirmCheckbox": "He guardado esta passphrase en mi gestor de contraseñas",
    "pgDumpMissing": "pg_dump no está en el PATH. Instala postgresql-client y reinicia opendray.",
    "encryption": {
      "checkAgain": "Volver a comprobar",
      "generate": "Generar",
      "paste": "Pegar",
      "random256bit": "Clave aleatoria de 256 bits",
      "passphraseLabel": "Tu passphrase",
      "passphraseHint": "Al menos 20 caracteres",
      "passphraseCopied": "Passphrase copiada al portapapeles"
    },
    "restoreFromFile": "Restaurar desde archivo",
    "restore": {
      "title": "Restaurar desde paquete",
      "subtitle": "Reproduce un paquete cifrado .tar.gz.enc en una base de datos Postgres. El paquete se sube desde este teléfono: elige un archivo generado por una copia de seguridad anterior.",
      "bundleLabel": "Archivo de paquete (.tar.gz.enc)",
      "pickFile": "Elegir archivo",
      "fileSelected": "{name} · {size}",
      "noFile": "Ningún archivo seleccionado",
      "targetDsnLabel": "DSN de Postgres de destino",
      "targetDsnHint": "Déjalo vacío para restaurar en la propia base de datos de opendray.",
      "targetDsnPlaceholder": "postgres://user:pass@host:5432/dbname",
      "cleanLabel": "pg_restore --clean --if-exists",
      "cleanHint": "Elimina los objetos existentes antes de volver a crearlos.",
      "auditNoteLabel": "Nota de auditoría (opcional)",
      "auditNotePlaceholder": "p. ej. recuperando de #INC-481",
      "ownDbWarning": "Restaurar en la PROPIA base de datos de opendray reescribirá las filas que este gateway está sirviendo actualmente. Escribe \"I understand\" para confirmar.",
      "confirmPlaceholder": "Escribe \"I understand\"",
      "confirmSentinel": "I understand",
      "restoring": "Restaurando…",
      "restore": "Restaurar",
      "succeededTitle": "Restauración completada",
      "succeededBody": "Se reprodujeron {bytes} de la copia de seguridad {id}.",
      "failedTitle": "Error en la restauración",
      "pickFileToast": "Primero elige un archivo de paquete.",
      "outputTitle": "Salida de pg_restore",
      "noPgRestoreOutput": "(vacío: la restauración se completó sin salida)",
      "manifestTitle": "Manifiesto",
      "manifestBackupId": "ID de copia de seguridad",
      "manifestVersion": "Versión del manifiesto",
      "manifestCreatedAt": "Creado",
      "manifestPgVersion": "pg_version",
      "manifestOpendrayVersion": "versión de opendray",
      "fingerprint": "Huella de la clave",
      "fingerprintOk": "coincide",
      "fingerprintMismatch": "NO COINCIDE",
      "encryptionAlgo": "Cifrado",
      "bytesRead": "Bytes leídos",
      "targetDsnUsed": "DSN de destino",
      "targetDsnSelfLabel": "(la propia base de datos de opendray)",
      "done": "Hecho"
    },
    "inventory": {
      "title": "Qué contiene una copia de seguridad",
      "summary": "{rows} filas · {tables} tablas",
      "description": "Recuentos de filas en vivo desde la base de datos Postgres de opendray. Las copias de seguridad capturan todas las filas de abajo; los artefactos binarios en disco no se incluyen.",
      "rowsLabel": "filas",
      "loadFailedToast": "Error al cargar el inventario",
      "loading": "Cargando…",
      "tap": "Toca para expandir"
    }
  },
  "backupTargets": {
    "title": "Destinos de copia de seguridad",
    "newTarget": "Nuevo destino",
    "testConnection": "Probar conexión",
    "editConfig": "Editar configuración",
    "viewRawConfig": "Ver configuración sin procesar",
    "configDialogTitle": "Configuración de {kind}",
    "deleteTitle": "¿Eliminar destino?",
    "errorWithMessage": "{prefix}: {error}"
  },
  "backupSchedules": {
    "title": "Programaciones de copia de seguridad",
    "newButton": "Nueva",
    "deleteTitle": "¿Eliminar programación?",
    "targetLabel": "Destino",
    "intervalLabel": "Intervalo",
    "retentionLabel": "Retención (conservar las N más recientes)",
    "errorWithMessage": "{prefix}: {error}",
    "noTargets": "No hay destinos de copia de seguridad configurados. Añade uno desde el panel de administración web o la pantalla de Destinos.",
    "okMsgCreate": "Programación creada.",
    "okMsgUpdate": "Programación actualizada.",
    "okMsgDelete": "Programación eliminada.",
    "errorPrefixCreate": "Error al crear",
    "errorPrefixUpdate": "Error al actualizar",
    "errorPrefixDelete": "Error al eliminar",
    "deleteBody": "Elimina la especificación recurrente para el destino {targetId}. Los blobs de copia de seguridad existentes no se modifican.",
    "emptyList": "Aún no hay programaciones.\nToca \"Nueva\" para crear una.",
    "validatePickTarget": "Elige un destino.",
    "validateInterval": "El intervalo debe ser > 0.",
    "formTitleEdit": "Editar programación",
    "formTitleNew": "Nueva programación",
    "saveButtonEdit": "Guardar",
    "saveButtonNew": "Crear",
    "targetFixedHint": "El destino queda fijado una vez creado.",
    "enabledOn": "El programador la ejecutará según la cadencia.",
    "enabledOff": "En pausa. No habrá ejecuciones automáticas hasta volver a activarla.",
    "loadFailedTitle": "Error al cargar las programaciones",
    "pausedBadge": "en pausa",
    "everyInterval": "cada {interval}",
    "keepRetention": "· conservar {n}",
    "nextRun": "· siguiente {when}",
    "lastRun": "· última {when}"
  },
  "backupTargetEditor": {
    "useHttps": "Usar HTTPS",
    "pathStyle": "Direccionamiento por ruta (path-style)",
    "pathStyleSubtitle": "Heredado / MinIO",
    "kinds": {
      "local": {
        "label": "Disco local",
        "description": "Carpeta en la máquina que ejecuta opendray"
      },
      "smb": {
        "label": "Recurso compartido SMB",
        "description": "Recursos compartidos de Windows y la mayoría de los NAS domésticos"
      },
      "webdav": {
        "label": "WebDAV",
        "description": "Nubes autoalojadas y servicios para compartir archivos"
      },
      "sftp": {
        "label": "SFTP",
        "description": "Cualquier servidor accesible por SSH"
      },
      "s3": {
        "label": "S3 / compatible",
        "description": "Amazon S3 y buckets compatibles con S3 (MinIO, R2, B2)"
      },
      "rclone": {
        "label": "rclone (cualquiera)",
        "description": "OneDrive, Google Drive, Dropbox a través de la CLI de rclone"
      }
    },
    "formTitleEdit": "Editar destino",
    "formTitleNew": "Nuevo destino de backup",
    "idHintAuto": "Automático: {prefix}-1",
    "idHelper": "Letras minúsculas, dígitos, guiones. Por defecto, la siguiente ranura disponible.",
    "enabledOn": "Los backups programados y puntuales pueden usar este destino.",
    "enabledOff": "El servidor se negará a escribir backups aquí.",
    "saving": "Guardando…",
    "create": "Crear",
    "rootDirLabel": "Directorio raíz",
    "rootDirHint": "Vacío = cfg.backup.local_dir (~/.opendray/backups)",
    "hostLabel": "Host",
    "portLabel": "Puerto",
    "shareLabel": "Recurso compartido",
    "shareHint": "Nombre del recurso compartido de nivel superior",
    "shareSampleHint": "Claude_Workspace",
    "userLabel": "Usuario",
    "passwordLabel": "Contraseña",
    "passwordHintKeepCurrent": "Déjalo en blanco para conservar la actual",
    "passwordHintKeep": "Déjalo en blanco para conservarla",
    "pathPrefixLabel": "Prefijo de ruta",
    "pathPrefixHintShareRoot": "Subcarpeta bajo la raíz del recurso compartido (opcional)",
    "pathPrefixHintBaseUrl": "Subcarpeta bajo la URL base (opcional)",
    "pathPrefixHintObjectKey": "Prefijo de clave de objeto (opcional)",
    "pathPrefixHintSshFolder": "Absoluta o relativa al home del usuario (opcional)",
    "pathPrefixHintRemoteRoot": "Subcarpeta bajo la raíz remota (opcional)",
    "endpointLabel": "Endpoint",
    "regionLabel": "Región",
    "bucketLabel": "Bucket",
    "accessKeyLabel": "Clave de acceso",
    "secretKeyLabel": "Clave secreta",
    "secretKeyHintEdit": "Déjalo en blanco para conservar la actual. Se almacena cifrada con AES-256-GCM.",
    "secretKeyHintNew": "Se almacena cifrada con AES-256-GCM; nunca se devuelve.",
    "baseUrlLabel": "URL base",
    "baseUrlHint": "URL completa incluyendo la ruta. Nextcloud: https://cloud.example/remote.php/dav/files/<user>",
    "sftpPasswordHintEdit": "Déjalo en blanco para conservarla. Si están presentes tanto la contraseña como la clave privada, prevalece la clave privada.",
    "sftpPasswordHintNew": "Contraseña O clave privada. Si están ambas, la contraseña pasa a ser solo un respaldo.",
    "privateKeyLabel": "Clave privada (PEM)",
    "privateKeyHintEdit": "Déjalo en blanco para conservarla. Pega el contenido OpenSSH/PEM.",
    "privateKeyHintNew": "Pega el contenido de una clave privada OpenSSH/PEM. Entrada de varias líneas: conserva los marcadores BEGIN/END.",
    "hostKeyLabel": "Clave de host (fijación)",
    "hostKeyHint": "Clave pública del servidor en formato OpenSSH. Usa `ssh-keyscan <host>` para obtenerla. En blanco = sin fijación (NO recomendado fuera de la LAN).",
    "rcloneNote": "Requiere la CLI de rclone en el host de opendray. Ejecuta primero `rclone config` una vez de forma interactiva para autenticar las cuentas en la nube.",
    "rcloneRemoteLabel": "Nombre del remoto",
    "rcloneRemoteHint": "Nombre de `rclone config` (sin los dos puntos).",
    "rcloneBinaryLabel": "Ruta del binario",
    "rcloneBinaryHint": "Anula `which rclone`. Vacío = búsqueda en PATH.",
    "rcloneConfigLabel": "Ruta de configuración",
    "rcloneConfigHint": "Anula --config. Vacío = valor por defecto de rclone."
  },
  "githosts": {
    "title": "Hosts de Git",
    "addHost": "Añadir host",
    "deleteTitle": "¿Eliminar host de Git?",
    "errorWithMessage": "{prefix}: {error}",
    "errorPrefix": {
      "toggle": "Error al alternar",
      "delete": "Error al eliminar"
    },
    "form": {
      "kindLabel": "Tipo",
      "hostLabel": "Host",
      "nameLabel": "Nombre",
      "nameHint": "work-github, personal-gitlab, …",
      "kinds": {
        "github": "GitHub",
        "gitlab": "GitLab",
        "bitbucket": "Bitbucket",
        "gitea": "Gitea",
        "custom": "Personalizado"
      },
      "validateHost": "El host es obligatorio.",
      "validateName": "El nombre es obligatorio.",
      "snackAdded": "Host añadido.",
      "snackUpdated": "Host actualizado.",
      "saveFailedApi": "Error al guardar: {error}",
      "saveFailedGeneric": "Error al guardar: {error}",
      "saving": "Guardando…",
      "save": "Guardar",
      "add": "Añadir",
      "nameHelper": "Nombre visible que se muestra en las listas de PR.",
      "tokenLabelKeep": "Token (déjalo en blanco para conservar el actual)",
      "tokenLabel": "Token",
      "tokenHintKeep": "Déjalo en blanco para conservar el actual.",
      "tokenHintNew": "Pega el token de acceso personal.",
      "enabledHelper": "Disponible para las sessions en búsquedas de PR / remotos.",
      "validateTokenRequired": "El token es obligatorio al añadir un host.",
      "appBarEdit": "Editar {name}",
      "appBarNew": "Añadir host de Git",
      "tokenPreviewHint": "Vista previa actual: {preview}",
      "tokenPreviewNone": "(ninguno)",
      "pausedSubtitle": "En pausa. Las sessions omiten este host."
    },
    "deleteBody": "Elimina la credencial. Las sessions que intenten listar PRs de {host} recurrirán a la API sin autenticar.",
    "deletedSnack": "{name} eliminado.",
    "enabledSnack": "{name} habilitado.",
    "disabledSnack": "{name} deshabilitado.",
    "emptyList": "No hay hosts de Git configurados.\n\nAñade una credencial para que el gateway pueda listar pull requests en todos tus repos.",
    "failedToLoad": "Error al cargar los hosts de Git"
  },
  "channels": {
    "title": "Canales",
    "new": "Nuevo",
    "sendTest": "Enviar mensaje de prueba",
    "editConfig": "Editar configuración",
    "editNotifications": "Editar notificaciones",
    "viewRawConfig": "Ver configuración sin procesar",
    "copyChannelId": "Copiar id del canal",
    "copiedSnack": "Copiado {id}",
    "createdSnack": "Canal {kind} creado.",
    "createFailedApi": "Error al crear: {error}",
    "createFailedGeneric": "Error al crear: {error}",
    "deleteTitle": "¿Eliminar canal?",
    "configDialog": {
      "title": "Configuración de {kind}"
    },
    "webhookDialog": {
      "title": "URL del webhook de {kind}",
      "copiedSnack": "URL del webhook copiada."
    },
    "errorWithMessage": "{prefix}: {error}",
    "notifications": {
      "title": "Preferencias de notificación",
      "repeatPolicy": "Política de repetición",
      "cooldownWindow": "Ventana de cooldown",
      "includeSnippet": "Incluir fragmento del terminal",
      "snippetLengthCap": "Límite de longitud del fragmento",
      "snippetHelper": "Incrusta el final reciente del terminal en cada notificación.",
      "snippetNoCap": "sin límite",
      "snippetChars": "{n} caracteres",
      "updatedSnack": "Preferencias de notificación actualizadas.",
      "modes": {
        "onceLabel": "Una vez por session",
        "onceDescription": "Se dispara una vez al quedar inactiva, permanece en silencio hasta la respuesta o el fin.",
        "cooldownLabel": "Cooldown por ventana de tiempo",
        "cooldownDescription": "Suprime las repeticiones dentro de la ventana elegida.",
        "everyLabel": "Cada evento (ruidoso)",
        "everyDescription": "Sin supresión. Solo para canales de baja frecuencia."
      }
    },
    "popup": {
      "enable": "Activar",
      "disable": "Desactivar",
      "mute": "Silenciar",
      "unmute": "Reactivar sonido",
      "deleteLabel": "Eliminar"
    },
    "badges": {
      "running": "en ejecución",
      "starting": "iniciando…",
      "disabled": "desactivado",
      "muted": "silenciado"
    },
    "capsLabel": "· caps: {list}",
    "bridgeWebOnly": "Los canales bridge solo están disponibles en la web",
    "bridgeEmptyAdd": "Añade uno desde el admin web: Canales → Nuevo.",
    "deleteBody": "Detiene el canal y elimina su configuración. Las notificaciones en curso dirigidas a él se descartarán de forma silenciosa.",
    "snacks": {
      "testDispatched": "Mensaje de prueba enviado.",
      "channelEnabled": "Canal activado.",
      "channelDisabled": "Canal desactivado.",
      "channelMuted": "Canal silenciado.",
      "channelUnmuted": "Sonido del canal reactivado.",
      "configUpdated": "Configuración del canal actualizada.",
      "channelDeleted": "Canal eliminado."
    },
    "errorPrefix": {
      "test": "Error en la prueba",
      "toggle": "Error al alternar",
      "muteToggle": "Error al alternar el silencio",
      "update": "Error al actualizar",
      "delete": "Error al eliminar"
    },
    "failedToLoad": "Error al cargar los canales",
    "kinds": {
      "telegram": {
        "description": "Bot mediante @BotFather. opendray hace long-polling de getUpdates y envía vía REST. Los botones y reply_to_message funcionan de forma nativa.",
        "botTokenLabel": "Token del bot",
        "botTokenHint": "De @BotFather. Se guarda en la configuración del canal; API solo para administradores.",
        "chatIdLabel": "Chat ID por defecto",
        "chatIdPlaceholder": "42 (opcional, se usa cuando no hay ReplyCtx)",
        "ownerUserIdsLabel": "ID(s) de usuario de Telegram del propietario",
        "ownerUserIdsPlaceholder": "123456789 (separados por comas para más de uno)",
        "ownerUserIdsHint": "Solo estos IDs numéricos de usuario de Telegram pueden controlar sessions, ejecutar comandos o pulsar botones; el resto se ignora. Déjalo en blanco para permitir a cualquiera (no recomendado para chat bidireccional). Obtén el tuyo enviando un DM a @userinfobot.",
        "chatEnabledLabel": "Chat bidireccional (enrutar mensajes a la session)",
        "chatEnabledHint": "Cuando está activado, tus mensajes se escriben en la session seleccionada y el agente responde aquí. Desactívalo solo para notificaciones.",
        "chatTypingLabel": "Mostrar “escribiendo…” mientras el agente trabaja",
        "chatTypingHint": "Muestra un indicador de escritura hasta que la respuesta del agente se asienta.",
        "replyMaxCharsLabel": "Longitud máxima de la respuesta (caracteres)",
        "replyMaxCharsPlaceholder": "3500 (en blanco = 3500, 0 = sin límite)",
        "replyMaxCharsHint": "Limita cuánto de la respuesta del agente se envía antes de recortarla con una nota “…(truncado)”. En blanco usa el valor por defecto de 3500 (~un mensaje); pon 0 para enviar la respuesta completa, dividida en varios mensajes."
      },
      "slack": {
        "description": "Socket Mode, no necesita un webhook público. Requiere un token OAuth de bot (xoxb-) y un token a nivel de app (xapp-) con connections:write.",
        "botTokenLabel": "Token del bot (xoxb-…)",
        "botTokenHint": "OAuth & Permissions → Bot User OAuth Token. Necesita chat:write.",
        "appTokenLabel": "Token a nivel de app (xapp-…)",
        "appTokenHint": "Settings → Basic Information → App-Level Tokens. Scope: connections:write.",
        "channelIdLabel": "ID de canal por defecto",
        "channelIdPlaceholder": "C0123ABC456 (opcional)"
      },
      "discord": {
        "description": "Bot mediante el Discord Developer Portal con MESSAGE CONTENT INTENT activado. Se conecta al Gateway WS, no requiere URL pública.",
        "botTokenLabel": "Token del bot",
        "botTokenPlaceholder": "Token del bot del Discord Developer Portal",
        "botTokenHint": "Application → Bot → Reset Token. Invita al bot con send_messages + embed_links.",
        "channelIdLabel": "ID de canal por defecto",
        "channelIdPlaceholder": "123456789012345678 (opcional)"
      },
      "feishu": {
        "description": "Credenciales a nivel de app. Usa el webhook de suscripción de eventos para la entrada. La URL pública del webhook se genera abajo: pégala en la consola de desarrollo de Feishu.",
        "afterCreateHint": "Abre la URL del webhook desde la tarjeta del canal y pégala en Feishu Open Platform → Event Subscriptions → Request URL.",
        "appIdLabel": "App ID",
        "appSecretLabel": "App secret",
        "appSecretPlaceholder": "Secret de la credencial de la aplicación",
        "verificationTokenLabel": "Token de verificación",
        "verificationTokenHint": "De Event Subscriptions → Verification Token. Cuando se establece, opendray rechaza los webhooks con un token diferente.",
        "chatIdLabel": "Chat ID por defecto (oc_…)",
        "chatIdPlaceholder": "oc_xxxxxxxxxx (opcional)"
      },
      "dingtalk": {
        "description": "Robot de grupo personalizado. Solo saliente. Chat de grupo → Robots → Añadir → Modo de firma → copia el webhook + secret.",
        "webhookUrlLabel": "URL del webhook",
        "secretLabel": "Secret de firma",
        "secretHint": "Cuando el robot está configurado en modo de seguridad \"Sign\", copia el secret aquí. opendray añade los parámetros timestamp + sign automáticamente."
      },
      "wecom": {
        "description": "Webhook de robot de grupo. Solo saliente (texto + markdown). Configuración del grupo → Robots de grupo → Añadir → copia la URL del webhook.",
        "webhookKeyLabel": "Clave del webhook",
        "webhookKeyPlaceholder": "El valor de la consulta \"key=\"",
        "webhookKeyHint": "O pega la URL completa del webhook en el campo de abajo: cualquiera de las dos opciones es suficiente.",
        "webhookUrlLabel": "O la URL completa del webhook"
      }
    }
  },
  "onboarding": {
    "gatewayLabel": "URL del gateway",
    "gatewayHint": "https://opendray.example.com",
    "continue": "Continuar"
  },
  "skills": {
    "title": "Skills",
    "newSkill": "Nuevo skill",
    "customizingBuiltin": "Personalizando {id} integrado",
    "idLabel": "Id (slug)",
    "idHint": "p. ej. tdd-guide",
    "bodyLabel": "Cuerpo (markdown)",
    "loadFailedApi": "Error al cargar: {error}",
    "loadFailedGeneric": "Error al cargar: {error}",
    "idRequired": "El Id es obligatorio.",
    "bodyRequired": "El cuerpo no puede estar vacío.",
    "snackCreated": "Skill creado.",
    "snackOverride": "Guardado como override del vault.",
    "snackUpdated": "Skill actualizado.",
    "saveFailedApi": "Error al guardar: {error}",
    "saveFailedGeneric": "Error al guardar: {error}",
    "resetTitle": "¿Restablecer al integrado?",
    "deleteTitle": "¿Eliminar skill?",
    "resetBody": "Elimina el override del vault para {id}. Las sesiones recurrirán al cuerpo integrado.",
    "resetButton": "Restablecer",
    "resetSnack": "{id} restablecido al integrado.",
    "deletedSnack": "{id} eliminado.",
    "deleteFailedApi": "Error al eliminar: {error}",
    "deleteFailedGeneric": "Error al eliminar: {error}",
    "deleteBody": "Elimina {id} del vault. Las sesiones que lo referencian fallarán hasta que se restaure.",
    "newSkillTitle": "Nuevo skill",
    "customizeTitle": "Personalizar {id}",
    "editTitle": "Editar {id}",
    "resetTooltip": "Restablecer al integrado",
    "deleteTooltip": "Eliminar",
    "saving": "Guardando…",
    "saveOverride": "Guardar override",
    "overrideBanner": "Al guardar se crea un override del vault con el mismo id. Las sesiones usarán este cuerpo en lugar del integrado hasta que lo restablezcas.",
    "idHelper": "Letras minúsculas / dígitos / guion. Se bloquea una vez creado.",
    "emptyList": "No hay skills configurados. El gateway incluye integrados (planner, code-reviewer, etc.).",
    "failedToLoad": "Error al cargar los skills"
  },
  "customTasks": {
    "title": "Tareas personalizadas",
    "newTask": "Nueva tarea",
    "deleteTitle": "¿Eliminar tarea?",
    "deletedSnack": "{name} eliminada.",
    "deleteFailedApi": "Error al eliminar: {error}",
    "deleteFailedGeneric": "Error al eliminar: {error}",
    "popupEdit": "Editar",
    "popupDelete": "Eliminar",
    "nameHint": "p. ej. backend-tests",
    "commandHint": "/run pnpm test --filter backend",
    "descriptionHint": "Frase breve que aparece bajo el nombre de la tarea.",
    "scopeGlobal": "Global",
    "scopeProject": "Proyecto",
    "cwdHint": "/Users/you/projects/backend",
    "snackCreated": "Tarea creada.",
    "snackUpdated": "Tarea actualizada.",
    "deleteBody": "Quita la tarea del catálogo. Las sessions que ya la insertaron no se ven afectadas.",
    "introBanner": "Define tus propios slash commands. Aparecen en el selector de tareas de la session junto a los integrados.",
    "validateNameRequired": "El nombre es obligatorio",
    "validateCommandRequired": "El comando es obligatorio",
    "validateProjectCwd": "Las tareas con ámbito de proyecto necesitan una ruta cwd absoluta",
    "appBarEdit": "Editar tarea personalizada",
    "appBarNew": "Nueva tarea personalizada",
    "fieldName": "Nombre",
    "nameHelper": "Aparece en el selector de tareas del inspector.",
    "fieldCommand": "Comando",
    "commandHelper": "El texto que se inserta en la session al elegirlo. Puede ser un comando de CLI o un slash command de Claude.",
    "fieldDescription": "Descripción (opcional)",
    "fieldScope": "Ámbito",
    "globalScopeHint": "Visible desde cualquier session, sin importar el cwd.",
    "projectScopeHint": "Visible solo cuando el cwd de una session coincide con la ruta de abajo.",
    "fieldProjectCwd": "cwd del proyecto",
    "cwdHelper": "Ruta absoluta. Las sessions creadas con este cwd exacto verán la tarea.",
    "saving": "Guardando…",
    "save": "Guardar",
    "create": "Crear",
    "failedToLoad": "Error al cargar las tareas personalizadas"
  },
  "notesPage": {
    "title": "Notas",
    "newButton": "Nueva",
    "newNoteDialogTitle": "Nueva nota",
    "searchHint": "Busca en todo el vault…",
    "up": "Arriba",
    "copyPath": "Copiar ruta",
    "open": "Abrir",
    "copiedSnack": "Copiado {path}",
    "deleteTitle": "¿Eliminar nota?",
    "deletedSnack": "Eliminado {path}",
    "deleteFailedApi": "Error al eliminar: {error}",
    "deleteFailedGeneric": "Error al eliminar: {error}",
    "createFailedApi": "Error al crear: {error}",
    "createFailedGeneric": "Error al crear: {error}",
    "pathLabel": "Ruta relativa al vault",
    "pathHint": "personal/scratch.md",
    "create": "Crear",
    "popupDelete": "Eliminar",
    "deleteBody": "Esto es irreversible. La sincronización git del vault también eliminará el archivo en el host del gateway.",
    "emptyFilterMatch": "Ninguna nota coincide con \"{query}\".",
    "emptyVault": "El vault está vacío. Toca + para crear tu primera nota.",
    "emptyFolder": "La carpeta \"{path}\" está vacía.",
    "validatePath": "La ruta es obligatoria",
    "validatePathDots": "La ruta no puede contener \"..\"",
    "pathHelper": "Añade .md automáticamente si falta.",
    "editor": {
      "markdownHint": "Markdown…",
      "saving": "Guardando…",
      "autosave": "Se guarda automáticamente mientras escribes",
      "loadFailedApi": "Error al cargar: {error}",
      "loadFailedGeneric": "Error al cargar: {error}",
      "saveFailedApi": "Error al guardar: {error}",
      "saveFailedGeneric": "Error al guardar: {error}",
      "savedAt": "Guardado {time}"
    }
  },
  "dataExport": {
    "title": "Exportación e importación de datos",
    "subtitle": "Paquetes a nivel de usuario para migración o verificación, independientes de /backups (recuperación ante desastres).",
    "sections": {
      "export": "Exportar",
      "import": "Importar"
    },
    "form": {
      "scope": "Alcance",
      "memories": "Memorias",
      "memoriesHint": "Todas las memorias persistidas y sus embeddings.",
      "integrations": "Integraciones",
      "integrationOptions": {
        "none": "Omitir",
        "noneHint": "No incluir el registro de /integrations.",
        "metadata": "Solo metadatos (predeterminado)",
        "metadataHint": "Nombre y endpoint por integración, sin API keys.",
        "plaintext": "Claves en texto plano",
        "plaintextHint": "PELIGROSO: incluye los API tokens en bruto. v1 solo almacena hashes bcrypt, así que hoy esto es efectivamente una operación nula; muéstralo de todos modos."
      },
      "confirmWarning": "La exportación de claves en texto plano contiene secretos descifrables. Escribe \"Lo entiendo\" para confirmar.",
      "confirmPlaceholder": "Escribe \"Lo entiendo\"",
      "confirmSentinel": "Lo entiendo",
      "customTasks": "Tareas personalizadas",
      "customTasksHint": "Definiciones de tareas por usuario (programaciones cron y cuerpos de script).",
      "footnote": "Los paquetes caducan 7 días después de su creación. El enlace de descarga es de un solo uso.",
      "create": "Crear paquete",
      "building": "Creando…",
      "readyToast": "Paquete listo",
      "readyDescription": "{bytes} bytes, descárgalo desde el historial de abajo.",
      "failedToast": "Error al crear el paquete: {error}"
    },
    "history": {
      "title": "Historial de exportaciones",
      "loading": "Cargando…",
      "empty": "Aún no hay exportaciones.",
      "listFailedToast": "Error al cargar las exportaciones: {error}",
      "downloadFailedToast": "Error al obtener el token de descarga: {error}",
      "noTokenToast": "Esta exportación no tiene un token de descarga utilizable (ya consumido o caducado).",
      "deletedToast": "Exportación eliminada.",
      "deleteFailedToast": "Error al eliminar la exportación: {error}",
      "deleteConfirmTitle": "¿Eliminar la exportación?",
      "deleteConfirmBody": "Elimina el paquete y revoca el token de descarga. {id}",
      "download": "Descargar",
      "delete": "Eliminar",
      "downloadCopiedToast": "URL de descarga copiada al portapapeles. Pégala en un navegador para obtenerla (un solo uso).",
      "columns": {
        "scope": "Alcance",
        "size": "Tamaño",
        "expires": "Caduca",
        "actions": "Acciones"
      },
      "scopeEmpty": "(vacío)",
      "scopeMemories": "memorias",
      "scopeIntegrations": "integraciones({mode})",
      "scopeCustomTasks": "custom_tasks"
    },
    "import": {
      "intro": "Reproduce un paquete generado previamente por Exportar. Solo se importan las entidades que marques abajo; todo lo demás en el paquete se ignora.",
      "bundleLabel": "Archivo de paquete (.zip)",
      "pickFile": "Elegir archivo",
      "fileSelected": "{name} · {size}",
      "noFile": "Ningún archivo seleccionado",
      "memoriesLabel": "Memorias",
      "integrationsLabel": "Integraciones",
      "customTasksLabel": "Tareas personalizadas",
      "importBundle": "Importar paquete",
      "importing": "Importando…",
      "pickFileToast": "Elige primero un archivo de paquete.",
      "doneToast": "Importación completada",
      "finishedWithErrors": "Importación finalizada con errores",
      "failedToast": "Error en la importación: {error}",
      "summaryCard": {
        "memories": "Memorias",
        "integrations": "Integraciones",
        "customTasks": "Tareas personalizadas",
        "created": "creadas",
        "skipped": "omitidas",
        "failed": "fallidas"
      }
    },
    "imports": {
      "title": "Historial de importaciones",
      "loading": "Cargando…",
      "empty": "Aún no hay importaciones.",
      "listFailedToast": "Error al cargar las importaciones: {error}",
      "noneCounts": "(sin recuentos)",
      "sourceUnknown": "(origen desconocido)",
      "columns": {
        "id": "ID",
        "status": "Estado",
        "source": "Origen",
        "counts": "Recuentos",
        "when": "Cuándo"
      }
    },
    "relative": {
      "inSeconds": "en {n}s",
      "inMinutes": "en {n}m",
      "inHours": "en {n}h",
      "inDays": "en {n}d",
      "secondsAgo": "hace {n}s",
      "minutesAgo": "hace {n}m",
      "hoursAgo": "hace {n}h"
    },
    "status": {
      "pending": "pendiente",
      "running": "en ejecución",
      "ready": "listo",
      "failed": "fallido",
      "expired": "caducado",
      "succeeded": "completado"
    }
  },
  "memory": {
    "status": {
      "label": "Embedder activo",
      "dimensions": "{dim}-dim · {state}",
      "enabled": "habilitado",
      "disabled": "deshabilitado",
      "floorNoModel": "Solo recuperación por palabras clave (BM25) — no hay modelo de embedding configurado. Configura un endpoint denso en Settings para habilitar la memoria semántica.",
      "denseConfiguredPendingRestart": "Configurado {model} (denso) — reinicia el gateway para activar la memoria semántica y re-embeber las memorias existentes.",
      "denseUnreachableFloor": "Configurado {model} (denso) pero el endpoint está inalcanzable — se usa el piso de palabras clave hasta que responda (se actualiza al reiniciar).",
      "denseDegraded": "Embedder denso activo pero su endpoint está inalcanzable ahora — los vectores existentes se conservan; las nuevas escrituras y la búsqueda por similitud se pausan hasta que responda."
    },
    "title": "Memoria",
    "more": "Más",
    "workers": "Workers de memoria",
    "rank": {
      "title": "Desglose del ranking",
      "effective": "Puntuación efectiva: {value}",
      "similarity": "Similitud del coseno",
      "ageMultiplier": "Multiplicador por antigüedad ({days}d de antigüedad)",
      "hitMultiplier": "Multiplicador por número de hits ({hits} hits)",
      "confidenceMultiplier": "Multiplicador por confianza",
      "formula": "effective = similarity × age × hits × confidence",
      "close": "Cerrar"
    },
    "new": "Nuevo",
    "searchHint": "Buscar…",
    "projectLabel": "Proyecto",
    "filterHint": "Filtrar por nombre o ruta…",
    "copied": "Copiado",
    "copyTooltip": "Copiar texto",
    "deleteAllConfirm": {
      "title": "¿Eliminar todas las memorias de este ámbito?",
      "deleteAll": "Eliminar todas"
    },
    "deletedSnackOne": "Se eliminó {n} elemento de memoria",
    "deletedSnackOther": "Se eliminaron {n} elementos de memoria",
    "bulkDeleteFailedApi": "Error al eliminar en bloque: {error}",
    "bulkDeleteFailedGeneric": "Error al eliminar en bloque: {error}",
    "deleteOne": {
      "title": "¿Eliminar memoria?",
      "body": "Esto no se puede deshacer."
    },
    "scope": {
      "project": "Proyecto",
      "global": "Global"
    },
    "create": {
      "textLabel": "Texto",
      "scopeKeyLabel": "Clave de ámbito (cwd del proyecto)",
      "scopeKeyHint": "/Users/you/projects/foo",
      "submit": "Crear"
    }
  },
  "about": {
    "title": "Acerca de",
    "loading": "Cargando…",
    "sections": {
      "app": "App",
      "server": "Servidor",
      "gateway": "Gateway"
    },
    "fields": {
      "app": "App",
      "version": "Versión",
      "versionFormat": "{version} (build {build})",
      "package": "Paquete",
      "url": "URL",
      "signedInAs": "Sesión iniciada como",
      "tokenExpires": "El token caduca"
    },
    "copied": "{label} copiado",
    "copyTooltip": "Copiar",
    "copyLabels": {
      "version": "versión",
      "serverUrl": "URL del servidor"
    },
    "tagline": "opendray móvil, control del gateway multi-CLI.\nFuente: github.com/Opendray/opendray",
    "gateway": {
      "version": "Versión",
      "commit": "Commit",
      "checking": "Buscando actualizaciones…",
      "upToDate": "Actualizado",
      "updateAvailable": "Actualización disponible: {version}",
      "releaseNotes": "Notas de la versión",
      "checkFailed": "Comprobación de actualizaciones no disponible"
    }
  },
  "settings": {
    "title": "Ajustes",
    "language": {
      "section": "Idioma",
      "system": "Sistema",
      "systemSubtitle": "Sigue la configuración de idioma de tu teléfono",
      "english": "English",
      "chinese": "中文",
      "spanish": "Español"
    },
    "appearance": {
      "section": "Apariencia",
      "system": "Sistema",
      "systemSubtitle": "Sigue la configuración de apariencia de tu teléfono",
      "light": "Claro",
      "lightSubtitle": "Usa siempre la paleta clara",
      "dark": "Oscuro",
      "darkSubtitle": "Usa siempre la paleta oscura"
    },
    "account": {
      "section": "Cuenta",
      "changeCredentials": "Cambiar credenciales",
      "changeCredentialsSubtitle": "Usuario y contraseña"
    },
    "gateway": {
      "section": "Gateway",
      "serverSettings": "Ajustes del servidor",
      "serverSettingsSubtitle": "Dirección de escucha, registro, vault, memoria, rutas de almacenamiento…",
      "liveLogs": "Logs en vivo",
      "liveLogsSubtitle": "Sigue el búfer de logs del gateway (la misma fuente que el panel web)"
    },
    "changeCredentials": {
      "title": "Cambiar credenciales",
      "explanation": "Verifica tu contraseña actual y luego elige nuevas credenciales. Todas las demás sessions con sesión iniciada se revocarán.",
      "currentPassword": "Contraseña actual",
      "newUsername": "Nuevo usuario",
      "newPassword": "Nueva contraseña",
      "confirmPassword": "Confirma la nueva contraseña",
      "validatorRequired": "Obligatorio",
      "passwordHelper": "Al menos 8 caracteres",
      "passwordTooShort": "Debe tener al menos 8 caracteres",
      "passwordMismatch": "No coincide con la nueva contraseña",
      "updatedSnack": "Credenciales actualizadas.",
      "wrongCurrent": "La contraseña actual es incorrecta.",
      "saving": "Guardando…",
      "update": "Actualizar"
    },
    "logViewer": {
      "title": "Logs en vivo",
      "reconnect": "Reconectar",
      "copyBuffer": "Copiar búfer",
      "clearLocal": "Borrar vista local",
      "copiedSnack": "Búfer copiado al portapapeles",
      "filterHint": "Filtrar subcadena…",
      "levels": {
        "all": "Todos",
        "debug": "Debug",
        "info": "Info",
        "warn": "Warn",
        "error": "Error"
      }
    },
    "serverSettings": {
      "title": "Ajustes del servidor",
      "reloadTooltip": "Recargar desde el servidor",
      "restartTooltip": "Reiniciar gateway",
      "restartConfirmTitle": "¿Reiniciar opendray?",
      "restartConfirmBody": "El gateway se ejecutará de nuevo a sí mismo. La app móvil puede perder la conexión brevemente.",
      "restart": "Reiniciar",
      "restartQueuedSnack": "Reinicio solicitado. Desliza para actualizar en un momento.",
      "restartFailedApi": "Falló el reinicio: {error}",
      "restartFailedGeneric": "Falló el reinicio: {error}",
      "loadedFrom": "Cargado desde: {path}",
      "restartHint": "La mayoría de las secciones necesitan un reinicio del gateway para surtir efecto. El botón de reinicio está en la AppBar.",
      "savedNeedsRestart": "Guardado. Reinicia el gateway para aplicar.",
      "savedSimple": "Guardado.",
      "changesNeedRestart": "Los cambios en esta sección necesitan un reinicio del gateway.",
      "loadFailed": "No se pudieron cargar los ajustes del servidor",
      "sections": {
        "general": "General",
        "logging": "Registro",
        "sessions": "Sessions",
        "vault": "Vault",
        "mcpRegistry": "Registro de MCP",
        "memory": "Memoria",
        "backup": "Copia de seguridad",
        "storageClaude": "Almacenamiento · Claude",
        "storageCodex": "Almacenamiento · Codex",
        "storageGemini": "Almacenamiento · Gemini"
      },
      "sectionDescriptions": {
        "general": "Dirección de escucha, cuenta del operador, TTL del token.",
        "logging": "Verbosidad, formato y ruta del log en disco.",
        "sessions": "Umbrales de detección de inactividad.",
        "vault": "Notas, skills y raíz versionada con git.",
        "mcpRegistry": "Rutas del vault para servidores MCP + archivo de secretos.",
        "memory": "Subsistema de memoria persistente entre CLIs.",
        "backup": "Copias de seguridad cifradas de la BD + exportaciones de datos de admin. La frase de contraseña vive en el keyfile (Ajustes → Copias de seguridad).",
        "storageClaude": "Dónde viven los transcripts de Claude en disco.",
        "storageCodex": "Raíz de las sessions de Codex.",
        "storageGemini": "Rutas de tmp por proyecto + projects.json."
      },
      "fields": {
        "listenAddress": "Dirección de escucha",
        "adminUser": "Usuario admin",
        "adminUserHelper": "Efectivo cuando no hay keyfile ni variable de entorno configurada. Si no, consulta Ajustes → Cuenta.",
        "adminPassword": "Contraseña admin",
        "adminPasswordHelper": "Envíalo en blanco para conservarlo. Para rotaciones continuas usa Ajustes → Cuenta (respaldado por keyfile, sin reinicio).",
        "tokenTtlWeb": "TTL del token (web)",
        "tokenTtlHelper": "Cadena de duración de Go, p. ej. 24h, 30m.",
        "level": "Nivel",
        "format": "Formato",
        "filePath": "Ruta del archivo",
        "filePathHelper": "Vacío = solo stdout.",
        "idleThreshold": "Umbral de inactividad",
        "idleThresholdHelper": "Periodo de silencio antes de marcar una session como inactiva. Duración de Go.",
        "idleCheckInterval": "Intervalo de comprobación de inactividad",
        "idleCheckHelper": "Con qué frecuencia se ejecuta el reaper de inactividad.",
        "root": "Raíz",
        "rootHelper": "Padre de las sub-rutas notes / skills / git_root.",
        "notesPath": "Ruta de notas",
        "skillsPath": "Ruta de skills",
        "gitRoot": "Raíz de git",
        "personalPrefix": "Prefijo personal",
        "projectsPrefix": "Prefijo de proyectos",
        "registryRoot": "Raíz del registro",
        "secretsFile": "Archivo de secretos",
        "backend": "Backend",
        "store": "Almacén",
        "defaultTopK": "Top-k por defecto",
        "similarityThreshold": "Umbral de similitud",
        "defaultScope": "Ámbito por defecto",
        "preserveHelper": "En blanco para conservar el actual.",
        "localModelName": "Nombre del modelo local",
        "localLibraryPath": "Ruta de la biblioteca local",
        "localModelPath": "Ruta del modelo local",
        "localTokenizerPath": "Ruta del tokenizador local",
        "localMaxSeqLen": "Longitud máx. de secuencia local",
        "backupEnabled": "Habilitado",
        "backupEnabledHelper": "Aunque esto esté activado, el subsistema de copias de seguridad permanece apagado hasta que se configure OPENDRAY_BACKUP_KEY o el keyfile.",
        "backupLocalDir": "Directorio local",
        "backupExportDir": "Directorio de exportación",
        "pathHelper": "Vacío = resolver desde PATH al arrancar.",
        "accountsDir": "Directorio de cuentas",
        "accountsHelper": "Padre de los subdirectorios .claude/ por cuenta. Vacío = ~/.claude-accounts.",
        "sessionsRoot": "Raíz de sessions",
        "sessionsRootHelper": "Vacío = ~/.codex/sessions.",
        "listenHelper": "host:port al que se vincula el gateway. Requiere reinicio.",
        "secretsHelper": "Vault de secretos cifrado con AES-256-GCM.",
        "backendHelper": "auto elige el mejor disponible; local necesita ONNX.",
        "similarityHelper": "0.0-1.0; los resultados por debajo de esto se filtran.",
        "defaultFallback": "Por defecto: {value}",
        "httpBaseUrl": "URL base HTTP",
        "httpModel": "Modelo HTTP",
        "httpApiKey": "API key HTTP",
        "httpDimensions": "Dimensiones HTTP",
        "pgDumpPath": "Ruta de pg_dump",
        "pgRestorePath": "Ruta de pg_restore",
        "tmpRoot": "Raíz de tmp",
        "projectsJson": "projects.json"
      },
      "validateInteger": "\"{field}\" debe ser un entero",
      "validateNumber": "\"{field}\" debe ser un número"
    }
  }
}
