diff --git a/app/src/i18n/index.ts b/app/src/i18n/index.ts index 85de47893..7d26f2ab9 100644 --- a/app/src/i18n/index.ts +++ b/app/src/i18n/index.ts @@ -2,15 +2,17 @@ import i18n from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import { initReactI18next } from 'react-i18next'; import en from './locales/en/translation.json'; +import es from './locales/es/translation.json'; +import fr from './locales/fr/translation.json'; +import it from './locales/it/translation.json'; import ja from './locales/ja/translation.json'; import ptBR from './locales/pt-BR/translation.json'; import zhCN from './locales/zh-CN/translation.json'; import zhTW from './locales/zh-TW/translation.json'; -import fr from './locales/fr/translation.json'; -import it from './locales/it/translation.json'; export const SUPPORTED_LANGUAGES = [ { code: 'en', label: 'English' }, + { code: 'es', label: 'Español' }, { code: 'pt-BR', label: 'Português (Brasil)' }, { code: 'ja', label: '日本語' }, { code: 'zh-CN', label: '简体中文' }, @@ -27,6 +29,7 @@ i18n .init({ resources: { en: { translation: en }, + es: { translation: es }, 'pt-BR': { translation: ptBR }, ja: { translation: ja }, 'zh-CN': { translation: zhCN }, diff --git a/app/src/i18n/locales/es/translation.json b/app/src/i18n/locales/es/translation.json new file mode 100644 index 000000000..f1cbfd19f --- /dev/null +++ b/app/src/i18n/locales/es/translation.json @@ -0,0 +1,1274 @@ +{ + "common": { + "cancel": "Cancelar", + "save": "Guardar", + "delete": "Eliminar", + "edit": "Editar", + "close": "Cerrar", + "confirm": "Confirmar", + "loading": "Cargando…", + "error": "Error", + "unknown": "Desconocido", + "unknownError": "Error desconocido" + }, + "nav": { + "generate": "Generar", + "stories": "Historias", + "captures": "Capturas", + "voices": "Voces", + "effects": "Efectos", + "audio": "Audio", + "models": "Modelos", + "settings": "Ajustes", + "updateBadge": "Actualizar" + }, + "captures": { + "title": "Capturas", + "beta": "Beta", + "searchPlaceholder": "Buscar transcripciones…", + "snippetEmpty": "(sin transcripción)", + "noTranscriptError": "La captura aún no tiene transcripción", + "captureCardLabel": "Captura · {{when}}", + "header": { + "modelSummary": "Whisper {{stt}} · Qwen3 · {{llm}}" + }, + "source": { + "dictation": "Dictado", + "recording": "Grabación", + "file": "Archivo" + }, + "transcript": { + "refined": "Refinada", + "raw": "En bruto", + "refinedHint": "Refinada con Qwen3 · {{model}}", + "rawHint": "Transcrita con Whisper {{model}}" + }, + "actions": { + "configure": "Configurar", + "import": "Importar", + "importing": "Subiendo…", + "dictate": "Dictar", + "stop": "Detener", + "copy": "Copiar", + "refine": "Refinar", + "reRefine": "Volver a refinar", + "export": "Exportar", + "exportDropdownLabel": "Exportar captura como", + "exportAudio": "Audio (WAV)", + "exportTranscript": "Transcripción (TXT)", + "exportMarkdown": "Markdown (MD)", + "delete": "Eliminar", + "playAs": "Reproducir como {{name}}", + "playAsFallback": "Reproducir como…", + "playAsGenerating": "Generando…", + "playAsStop": "Detener · {{name}}", + "playAsStopFallback": "Detener · Voz", + "playAsDropdownLabel": "Reproducir transcripción como" + }, + "empty": { + "noMatches": "Ninguna captura coincide con \"{{query}}\"", + "none": "Aún no hay capturas.", + "loading": "Cargando capturas…", + "pickOne": "Elige una captura para ver la transcripción.", + "holdToRecord": "Mantén pulsado para grabar", + "toggleHandsFree": "Alternar manos libres", + "pressShortcut": "Pulsa el atajo en cualquier parte de tu equipo para iniciar tu primera captura.", + "turnOnShortcut": "Activa el atajo global para dictar desde cualquier sitio — o haz clic en Dictar arriba para una captura dentro de la app.", + "openSettings": "Abrir ajustes de Capturas" + }, + "deleteDialog": { + "title": "Eliminar captura", + "description": "Esto eliminará permanentemente la captura, su audio y su transcripción. No se puede deshacer.", + "deleting": "Eliminando…" + }, + "toast": { + "deleteFailed": "Error al eliminar", + "playAsFailed": "Error al reproducir como", + "noVoice": "Sin perfil de voz", + "noVoiceDescription": "Crea un perfil de voz antes de usar Reproducir como.", + "transcriptCopied": "Transcripción copiada", + "copyFailed": "Error al copiar", + "exportSuccess": "Exportado a {{path}}", + "exportFailed": "Error al exportar", + "exportEmpty": "Nada que exportar", + "shortcutNotArmed": "Atajo activado, pero aún no listo", + "shortcutNotArmedDescription_one": "{{names}} aún necesita descargarse. Abre la pestaña Capturas para empezar.", + "shortcutNotArmedDescription_other": "{{names}} aún necesitan descargarse. Abre la pestaña Capturas para empezar." + }, + "pill": { + "recording": "Grabando", + "transcribing": "Transcribiendo", + "refining": "Refinando", + "speaking": "Hablando", + "completed": "Listo", + "stopAria": "Detener grabación", + "errorFallback": "Algo salió mal", + "errorCopyTooltip": "Haz clic para copiar el error" + }, + "chord": { + "capturing": "Capturando…", + "pressShortcut": "Pulsa tu atajo", + "noKeys": "Aún no hay teclas", + "unsupported": "\"{{key}}\" no se admite en combinaciones. Prueba con un modificador o una tecla de letra.", + "notSet": "Sin definir" + }, + "readiness": { + "title": "Algunas cosas antes de que puedas dictar", + "subheading": "El atajo permanece desactivado hasta que todo lo de abajo esté listo.", + "downloadButton": "Descargar", + "downloading": "Descargando…", + "downloadingPercent": "Descargando… {{pct}}%", + "downloadStarted": "Descarga iniciada", + "downloadStartedDescription": "{{name}} se está descargando. El atajo se activará cuando termine.", + "downloadFailed": "Error de descarga", + "stt": { + "label": "{{name}} (voz a texto)", + "ready": "Modelo descargado.", + "missing": "Necesario para transcribir tu audio", + "missingWithSize": "Necesario para transcribir tu audio · {{size}}" + }, + "llm": { + "label": "{{name}} (refinamiento)", + "ready": "Modelo descargado.", + "missing": "Limpia la transcripción en bruto antes de pegar", + "missingWithSize": "Limpia la transcripción en bruto antes de pegar · {{size}}" + }, + "inputMonitoring": { + "label": "Permiso de Monitorización de entrada", + "ready": "macOS permite que Voicebox detecte tu atajo global.", + "missing": "macOS debe permitir que Voicebox detecte el atajo global.", + "openSettings": "Abrir Ajustes" + }, + "accessibility": { + "label": "Permiso de Accesibilidad", + "ready": "Voicebox puede pegar transcripciones en otras apps.", + "missing": "Necesario para que las transcripciones se peguen en la app activa.", + "openSettings": "Abrir Ajustes" + } + }, + "permissions": { + "accessibility": { + "title": "Concede el permiso de Accesibilidad para habilitar el pegado automático", + "body": "Voicebox necesita Ajustes del Sistema → Privacidad y seguridad → Accesibilidad para pegar transcripciones en otras apps. Tu dictado igualmente aparece en la pestaña Capturas sin él.", + "openSettings": "Abrir Ajustes", + "recheck": "Ya lo he activado", + "rechecking": "Comprobando…", + "stillMissing": "Sigue sin detectarse. macOS suele requerir salir y reabrir Voicebox tras cambiar el permiso." + }, + "inputMonitoring": { + "title": "Concede Monitorización de entrada para habilitar el atajo global", + "body": "Voicebox necesita Ajustes del Sistema → Privacidad y seguridad → Monitorización de entrada para detectar tu combinación de dictado. La opción está activada, pero macOS bloquea los eventos de teclado hasta que lo permitas.", + "openSettings": "Abrir Ajustes", + "recheck": "Ya lo he activado", + "rechecking": "Comprobando…", + "stillMissing": "Sigue sin detectarse. macOS suele requerir salir y reabrir Voicebox tras cambiar el permiso." + } + } + }, + "voicesTab": { + "title": "Voces", + "loading": "Cargando voces…", + "searchPlaceholder": "Buscar voces…", + "newVoice": "Nueva voz", + "avatarAlt": "Avatar de {{name}}", + "selectChannels": "Seleccionar canales…", + "channelDefaultLabel": "{{name}} (Predeterminado)", + "columns": { + "name": "Nombre", + "language": "Idioma", + "generations": "Generaciones", + "samples": "Muestras", + "effects": "Efectos", + "channels": "Canales" + } + }, + "voiceInspector": { + "loading": "Cargando…", + "defaultEffectsHint": "Se aplican automáticamente a las nuevas generaciones con esta voz.", + "fields": { + "description": "Descripción" + }, + "toast": { + "invalidImageFormat": "Selecciona PNG, JPG o WebP", + "avatarUpdated": "Avatar actualizado", + "savedDescription": "\"{{name}}\" guardada." + } + }, + "audioChannels": { + "title": "Canales de audio", + "newChannel": "Nuevo canal", + "loading": "Cargando…", + "confirmDelete": "¿Eliminar este canal?", + "noVoicesAssigned": "Sin voces asignadas", + "selectDevice": "Seleccionar dispositivo", + "addDevice": "Añadir dispositivo", + "addVoice": "Añadir voz", + "defaultSuffix": "predeterminado", + "empty": { + "message": "Aún no hay canales de audio. Crea tu primer canal para enrutar voces a dispositivos concretos.", + "action": "Crear canal" + }, + "labels": { + "outputDevices": "Dispositivos de salida", + "assignedVoices": "Voces asignadas" + }, + "devices": { + "title": "Dispositivos disponibles", + "defaultNote": "El canal predeterminado usa el dispositivo predeterminado del sistema", + "toggleHint": "Haz clic en los dispositivos para añadirlos o quitarlos del canal seleccionado", + "selectHint": "Selecciona un canal para asignar dispositivos", + "empty": "No se encontraron dispositivos de audio", + "requiresTauri": "La selección de dispositivos de audio requiere Tauri" + }, + "fields": { + "name": "Nombre del canal", + "namePlaceholder": "p. ej., Cable virtual, Emisión" + }, + "createDialog": { + "title": "Crear canal de audio", + "description": "Crea un nuevo canal de audio (bus) para enrutar voces a dispositivos de salida concretos.", + "action": "Crear" + }, + "editDialog": { + "title": "Editar canal", + "description": "Actualiza los ajustes del canal y las asignaciones de voz." + } + }, + "profileForm": { + "createTitle": "Crear voz", + "editTitle": "Editar voz", + "createDescription": "Crea un nuevo perfil de voz a partir de una muestra de audio o de una voz integrada.", + "editDescription": "Actualiza los detalles de tu perfil de voz y gestiona las muestras.", + "draftRestored": "Borrador restaurado", + "discard": "Descartar", + "source": { + "clone": "Clonar desde audio", + "builtin": "Voz integrada" + }, + "builtin": { + "hint": "Elige una voz prediseñada. Estas no requieren una muestra de audio.", + "badge": "Voz integrada", + "note": "Este perfil usa una voz integrada. La voz no se puede cambiar después de crearlo." + }, + "sampleTabs": { + "upload": "Subir", + "record": "Grabar", + "system": "Audio del sistema" + }, + "fields": { + "engine": "Motor", + "voice": "Voz", + "name": "Nombre", + "namePlaceholder": "Mi voz", + "descriptionLabel": "Descripción (opcional)", + "descriptionPlaceholder": "Describe esta voz…", + "language": "Idioma", + "referenceText": "Texto de referencia", + "referenceTextPlaceholder": "Introduce el texto exacto hablado en el audio…", + "defaultEngine": "Motor predeterminado", + "noPreference": "Sin preferencia", + "defaultEngineHint": "Selecciona automáticamente este motor cuando se elige el perfil.", + "defaultEffects": "Efectos predeterminados", + "defaultEffectsHint": "Efectos aplicados automáticamente a todas las nuevas generaciones con esta voz.", + "personalityLabel": "Personalidad", + "personalityPlaceholder": "p. ej. \"un pirata gruñón que solo habla con metáforas náuticas\"", + "personalityHint": "Quién es esta voz y cómo habla. Impulsa el botón Redactar y el interruptor de reescritura en personaje de la página de generación. Déjalo en blanco para ocultar ambos." + }, + "avatar": { + "alt": "Vista previa del avatar" + }, + "actions": { + "saving": "Guardando…", + "saveChanges": "Guardar cambios", + "createProfile": "Crear perfil" + }, + "validation": { + "nameRequired": "El nombre es obligatorio", + "referenceRequired": "El texto de referencia es obligatorio al añadir una muestra", + "sampleRequired": "La muestra de audio es obligatoria", + "referenceTextRequired": "El texto de referencia es obligatorio", + "audioTooLong": "El audio es demasiado largo ({{duration}}). La duración máxima es {{max}}.", + "audioFailed": "Error al validar el archivo de audio. Prueba con otro archivo." + }, + "toast": { + "recordingComplete": "Grabación completada", + "recordingCompleteDescription": "El audio se ha grabado correctamente.", + "recordingError": "Error de grabación", + "systemAudioCaptured": "Audio del sistema capturado", + "systemAudioCapturedDescription": "El audio se ha capturado correctamente.", + "systemAudioError": "Error de captura de audio del sistema", + "transcribeFailed": "Error de transcripción", + "transcribeFailedFallback": "Error al transcribir el audio", + "noFile": "Ningún archivo seleccionado", + "noFileDescription": "Selecciona primero un archivo de audio.", + "invalidFile": "Tipo de archivo no válido", + "invalidImageFormat": "Selecciona un archivo de imagen (PNG, JPG o WebP)", + "fileTooLarge": "Archivo demasiado grande", + "imageTooLargeDescription": "La imagen debe ocupar menos de 5 MB", + "avatarRemoved": "Avatar eliminado", + "avatarRemovedDescription": "La imagen del avatar se ha eliminado correctamente.", + "avatarRemoveFailed": "Error al eliminar el avatar", + "avatarUploadFailed": "Error al subir el avatar", + "avatarUploadFailedFallback": "Error al subir el avatar", + "effectsUpdateFailed": "Error al actualizar los efectos", + "effectsUpdateFailedFallback": "Error al guardar la cadena de efectos", + "voiceUpdated": "Voz actualizada", + "voiceUpdatedDescription": "\"{{name}}\" se ha actualizado correctamente.", + "noVoiceSelected": "Ninguna voz seleccionada", + "noVoiceSelectedDescription": "Selecciona una voz integrada.", + "profileCreated": "Perfil creado", + "profileCreatedBuiltin": "\"{{name}}\" se ha creado con una voz integrada.", + "profileCreatedSample": "\"{{name}}\" se ha creado con una muestra.", + "sampleRequired": "Muestra de audio obligatoria", + "sampleRequiredDescription": "Proporciona una muestra de audio para crear el perfil de voz.", + "referenceTextRequired": "Texto de referencia obligatorio", + "referenceTextRequiredDescription": "Proporciona el texto de referencia para la muestra de audio.", + "invalidAudio": "Archivo de audio no válido", + "invalidAudioDescription": "La duración del audio es {{duration}}, pero el máximo es {{max}}.", + "validationError": "Error de validación", + "rollbackFailed": "Error al revertir", + "rollbackFailedDescription": "El perfil creado no se pudo eliminar tras el fallo de subida de la muestra.", + "profileRolledBack": "El perfil se ha revertido.", + "sampleFailed": "Error al añadir la muestra", + "sampleFailedDescription": "Error al añadir la muestra.", + "sampleFailedRolledBack": "Error al añadir la muestra. El perfil se ha revertido.", + "saveFailed": "Error al guardar el perfil" + } + }, + "audioSample": { + "chooseFile": "Elegir archivo", + "uploadHint": "Haz clic para elegir un archivo o arrástralo y suéltalo. Duración máxima: 30 segundos.", + "fileUploaded": "Archivo subido", + "fileLabel": "Archivo: {{name}}", + "play": "Reproducir", + "pause": "Pausar", + "transcribe": "Transcribir", + "transcribing": "Transcribiendo…", + "remove": "Quitar", + "startRecording": "Iniciar grabación", + "recordHint": "Haz clic para empezar a grabar. Duración máxima: 30 segundos.", + "stopRecording": "Detener grabación", + "remaining": "{{time}} restantes", + "recordingComplete": "Grabación completada", + "recordAgain": "Grabar de nuevo", + "startCapture": "Iniciar captura", + "systemHint": "Captura el audio de tu sistema. Duración máxima: 30 segundos.", + "stopCapture": "Detener captura", + "captureComplete": "Captura completada", + "captureAgain": "Capturar de nuevo" + }, + "sampleList": { + "loading": "Cargando muestras…", + "empty": { + "title": "Aún no hay muestras", + "hint": "Añade tu primera muestra de audio para empezar" + }, + "editing": "Editando transcripción", + "placeholder": "Introduce el texto de referencia…", + "saving": "Guardando…", + "editTranscription": "Editar transcripción", + "deleteSample": "Eliminar muestra", + "addSample": "Añadir muestra", + "note": "Nota: una sola muestra de 30 segundos es el punto ideal. La calidad puede disminuir con varias muestras. En una futura actualización las muestras podrían ser intercambiables y etiquetarse para distintos estilos de la misma voz.", + "deleteDialog": { + "title": "Eliminar muestra", + "description": "¿Seguro que quieres eliminar esta muestra de audio? Esta acción no se puede deshacer.", + "deleting": "Eliminando…" + }, + "player": { + "play": "Reproducir muestra", + "pause": "Pausar muestra", + "stop": "Detener", + "stopAria": "Detener reproducción", + "position": "Posición de reproducción de la muestra", + "positionValue": "{{current}} de {{total}}" + }, + "toast": { + "invalidText": "Texto no válido", + "invalidTextDescription": "El texto de referencia no puede estar vacío.", + "updated": "Muestra actualizada", + "updatedDescription": "El texto de referencia se ha actualizado correctamente.", + "updateFailed": "Error al actualizar", + "updateFailedFallback": "Error al actualizar la muestra" + } + }, + "profiles": { + "card": { + "noDescription": "Sin descripción", + "designed": "diseñada", + "export": "Exportar perfil", + "edit": "Editar perfil", + "delete": "Eliminar perfil", + "selectLabel": "{{name}}, {{language}}. Seleccionar como voz para la generación.", + "selectLabelSelected": "{{name}}, {{language}}. Seleccionada como voz para la generación." + }, + "list": { + "errorLoading": "Error al cargar los perfiles: {{message}}", + "empty": "Aún no hay perfiles de voz. Crea tu primer perfil para empezar.", + "createVoice": "Crear voz", + "unsupportedNote": "Solo se pueden seleccionar los perfiles de voz compatibles con el modelo actual." + }, + "deleteDialog": { + "title": "Eliminar perfil", + "body": "¿Seguro que quieres eliminar \"{{name}}\"? Esta acción no se puede deshacer.", + "deleting": "Eliminando…" + } + }, + "effects": { + "title": "Efectos", + "newPreset": "Nuevo preajuste", + "noDescription": "Sin descripción", + "placeholder": "Selecciona un preajuste o crea uno nuevo", + "effectCount_one": "{{count}} efecto", + "effectCount_other": "{{count}} efectos", + "sections": { + "builtin": "Integrados", + "custom": "Personalizados", + "new": "Nuevo" + }, + "badge": { + "builtin": "integrado" + }, + "unsaved": { + "title": "Preajuste sin guardar", + "hint": "Configura los efectos en el panel de la derecha." + }, + "detail": { + "newTitle": "Nuevo preajuste", + "editTitle": "Editar preajuste", + "savePreset": "Guardar preajuste", + "saveAsCustom": "Guardar como personalizado", + "saving": "Guardando…", + "deleting": "Eliminando…" + }, + "fields": { + "name": "Nombre", + "namePlaceholder": "Mi preajuste…", + "description": "Descripción", + "descriptionPlaceholder": "Describe qué hace este preajuste…" + }, + "preview": { + "label": "Vista previa", + "button": "Vista previa", + "processing": "Procesando…", + "hint": "La vista previa aplica los efectos a la versión limpia sin guardar." + }, + "saveAs": { + "title": "Guardar como preajuste personalizado", + "description": "Crea un nuevo preajuste personalizado basado en la cadena de efectos actual.", + "suggestedName": "{{name}} (copia)" + }, + "toast": { + "saved": "Preajuste guardado", + "createdDescription": "\"{{name}}\" se ha creado.", + "updated": "Preajuste actualizado", + "deleted": "Preajuste eliminado", + "saveFailed": "Error al guardar", + "deleteFailed": "Error al eliminar", + "previewFailed": "Error en la vista previa", + "nameRequired": "Nombre obligatorio" + }, + "chain": { + "loadPreset": "Cargar preajuste…", + "addEffect": "Añadir efecto…", + "clear": "Limpiar", + "enable": "Activar", + "disable": "Desactivar", + "remove": "Quitar" + }, + "types": { + "chorus": { + "label": "Coro / Flanger", + "params": { + "rate_hz": "Velocidad del LFO (Hz)", + "depth": "Profundidad de modulación", + "feedback": "Cantidad de realimentación", + "centre_delay_ms": "Retardo central (ms)", + "mix": "Mezcla húmedo/seco" + } + }, + "reverb": { + "label": "Reverberación", + "params": { + "room_size": "Tamaño de sala", + "damping": "Amortiguación de altas frecuencias", + "wet_level": "Nivel húmedo", + "dry_level": "Nivel seco", + "width": "Anchura estéreo" + } + }, + "delay": { + "label": "Retardo", + "params": { + "delay_seconds": "Tiempo de retardo (segundos)", + "feedback": "Cantidad de realimentación", + "mix": "Mezcla húmedo/seco" + } + }, + "compressor": { + "label": "Compresor", + "params": { + "threshold_db": "Umbral (dB)", + "ratio": "Relación de compresión", + "attack_ms": "Tiempo de ataque (ms)", + "release_ms": "Tiempo de liberación (ms)" + } + }, + "gain": { + "label": "Ganancia", + "params": { + "gain_db": "Ganancia (dB)" + } + }, + "highpass": { + "label": "Filtro paso alto", + "params": { + "cutoff_frequency_hz": "Frecuencia de corte (Hz)" + } + }, + "lowpass": { + "label": "Filtro paso bajo", + "params": { + "cutoff_frequency_hz": "Frecuencia de corte (Hz)" + } + }, + "pitch_shift": { + "label": "Cambio de tono", + "params": { + "semitones": "Semitonos a desplazar" + } + } + }, + "builtinPresets": { + "Robotic": { + "name": "Robótica", + "description": "Voz robótica metálica (flanger con LFO lento y realimentación alta)" + }, + "Radio": { + "name": "Radio", + "description": "Voz fina de radio AM con filtrado paso banda y compresión ligera" + }, + "Echo Chamber": { + "name": "Cámara de eco", + "description": "Reverberación amplia con eco de cola" + }, + "Deep Voice": { + "name": "Voz grave", + "description": "Tono más grave con calidez añadida" + } + } + }, + "stories": { + "title": "Historias", + "newStory": "Nueva historia", + "loading": "Cargando historias…", + "searchPlaceholder": "Buscar historias…", + "empty": { + "title": "Aún no hay historias", + "hint": "Crea tu primera historia para empezar", + "noMatches": "Ninguna historia coincide con \"{{query}}\"" + }, + "row": { + "itemCount_one": "{{count}} elemento", + "itemCount_other": "{{count}} elementos", + "ariaLabel": "Historia {{name}}, {{count}} elementos, {{updated}}", + "actionsLabel": "Acciones para {{name}}" + }, + "createDialog": { + "title": "Crear nueva historia", + "description": "Crea una nueva historia para organizar tus generaciones de voz en conversaciones.", + "action": "Crear", + "creating": "Creando…" + }, + "editDialog": { + "title": "Editar historia", + "description": "Actualiza el nombre y la descripción de la historia.", + "saving": "Guardando…" + }, + "deleteDialog": { + "title": "¿Estás seguro?", + "description": "Esto eliminará permanentemente la historia y todos sus elementos. Esta acción no se puede deshacer.", + "deleting": "Eliminando…" + }, + "fields": { + "name": "Nombre", + "namePlaceholder": "Mi historia", + "descriptionLabel": "Descripción (opcional)", + "descriptionPlaceholder": "Una conversación entre…" + }, + "toast": { + "nameRequired": "Nombre obligatorio", + "nameRequiredDescription": "Introduce un nombre para la historia", + "created": "Historia creada", + "createdDescription": "\"{{name}}\" se ha creado", + "createFailed": "Error al crear la historia", + "updateFailed": "Error al actualizar la historia", + "deleteFailed": "Error al eliminar la historia" + } + }, + "storyContent": { + "selectStory": { + "title": "Selecciona una historia", + "hint": "Elige una historia de la lista para ver su contenido" + }, + "loading": "Cargando historia…", + "notFound": { + "title": "Historia no encontrada", + "hint": "No se pudo cargar la historia seleccionada" + }, + "generatingCount_one": "Generando {{count}} audio", + "generatingCount_other": "Generando {{count}} audios", + "add": "Añadir", + "searchPlaceholder": "Buscar por nombre o transcripción…", + "searchNoMatches": "No se encontraron generaciones coincidentes", + "searchNoAvailable": "No hay generaciones disponibles", + "exportAudio": "Exportar audio", + "empty": { + "title": "No hay elementos en esta historia", + "hint": "Genera voz con el cuadro de abajo para añadir elementos" + }, + "itemActions": { + "playFromHere": "Reproducir desde aquí", + "regenerate": "Regenerar", + "removeFromStory": "Quitar de la historia" + }, + "importAudio": "Importar audio…", + "importing": "Importando…", + "dropToImport": "Suelta el audio para importar", + "toast": { + "removeFailed": "Error al quitar el elemento", + "reorderFailed": "Error al reordenar los elementos", + "exportFailed": "Error al exportar el audio", + "addFailed": "Error al añadir la generación", + "regenerateFailed": "Error al regenerar", + "importFailed": "Error al importar el audio" + } + }, + "history": { + "empty": "Aún no hay generaciones de voz…", + "actions": { + "menu": "Acciones", + "play": "Reproducir", + "exportAudio": "Exportar audio", + "exportPackage": "Exportar paquete", + "applyEffects": "Aplicar efectos", + "regenerate": "Regenerar" + }, + "deleteDialog": { + "title": "Eliminar generación", + "body": "¿Seguro que quieres eliminar esta generación de \"{{name}}\"? Esta acción no se puede deshacer.", + "deleting": "Eliminando…" + }, + "clearFailedDialog": { + "title": "Borrar generaciones fallidas", + "body_one": "Esto eliminará permanentemente {{count}} generación fallida de tu historial. No se puede deshacer.", + "body_other": "Esto eliminará permanentemente {{count}} generaciones fallidas de tu historial. No se puede deshacer.", + "clearing": "Borrando…", + "clearAll": "Borrar todo" + }, + "importDialog": { + "title": "Importar generación", + "body": "Importa la generación de \"{{name}}\". Se añadirá a tu historial.", + "importing": "Importando…", + "action": "Importar" + }, + "effectsDialog": { + "title": "Aplicar efectos", + "body": "Configura los efectos de posprocesado que se aplicarán a esta generación. Se creará una nueva versión.", + "sourceLabel": "Origen", + "sourcePlaceholder": "Selecciona la versión de origen", + "apply": "Aplicar", + "applying": "Aplicando…" + } + }, + "generation": { + "placeholder": { + "storyWithEffects": "Genera voz para \"{{name}}\"… (escribe / para efectos)", + "story": "Genera voz para \"{{name}}\"…", + "profile": "Genera voz con {{name}}…", + "effectsHint": "Escribe / para efectos como [laugh], [sigh]…", + "selectVoice": "Selecciona un perfil de voz arriba…" + }, + "button": { + "generate": "Generar voz", + "generating": "Generando…", + "selectFirst": "Selecciona primero un perfil de voz" + }, + "instruct": { + "show": "Mostrar instrucciones de interpretación", + "hide": "Ocultar instrucciones de interpretación", + "tooltip": "Instrucciones de interpretación (tono, emoción, ritmo)", + "placeholder": "Instrucciones de interpretación — p. ej. Habla despacio y con calidez, Con autoridad y claridad…" + }, + "voiceSelector": { + "placeholder": "Selecciona una voz…" + }, + "effects": { + "none": "Sin efectos", + "profileDefault": "Predeterminado del perfil" + }, + "compose": { + "tooltip": "Redactar", + "ariaLabel": "Redactar una frase en personaje", + "failedTitle": "Error al redactar", + "failedDescription": "No se pudo generar texto a partir de esta personalidad." + }, + "persona": { + "tooltipActive": "Hablando en personaje", + "tooltipInactive": "Hablar en personaje", + "ariaLabelActive": "Hablando en personaje", + "ariaLabelInactive": "Hablar en personaje" + } + }, + "main": { + "importVoice": "Importar voz", + "createVoice": "Crear voz", + "import": { + "invalidTitle": "Tipo de archivo no válido", + "invalidDescription": "Selecciona un archivo .voicebox.zip válido", + "successTitle": "Perfil importado", + "successDescription": "Perfil de voz importado correctamente", + "failedTitle": "Error al importar el perfil", + "dialogTitle": "Importar perfil", + "dialogDescription": "Importa el perfil de \"{{name}}\". Se creará un nuevo perfil con todas las muestras.", + "importing": "Importando…", + "action": "Importar" + } + }, + "settings": { + "tabs": { + "general": "General", + "generation": "Generación", + "captures": "Capturas", + "mcp": "MCP", + "gpu": "GPU", + "logs": "Registros", + "changelog": "Cambios", + "about": "Acerca de" + }, + "language": { + "label": "Idioma", + "description": "Elige el idioma de la interfaz de Voicebox." + }, + "theme": { + "label": "Tema", + "description": "Adáptalo a tu sistema o elige una apariencia fija clara u oscura.", + "options": { + "system": "Sistema", + "light": "Claro", + "dark": "Oscuro" + } + }, + "general": { + "docs": { + "title": "Leer la documentación" + }, + "discord": { + "title": "Únete al Discord", + "subtitle": "Consigue ayuda y comparte voces" + }, + "serverUrl": { + "title": "URL del servidor", + "description": "La dirección de tu servidor backend de Voicebox.", + "invalidUrl": "Introduce una URL válida", + "updatedTitle": "URL del servidor actualizada", + "updatedDescription": "Conectado a {{url}}" + }, + "keepServerRunning": { + "title": "Mantener el servidor en marcha al cerrar la app", + "description": "El servidor seguirá ejecutándose en segundo plano después de cerrar la app.", + "failedTitle": "Error al actualizar el ajuste", + "failedDescription": "No se pudo sincronizar el ajuste con el backend.", + "updatedTitle": "Ajuste actualizado", + "runningDescription": "El servidor seguirá en marcha al cerrar la app", + "stoppedDescription": "El servidor se detendrá al cerrar la app" + }, + "networkAccess": { + "title": "Permitir acceso de red", + "description": "Hace que el servidor sea accesible desde otros dispositivos de tu red. Reinicia la app después de cambiarlo.", + "updatedTitle": "Ajuste actualizado", + "enabled": "Acceso de red habilitado. Reinicia la app para aplicarlo.", + "disabled": "Acceso de red deshabilitado. Reinicia la app para aplicarlo." + }, + "connection": { + "connecting": "Conectando", + "offline": "Sin conexión", + "online": "En línea" + }, + "updates": { + "title": "Actualizaciones de la app", + "devSuffix": " (dev)", + "devMode": { + "title": "Modo de desarrollo", + "description": "Las actualizaciones automáticas están deshabilitadas en el modo de desarrollo." + }, + "check": { + "title": "Buscar actualizaciones", + "available": "Versión {{version}} disponible", + "checking": "Comprobando…", + "upToDate": "Estás al día", + "button": "Comprobar" + }, + "error": "Error de actualización", + "download": { + "title": "Actualizar a {{version}}", + "description": "Descarga e instala la última versión.", + "button": "Descargar" + }, + "downloading": "Descargando actualización…", + "ready": { + "title": "Actualización lista para instalar", + "description": "Se ha descargado la versión {{version}}. Reinicia para completar.", + "button": "Reiniciar ahora" + } + }, + "api": { + "title": "Acceso a la API", + "description": "Integra Voicebox en tu flujo de trabajo mediante la API REST en {{url}}", + "viewReference": "Ver la referencia completa de la API", + "endpoints": { + "generate": "Generar voz", + "health": "Estado del servidor", + "profiles": "Listar voces", + "history": "Generaciones anteriores" + } + } + }, + "generation": { + "title": "Generación", + "description": "Controles para la generación de texto largo. Estos ajustes se aplican a todos los motores.", + "chunkLimit": { + "title": "Límite de fragmentación automática", + "description": "El texto largo se divide en fragmentos en los límites de las frases. Valores más bajos pueden mejorar la calidad en salidas largas.", + "value": "{{chars}} caracteres" + }, + "crossfade": { + "title": "Fundido cruzado entre fragmentos", + "description": "Mezcla el audio entre fragmentos para suavizar las transiciones. Pon 0 para un corte seco.", + "cut": "Corte", + "ms": "{{ms}} ms" + }, + "normalize": { + "title": "Normalizar audio", + "description": "Ajusta el volumen de salida a un nivel uniforme entre generaciones." + }, + "autoplay": { + "title": "Reproducción automática al generar", + "description": "Reproduce automáticamente el audio cuando se completa una generación." + }, + "folder": { + "title": "Carpeta de generaciones", + "description": "Dónde se guardan en disco los archivos de audio generados.", + "open": "Abrir" + }, + "sidebar": { + "aboutTitle": "Acerca de la generación de voz", + "aboutBody": "Clona una voz a partir de una muestra corta y luego genera voz con cualquier voz en cualquier idioma. Lleva TTS a agentes de IA, videojuegos, pódcasts o narración de formato largo.", + "differencesTitle": "Qué la diferencia", + "clone": { + "title": "Clona cualquier voz en segundos.", + "body": "Bastan unos segundos de audio de referencia. Compatible con varias muestras para mayor calidad cuando lo necesites." + }, + "engines": { + "title": "Siete motores, 23 idiomas.", + "body": "Elige el equilibrio que encaje: calidad, velocidad o cobertura multilingüe." + }, + "agentReady": { + "title": "Listo para agentes.", + "body": "API REST con control por perfil: dale a cualquier IA una voz que hayas clonado." + } + } + }, + "captures": { + "dictation": { + "title": "Dictado", + "description": "Captura desde cualquier parte de tu equipo con un atajo global.", + "globalShortcut": { + "title": "Atajo global", + "description": "Mantén pulsado el atajo para grabar desde cualquier parte de tu equipo. Suelta para transcribir." + }, + "pushToTalk": { + "title": "Atajo de pulsar para hablar", + "description": "Mantén estas teclas pulsadas en cualquier parte de tu sistema para grabar. Suelta para detener y transcribir.", + "change": "Cambiar" + }, + "toggle": { + "title": "Atajo de alternancia", + "description": "Pulsa una vez para iniciar una grabación manos libres. Pulsa de nuevo para detener. Normalmente, pulsar para hablar más Espacio.", + "change": "Cambiar" + }, + "chordPicker": { + "pttTitle": "Definir el atajo de pulsar para hablar", + "pttDescription": "Mantén pulsadas las teclas que quieras usar, luego suéltalas y haz clic en Guardar. La insignia del modificador derecho muestra si una tecla es la variante izquierda o derecha.", + "toggleTitle": "Definir el atajo de alternancia", + "toggleDescription": "Mantén pulsadas las teclas que quieras usar, luego suéltalas y haz clic en Guardar. Elige algo distinto de tu combinación de pulsar para hablar." + }, + "preview": { + "title": "Vista previa", + "description": "Lo que aparece en pantalla mientras mantienes pulsado el atajo." + }, + "copyToClipboard": { + "title": "Copiar la transcripción al portapapeles", + "description": "La transcripción ya limpia llega a tu portapapeles cuando termina la captura." + }, + "autoPaste": { + "title": "Pegar automáticamente en el campo de texto activo", + "description": "Si hay un campo de texto activo en otra app, pega directamente en él. Voicebox guarda y restaura lo que hubiera en tu portapapeles." + } + }, + "transcription": { + "title": "Transcripción", + "description": "Elige qué modelo de voz a texto se ejecuta en tus capturas.", + "model": { + "title": "Modelo de transcripción", + "description": "Whisper viene incluido con Voicebox y se ejecuta enteramente en tu equipo.", + "base": "Whisper Base · 74M · {{tail}}", + "small": "Whisper Small · 244M · {{tail}}", + "medium": "Whisper Medium · 769M · {{tail}}", + "large": "Whisper Large · 1.5B · {{tail}}", + "turbo": "Whisper Turbo · Pruned Large v3 · {{tail}}", + "tail": { + "fast": "Rápido", + "balanced": "Equilibrado", + "higher": "Mayor precisión", + "best": "Mejor precisión", + "nearBest": "Casi la mejor, rápido" + } + }, + "language": { + "title": "Idioma", + "description": "La detección automática funciona para la mayoría de las capturas. Fíjalo si siempre hablas el mismo idioma.", + "auto": "Detección automática", + "en": "Inglés", + "es": "Español", + "fr": "Francés", + "de": "Alemán", + "ja": "Japonés", + "zh": "Chino", + "hi": "Hindi" + }, + "archive": { + "title": "Archivar audio", + "description": "Conserva la grabación original junto a cada transcripción." + } + }, + "refinement": { + "title": "Refinamiento", + "description": "Opcionalmente, ejecuta un LLM local sobre las transcripciones para limpiar muletillas, puntuación y autocorrecciones.", + "auto": { + "title": "Refinar transcripciones automáticamente", + "description": "Se ejecuta después de cada captura. Aún puedes alternar entre en bruto y refinada en la pestaña Capturas." + }, + "model": { + "title": "Modelo de refinamiento", + "description": "Los modelos más grandes son más lentos, pero manejan mejor las autocorrecciones sutiles y el vocabulario técnico.", + "size06": "Qwen3 · 0.6B · 400 MB · {{tail}}", + "size17": "Qwen3 · 1.7B · 1.1 GB · {{tail}}", + "size40": "Qwen3 · 4B · 2.5 GB · {{tail}}", + "tail": { + "veryFast": "Muy rápido", + "fast": "Rápido", + "fullQuality": "Calidad completa" + } + }, + "smartCleanup": { + "title": "Limpieza inteligente", + "description": "Elimina muletillas (em, eh, o sea), restaura la puntuación y corrige las mayúsculas sin reformular." + }, + "selfCorrection": { + "title": "Eliminar autocorrecciones", + "description": "Cuando cambias de idea a mitad de frase (\"en realidad, no...\", \"espera, quería decir...\"), descarta la parte retractada y conserva la intención final." + }, + "preserveTechnical": { + "title": "Conservar términos técnicos", + "description": "Mantén los identificadores de código, nombres de comandos y siglas exactamente como se dicen. Actívalo cuando dictes en un prompt de código." + } + }, + "playback": { + "title": "Reproducción", + "description": "Voz predeterminada para la acción \"Reproducir como\" en la pestaña Capturas.", + "defaultVoice": { + "title": "Voz predeterminada", + "description": "Se usa cuando haces clic en Reproducir como sin elegir antes una voz. Puedes cambiarla en cada captura.", + "noClonedVoices": "Aún no hay voces clonadas", + "noneSelected": "Ninguna seleccionada", + "clonedVoices": "Voces clonadas" + } + }, + "storage": { + "title": "Almacenamiento", + "description": "Las capturas se guardan como archivos emparejados de audio y transcripción en tu directorio de datos de Voicebox.", + "retention": { + "title": "Retención", + "description": "Cuánto tiempo conservar las capturas. Se aplica tanto al audio como a las transcripciones.", + "forever": "Conservar siempre", + "d90": "90 días", + "d30": "30 días", + "d7": "7 días" + }, + "folder": { + "title": "Carpeta de capturas", + "description": "Dónde se almacenan en disco el audio y las transcripciones de las capturas.", + "open": "Abrir" + } + }, + "sidebar": { + "aboutTitle": "Acerca de Capturas", + "aboutBody": "Mantén pulsado un atajo en cualquier parte de tu equipo, habla, y Voicebox convierte tu voz en texto. Reprodúcelo con cualquier voz clonada, pégalo en cualquier app o canalízalo a tu agente de programación.", + "differencesTitle": "Qué lo diferencia", + "local": { + "title": "Totalmente local.", + "body": "Whisper y el LLM de refinamiento se ejecutan en tu hardware. Sin nube, sin cuentas; tu voz nunca sale del equipo." + }, + "playAs": { + "title": "Reproduce con cualquier voz.", + "body": "Las transcripciones se pueden leer con cualquier perfil que hayas clonado." + }, + "crossPlatform": { + "title": "Multiplataforma.", + "body": "El mismo atajo, el mismo flujo en macOS, Windows y Linux." + }, + "windowsCaveat": { + "title": "Aviso en Windows", + "body": "El atajo no se activará mientras Voicebox o cualquier app ejecutada como administrador esté en primer plano. Estamos en ello." + } + } + }, + "mcp": { + "install": { + "title": "Instalar en tu agente", + "description": "Voicebox expone un servidor MCP local siempre que la app está abierta. Pega uno de estos fragmentos en la configuración MCP de tu agente.", + "http": { + "title": "HTTP (recomendado)", + "description": "Para clientes que hablan MCP por HTTP: Claude Code, Cursor, Windsurf, VS Code." + }, + "claudeCode": { + "title": "Comando de una línea para Claude Code", + "description": "Se registra mediante la CLI de Claude Code." + }, + "stdio": { + "title": "Stdio (alternativa)", + "description": "Para clientes que solo generan procesos stdio. El binario adaptador viene con la app." + }, + "copy": "Copiar", + "copied": "Copiado" + }, + "defaultVoice": { + "title": "Voz predeterminada", + "description": "Se usa cuando un agente llama a voicebox.speak sin un perfil específico y no tiene una vinculación por cliente.", + "label": "Voz de reproducción predeterminada", + "labelHint": "Compartida con el desplegable 'Reproducir como voz' de la pestaña Capturas: una voz predeterminada para la reproducción pasiva.", + "none": "(ninguna)" + }, + "bindings": { + "title": "Voz por agente", + "description": "Vincula agentes concretos a voces concretas para saber quién habla sin mirar. El agente se identifica mediante la cabecera X-Voicebox-Client-Id (o la variable de entorno VOICEBOX_CLIENT_ID para stdio).", + "empty": "Aún no hay vinculaciones. Añade una abajo y luego configura tu cliente MCP para que envíe el X-Voicebox-Client-Id correspondiente.", + "lastSeen": "visto por última vez {{when}}", + "lastSeenTitle": "Visto por última vez {{when}}", + "neverConnected": "nunca conectado", + "defaultOption": "(predeterminado)", + "removeAria": "Quitar vinculación de {{client}}", + "add": { + "title": "Añadir una vinculación", + "clientIdPlaceholder": "id de cliente (p. ej. claude-code)", + "labelPlaceholder": "etiqueta (opcional)", + "action": "Añadir vinculación" + } + }, + "sidebar": { + "aboutTitle": "Acerca de MCP", + "aboutBody": "El Model Context Protocol permite que tu agente de programación con IA —Claude Code, Cursor, Windsurf— llame a las herramientas de Voicebox. Habla con una voz clonada, transcribe audio, explora capturas.", + "toolsTitle": "Herramientas disponibles", + "tools": { + "speak": "Pronuncia texto con un perfil de voz.", + "transcribe": "Whisper STT sobre un clip.", + "listCaptures": "Dictados / grabaciones recientes.", + "listProfiles": "Perfiles de voz disponibles." + }, + "postSpeak": "También expuesto como POST /speak para scripts de shell, ACP, A2A." + } + }, + "gpu": { + "cpuOnly": "Solo CPU", + "vramUsed": "{{mb}} MB de VRAM", + "noAcceleration": "No se detectó aceleración por GPU", + "active": "Activa", + "cuda": { + "title": "Backend CUDA", + "description": "Aceleración por GPU NVIDIA mediante un backend CUDA descargable.", + "downloading": "Descargando el backend CUDA…", + "downloadingShort": "Descargando…", + "updating": "Actualizando…", + "activeTitle": "Backend CUDA activo" + }, + "restart": { + "ready": "Servidor reiniciado correctamente", + "waiting": "Reiniciando el servidor…", + "stopping": "Deteniendo el servidor…" + }, + "download": { + "title": "Descargar el backend CUDA", + "description": "Descarga de ~2.4 GB. Requiere una GPU NVIDIA compatible con CUDA.", + "button": "Descargar" + }, + "switchToCuda": { + "title": "Cambiar al backend CUDA", + "description": "El backend CUDA está descargado y listo. Reinicia para habilitarlo.", + "button": "Reiniciar" + }, + "switchToCpu": { + "title": "Cambiar al backend de CPU", + "description": "Deshabilita la aceleración por GPU. Puedes volver a descargar el backend de GPU más tarde.", + "button": "Cambiar" + }, + "remove": { + "title": "Quitar el backend CUDA", + "description": "Elimina el binario CUDA descargado para liberar espacio en disco.", + "button": "Quitar" + }, + "errors": { + "downloadFailed": "Error de descarga", + "downloadStart": "Error al iniciar la descarga", + "restartFailed": "Error al reiniciar", + "switchCpu": "Error al cambiar a CPU", + "deleteCuda": "Error al eliminar el backend CUDA", + "deleteRocm": "Error al eliminar el backend ROCm" + }, + "footer": "Voicebox detecta y usa automáticamente la mejor GPU disponible en tu sistema. En Macs con Apple Silicon, el backend MLX se ejecuta de forma nativa en el Neural Engine y la GPU mediante Metal Performance Shaders (MPS), sin configuración adicional. En Windows, puedes descargar backends opcionales CUDA (NVIDIA) o ROCm (AMD) para inferencia acelerada por hardware. Intel XPU y DirectML también son compatibles cuando están disponibles a través de PyTorch. Cuando no se detecta ninguna GPU, Voicebox recurre a la CPU: todos los motores siguen funcionando, solo que más despacio.", + "activeBackend": { + "description": "La aceleración por GPU está habilitada actualmente." + }, + "rocm": { + "title": "Backend AMD ROCm", + "activeTitle": "Backend ROCm activo", + "description": "Aceleración por GPU AMD mediante un backend ROCm descargable.", + "downloading": "Descargando el backend ROCm…", + "downloadingShort": "Descargando…", + "updating": "Actualizando…" + }, + "downloadRocm": { + "title": "Descargar el backend AMD ROCm", + "description": "Descarga de ~2-3 GB. Requiere una GPU AMD Radeon compatible con ROCm.", + "button": "Descargar" + }, + "switchToRocm": { + "title": "Cambiar al backend ROCm", + "description": "El backend ROCm está descargado y listo. Reinicia para habilitarlo.", + "button": "Reiniciar" + }, + "removeRocm": { + "title": "Quitar el backend ROCm", + "description": "Elimina el binario ROCm descargado para liberar espacio en disco.", + "button": "Quitar" + } + }, + "logs": { + "title": "Registros del servidor", + "lineCount_one": "{{count}} línea", + "lineCount_other": "{{count}} líneas", + "scrollToBottom": "Desplazar al final", + "clear": "Limpiar", + "empty": "Aún no hay salida de registro.", + "devHint": "Los registros del servidor solo se capturan cuando la app gestiona el proceso del servidor (compilaciones de producción)." + }, + "changelog": { + "devBadge": "dev", + "showLess": "Mostrar menos", + "showMore": "Mostrar más" + }, + "about": { + "tagline": "El estudio de síntesis de voz de código abierto. Clona voces, genera voz, aplica efectos y crea apps con voz, todo ejecutándose localmente en tu equipo.", + "createdBy": "Creado por", + "buyCoffee": "Invítame a un café", + "license": "Con licencia MIT" + } + }, + "models": { + "title": "Modelos", + "subtitle": "Descarga y gestiona modelos de IA para la generación de voz y la transcripción", + "defaultName": "Modelo", + "unknownSize": "Tamaño desconocido", + "sections": { + "voiceGeneration": "Generación de voz", + "transcription": "Transcripción", + "languageModels": "Modelos de lenguaje" + }, + "status": { + "loaded": "Cargado" + }, + "storage": { + "location": "Ubicación de almacenamiento", + "open": "Abrir", + "change": "Cambiar", + "migrating": "Migrando…", + "reset": "Restablecer", + "pickerTitle": "Elegir la carpeta de almacenamiento de modelos" + }, + "progress": { + "connecting": "Conectando…", + "connectingHf": "Conectando con HuggingFace…" + }, + "problems": { + "title": "Problemas", + "clearAll": "Borrar todo", + "noDetails": "No hay detalles del error disponibles. Prueba a descargar de nuevo.", + "startedAt": "iniciado a las {{time}}" + }, + "detail": { + "loadingInfo": "Cargando información del modelo…", + "byAuthor": "por {{author}}", + "downloads": "Descargas", + "likes": "Me gusta", + "license": "Licencia", + "languagesCount": "{{count}} idiomas admitidos", + "languagesList": "Idiomas: {{list}}", + "onDisk": "{{size}} en disco" + }, + "actions": { + "download": "Descargar", + "retry": "Reintentar descarga", + "unload": "Liberar", + "unloading": "Liberando…", + "unloadFirst": "Libera el modelo de la memoria antes de eliminarlo", + "deleteModel": "Eliminar modelo" + }, + "deleteDialog": { + "title": "Eliminar modelo", + "body": "¿Seguro que quieres eliminar {{name}}?", + "sizeNote": "Esto liberará {{size}} de espacio en disco. Habrá que volver a descargar el modelo si quieres usarlo de nuevo.", + "deleting": "Eliminando…" + }, + "migrateDialog": { + "title": "¿Mover los modelos a la nueva ubicación?", + "description": "El servidor se apagará mientras se mueven los modelos a la nueva carpeta. Se reiniciará automáticamente cuando la migración termine.", + "action": "Mover modelos", + "preparing": "Preparando…", + "restartingServer": "Reiniciando el servidor…" + }, + "migrate": { + "title": "Moviendo modelos", + "offline": "El servidor está sin conexión mientras se mueven los modelos." + }, + "toast": { + "downloadFailed": "Error de descarga", + "cancelFailed": "Error al cancelar", + "cancelFailedDescription": "No se pudo cancelar la tarea de descarga.", + "deleted": "Modelo eliminado", + "deletedDescription": "{{name}} se ha eliminado correctamente.", + "deleteFailed": "Error al eliminar", + "unloaded": "Modelo liberado", + "unloadedDescription": "{{name}} se ha liberado de la memoria.", + "unloadFailed": "Error al liberar", + "openFolderFailed": "Error al abrir la carpeta del modelo", + "pickerFailed": "Error al abrir el selector de carpetas", + "resetToDefault": "Restablecido a la ubicación predeterminada. Reiniciando el servidor…", + "noModelsToMigrate": "No hay modelos que migrar", + "noModelsToMigrateDescription": "Descarga al menos un modelo antes de cambiar la ubicación de almacenamiento.", + "migrated": "Modelos movidos correctamente", + "migrationFailed": "Error de migración", + "migrationFailedGeneric": "Error al migrar los modelos", + "migrationConnectionLost": "Se perdió la conexión durante la migración" + } + } +} diff --git a/app/src/lib/utils/format.ts b/app/src/lib/utils/format.ts index 0d01a7fdd..6297332a0 100644 --- a/app/src/lib/utils/format.ts +++ b/app/src/lib/utils/format.ts @@ -1,5 +1,5 @@ import { formatDistance } from 'date-fns'; -import { ja, zhCN, zhTW, fr } from 'date-fns/locale'; +import { es, fr, ja, zhCN, zhTW } from 'date-fns/locale'; import i18n from '@/i18n'; export function formatDuration(seconds: number): string { @@ -10,6 +10,8 @@ export function formatDuration(seconds: number): string { function getDateLocale() { switch (i18n.language) { + case 'es': + return es; case 'ja': return ja; case 'zh-CN':