mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
a6ee0668c8
* feat(crossfade): add "trim silence between tracks" toggle
New persisted setting `crossfadeTrimSilence` (default off; existing
installs rehydrate off via the persist default-merge). Surfaced in
Settings -> Audio and in the crossfade popovers of the queue toolbar
and the mini-player.
Crossfade buttons now separate the two actions: left-click toggles
crossfade on/off, right-click opens the settings popover (seconds +
trim). Shared the mini popover positioning into useMiniAnchoredPopover
(now backing both volume and crossfade). Mini bridge carries
crossfadeSecs/crossfadeTrimSilence and gains mini:set-crossfade-secs /
mini:set-crossfade-trim-silence.
The actual silence-trimming playback behaviour is wired in a follow-up;
this commit only persists the user intent. i18n added across 9 locales.
* feat(crossfade): trim silence between tracks (waveform-driven)
Wire the actual silence-aware crossfade behind the (default-off)
crossfadeTrimSilence toggle. Detection is derived on the fly from the
cached 500-bin waveform + track duration — no new analysis pass or
cache fields.
- waveformSilence.ts: computeWaveformSilence(bins, duration) → lead/trail
silence + content bounds, using the peak curve, a low absolute cut and
a per-side cap. Unit-tested.
- A-tail (JS): handleAudioProgress advances the crossfade early, at
contentEnd - crossfadeSecs, when the current track ends in real
trailing silence, so the fade overlaps music. Guarded once per play
generation.
- B-head: audio_play gains an additive optional start_secs; the freshly
built source is try_seek'd past the next track's leading silence before
append, then seek_offset/samples_played are re-anchored so position is
content-relative. Non-seekable / cold sources degrade to today.
- Pre-buffer: crossfade next-track download + B-head probe moved to
crossfadePreload.ts with a fixed ~30 s budget before the track needs to
play (widened by trailing silence so the early advance keeps the
budget). Also fired right after a seek into the window so jumping near
the end still buffers in time.
Checks: tsc, vitest (store suite + new units), cargo test/clippy for
psysonic-audio.
* feat(crossfade): recommend hot cache for trim; probe B-head regardless
Add a "for reliable results, enable the Hot playback cache" note to the
trim-silence toggle description across all 9 locales — hot cache keeps
the next track on disk so it starts instantly past its lead silence.
Fix: the leading-silence probe (B-head) now runs even when hot cache is
on; only the redundant byte pre-download is gated on !hotCache. Without
this, enabling hot cache (the recommended setting) would have skipped the
probe and disabled leading-silence trimming.
* feat(crossfade): content-driven smart crossfade overlap
Smart crossfade derives the per-transition overlap purely from the
waveform envelopes — max(A outro fade, B intro rise) clamped 0.5–12s —
instead of the fixed crossfadeSecs ("work by fact"). The JS early
advance arms the computed overlap and audio_play applies it through a
new crossfade_secs_override (capping only this swap's fade); plain
loud→loud endings fall back to the engine crossfade at crossfadeSecs.
* feat(crossfade): "Crossfade | Smart crossfade" mode switch in UI
Replace the standalone "trim silence" toggle with a Crossfade / Smart
crossfade segmented control in settings and both crossfade popovers
(queue toolbar + mini-player). Classic Crossfade shows the seconds
slider; Smart crossfade is content-driven with no duration to set.
Adds smartCrossfade / smartCrossfadeDesc strings to all nine locales
and the "smart crossfade" search keyword.
* feat(crossfade): don't double-fade a track that already fades out
Decouple the outgoing track's fade-out from the incoming fade-in. When
A carries its own recorded fade-out (outroFadeA ≥ 1s and ≥ B's intro
rise), planCrossfadeTransition now sets outgoingFadeSec = 0; the engine
then skips A's TriggeredFadeOut so A keeps full gain and its recording
carries it down while B rises underneath — no more double attenuation
that made A vanish early and B blare in. Hard-cut endings still get an
engine fade over the overlap.
audio_play gains outgoing_fade_secs_override (Some(0) = ride A's own
fade), threaded via SinkSwapInputs.outgoing_fade_secs; the JS advance
arms it alongside the overlap.
* feat(crossfade): rename the smart crossfade mode to "AutoDJ"
User-facing rename of the content-driven crossfade mode from "Smart
crossfade" to "AutoDJ" across the settings segmented switch, the queue
and mini-player popovers, all nine locales, and the settings search
keywords. The underlying store flag (crossfadeTrimSilence) is unchanged.
* feat(crossfade): standard ~2s blend for hard loud→loud meetings
When AutoDJ trims a track's protective trailing silence and the loud
ending butts straight into a loud intro, neither edge fades, so the old
0.5s anti-click floor sounded like an abrupt cut. Use a standard ~2s
equal-power crossfade for that case (both edges analysed, nothing
fades); real fade-outs/buildups keep their longer content-driven span,
and the bare floor only survives when an envelope is missing.
* fix(crossfade): keep B's fade-in across the B-head start-offset seek
EqualPowerFadeIn::try_seek jumped straight to unity gain for any seek
≥100ms, which also hit the initial start-offset seek that skips the
incoming track's leading silence — so a crossfaded track with trimmed
lead silence popped in at full gain instead of fading in. Only skip the
fade-in for mid-playback seeks (sample_count > 0); a seek before any
audio has played keeps the fade-in.
* feat(crossfade): gate AutoDJ early fade on next-track readiness
The early, content-driven advance now fires only when the next track's
audio is actually available — in the engine RAM preload slot
(enginePreloadedTrackId) or local on disk (offline library, favourite-auto
or hot-cache ephemeral) — via isCrossfadeNextReady(). Analysis alone is
not enough: a cold, still-buffering stream would fade in over silence.
When B isn't ready the gen guard stays unset so it re-checks on later
ticks; if B never readies, the plain engine crossfade handles the
transition (graceful degrade) instead of a broken fade. A RAM preload
copy suffices — the full track need not be cached to disk.
* feat(crossfade): suppress engine auto-crossfade for AutoDJ + eager preload
With the hot cache off the readiness gate alone wasn't enough: the engine's
progress task autonomously fires its crossfade audio:ended ~crossfadeSecs
before the end, independently of JS, and would start a still-buffering next
track and fade over it — an audible jump.
- Engine: add autodj_suppress_autocrossfade flag (audio_set_autodj_suppress);
the progress task treats it like crossfade-off, so the early timer never
fires and audio:ended only comes from real source exhaustion / watchdog.
- JS drives the transition: set the flag when a content fade is pending
(wantEarly) and clear it for plain loud→loud / non-AutoDJ, so the normal
engine crossfade is preserved there. When the next track never readies, A
plays out and we degrade to a clean sequential start instead of a jump.
- audio_preload gains an `eager` flag; the crossfade/AutoDJ pre-buffer passes
it to skip the 8s start throttle so the RAM slot fills before the fade.
* docs(changelog): AutoDJ content-aware crossfade (PR #1122)
Add the 1.49.0 Added entry and the cucadmuh credits line for AutoDJ.
704 lines
48 KiB
TypeScript
704 lines
48 KiB
TypeScript
export const settings = {
|
||
title: 'Configuración',
|
||
language: 'Idioma',
|
||
languageEn: 'English',
|
||
languageDe: 'Deutsch',
|
||
languageEs: 'Español',
|
||
languageFr: 'Français',
|
||
languageNl: 'Nederlands',
|
||
languageNb: 'Norsk',
|
||
languageRu: 'Русский',
|
||
languageZh: '中文',
|
||
languageRo: 'Română',
|
||
font: 'Fuente',
|
||
fontHintOpenDyslexic: 'Compatible con dislexia · sin soporte para chino',
|
||
theme: 'Tema',
|
||
appearance: 'Apariencia',
|
||
servers: 'Servidores',
|
||
serverName: 'Nombre del Servidor',
|
||
serverUrl: 'URL del Servidor',
|
||
serverUrlPlaceholder: '192.168.1.100:4533 o https://music.example.com',
|
||
serverAlternateUrl: 'Segunda dirección (opcional)',
|
||
serverAlternateUrlPlaceholderPublic: 'https://music.example.com',
|
||
serverAlternateUrlPlaceholderLocal: '192.168.1.100:4533',
|
||
serverAlternateUrlHintAddPublic: 'Puedes añadir una dirección pública para acceder fuera de tu red doméstica.',
|
||
serverAlternateUrlHintAddLocal: 'Puedes añadir una dirección local para una conexión más rápida en casa.',
|
||
serverBothLanError: 'Ambas direcciones son locales: conserva una o añade una pública.',
|
||
dualAddressVerifying: 'Comprobando que ambas direcciones apuntan al mismo servidor…',
|
||
dualAddressMismatch: 'Las dos direcciones apuntan a servidores distintos. Comprueba la ortografía y vuelve a intentarlo.',
|
||
dualAddressInsufficient: 'No se pudo confirmar que ambas direcciones sean el mismo servidor. Elimina una dirección para continuar.',
|
||
dualAddressUnreachable: 'No se pudo contactar con {{host}}. Comprueba la dirección y tu red, y vuelve a intentarlo.',
|
||
urlRemigrationTitle: '¿Mover los datos de la biblioteca?',
|
||
urlRemigrationMessage: 'Cambiar la dirección moverá todos los datos de biblioteca, análisis y caché de carátulas de {{oldKey}} a {{newKey}}. Esto no se puede deshacer.',
|
||
urlRemigrationConfirm: 'Mover datos',
|
||
urlRemigrationProgress: 'Moviendo los datos de la biblioteca…',
|
||
urlRemigrationFailureInspect: 'No se pudo comprobar qué se movería. Vuelve a intentarlo o abre el registro de depuración.',
|
||
urlRemigrationFailureRun: 'Fallo al mover los datos de la biblioteca. Tus datos guardados están intactos — vuelve a intentarlo.',
|
||
urlRemigrationFailureCoverRename: 'Los datos de la biblioteca se movieron, pero los archivos de carátulas no se pudieron renombrar. Las carátulas se reconstruirán en el próximo acceso.',
|
||
shareUsesLocalUrl: 'Usar la dirección local en los enlaces compartidos',
|
||
shareUsesLocalUrlDesc: 'Afecta a las invitaciones de Orbit y a los enlaces de la biblioteca. Por defecto: dirección pública.',
|
||
serverUsername: 'Usuario',
|
||
serverPassword: 'Contraseña',
|
||
addServer: 'Agregar Servidor',
|
||
addServerTitle: 'Agregar Nuevo Servidor',
|
||
editServer: 'Editar',
|
||
editServerTitle: 'Editar Servidor',
|
||
useServer: 'Usar',
|
||
deleteServer: 'Eliminar',
|
||
noServers: 'No hay servidores guardados.',
|
||
serverActive: 'Activo',
|
||
serverVersionUnknown: 'Versión del servidor desconocida',
|
||
confirmDeleteServer: '¿Eliminar servidor "{{name}}"?',
|
||
serverConnecting: 'Conectando…',
|
||
serverConnected: '¡Conectado!',
|
||
serverFailed: 'Conexión fallida.',
|
||
testBtn: 'Probar Conexión',
|
||
testingBtn: 'Probando…',
|
||
serverCompatible: 'Diseñado para Navidrome. Otros servidores compatibles con Subsonic (Gonic, Airsonic, …) pueden funcionar con funcionalidad reducida, ya que Psysonic utiliza muchos endpoints específicos de la API de Navidrome.',
|
||
userMgmtTitle: 'Gestión de usuarios',
|
||
userMgmtDesc: 'Administra los usuarios de este servidor. Requiere privilegios de administrador.',
|
||
userMgmtNoAdmin: 'Necesitas privilegios de administrador para gestionar usuarios en este servidor.',
|
||
userMgmtLoadError: 'No se pudieron cargar los usuarios.',
|
||
userMgmtLoadFriendly: 'El servidor no respondió — suele ser algo puntual.',
|
||
userMgmtRetry: 'Reintentar',
|
||
userMgmtEmpty: 'No se encontraron usuarios.',
|
||
userMgmtYouBadge: 'Tú',
|
||
userMgmtAdminBadge: 'Admin',
|
||
userMgmtAddUser: 'Añadir usuario',
|
||
userMgmtAddUserTitle: 'Nuevo usuario',
|
||
userMgmtEditUserTitle: 'Editar usuario',
|
||
userMgmtUsername: 'Nombre de usuario',
|
||
userMgmtName: 'Nombre visible',
|
||
userMgmtEmail: 'Correo electrónico',
|
||
userMgmtPassword: 'Contraseña',
|
||
userMgmtPasswordEditHint: 'Introduce una nueva contraseña para actualizarla.',
|
||
userMgmtRoleAdmin: 'Admin',
|
||
userMgmtLibraries: 'Bibliotecas',
|
||
userMgmtLibrariesAdminHint: 'Los usuarios admin tienen acceso automáticamente a todas las bibliotecas.',
|
||
userMgmtLibrariesEmpty: 'No hay bibliotecas disponibles en este servidor.',
|
||
userMgmtLibrariesValidation: 'Selecciona al menos una biblioteca.',
|
||
userMgmtLibrariesUpdateError: 'Usuario guardado, pero la asignación de bibliotecas falló',
|
||
userMgmtNoLibraries: 'Sin bibliotecas asignadas',
|
||
userMgmtNeverSeen: 'Nunca',
|
||
userMgmtSave: 'Guardar',
|
||
userMgmtCancel: 'Cancelar',
|
||
userMgmtDelete: 'Eliminar',
|
||
userMgmtEdit: 'Editar',
|
||
userMgmtConfirmDelete: '¿Eliminar el usuario «{{username}}»? Esta acción no se puede deshacer.',
|
||
userMgmtCreateError: 'No se pudo crear el usuario.',
|
||
userMgmtUpdateError: 'No se pudo actualizar el usuario.',
|
||
userMgmtDeleteError: 'No se pudo eliminar el usuario.',
|
||
userMgmtCreated: 'Usuario creado.',
|
||
userMgmtUpdated: 'Usuario actualizado.',
|
||
userMgmtDeleted: 'Usuario eliminado.',
|
||
userMgmtValidationMissing: 'Se requieren nombre de usuario, nombre visible y contraseña.',
|
||
userMgmtValidationMissingIdentity: 'Se requieren nombre de usuario y nombre visible.',
|
||
userMgmtMagicStringGenerate: 'Generar cadena mágica',
|
||
userMgmtSaveAndMagicString: 'Guardar y obtener cadena mágica',
|
||
userMgmtMagicStringPasswordNavHint:
|
||
'Navidrome guardará esta contraseña para el usuario. Si difiere de la actual, se actualizará la contraseña de acceso en el servidor.',
|
||
userMgmtMagicStringPlaintextWarning:
|
||
'Comparte la cadena mágica con cuidado: contiene la contraseña sin cifrar (la codificación no es cifrado). Quien tenga la cadena completa puede iniciar sesión como este usuario.',
|
||
userMgmtMagicStringCopied: 'Cadena mágica copiada al portapapeles.',
|
||
userMgmtMagicStringCopyFailed: 'No se pudo copiar al portapapeles.',
|
||
userMgmtMagicStringLoginFailed: 'Falló la comprobación de la contraseña; no se pudieron verificar las credenciales.',
|
||
userMgmtMagicStringModalTitle: 'Generar cadena mágica',
|
||
userMgmtMagicStringModalDesc: 'Introduce la contraseña Subsonic de «{{username}}». Se incluirá en la cadena mágica copiada.',
|
||
userMgmtMagicStringModalConfirm: 'Copiar cadena',
|
||
audiomuseTitle: 'AudioMuse-AI (Navidrome)',
|
||
audiomuseBadge: 'AudioMuse-AI',
|
||
audiomuseStatusActive: 'Activo',
|
||
audiomuseStatusChecking: 'Comprobando…',
|
||
audiomuseStatusNotDetected: 'No detectado',
|
||
audiomuseStatusProbeFailed: 'Error de comprobación',
|
||
audiomuseDesc:
|
||
'Activa si este servidor tiene el plugin <pluginLink>AudioMuse-AI Navidrome</pluginLink> configurado. Habilita Mezcla Instantánea desde pistas y usa artistas similares del servidor en lugar de Last.fm en páginas de artistas.',
|
||
audiomuseIssueHint:
|
||
'La Mezcla Instantánea falló recientemente — verifica el plugin de Navidrome y la API de AudioMuse. Artistas similares usan Last.fm como respaldo cuando el servidor no devuelve ninguno.',
|
||
connected: 'Conectado',
|
||
failed: 'Fallido',
|
||
eqTitle: 'Ecualizador',
|
||
eqEnabled: 'Habilitar Ecualizador',
|
||
eqPreset: 'Preajuste',
|
||
eqPresetCustom: 'Personalizado',
|
||
eqPresetBuiltin: 'Preajustes Integrados',
|
||
eqPresetCustomGroup: 'Mis Preajustes',
|
||
eqSavePreset: 'Guardar como Preajuste',
|
||
eqPresetName: 'Nombre del preajuste…',
|
||
eqDeletePreset: 'Eliminar preajuste',
|
||
eqResetBands: 'Restablecer a Plano',
|
||
eqPreGain: 'Pre-ganancia',
|
||
eqResetPreGain: 'Restablecer pre-ganancia',
|
||
eqAutoEqTitle: 'Búsqueda AutoEQ de Auriculares',
|
||
eqAutoEqPlaceholder: 'Buscar modelo de auricular / IEM…',
|
||
eqAutoEqSearching: 'Buscando…',
|
||
eqAutoEqNoResults: 'No se encontraron resultados',
|
||
eqAutoEqError: 'Error de búsqueda',
|
||
eqAutoEqRateLimit: 'Límite de GitHub alcanzado — intenta de nuevo en un minuto',
|
||
eqAutoEqFetchError: 'Error al obtener perfil EQ',
|
||
behavior: 'Comportamiento de la App',
|
||
cacheTitle: 'Tamaño Máx. de Almacenamiento',
|
||
cacheDesc: 'Caché del navegador para portadas e imágenes de artistas (IndexedDB). Cuando está lleno, las entradas más antiguas se eliminan automáticamente.',
|
||
cacheUsedImages: 'Imágenes:',
|
||
cacheUsedOffline: 'Pistas offline:',
|
||
cacheUsedHot: 'Tamaño en disco:',
|
||
hotCacheTrackCount: 'Pistas en caché:',
|
||
cacheMaxLabel: 'Tamaño máx.',
|
||
imageCacheScopeLabel: 'Alcance',
|
||
imageCacheSubTitle: 'Vistas previas en el navegador',
|
||
coverDiskCacheSubTitle: 'Caché de portadas en disco',
|
||
coverCacheStrategyTitle: 'Caché de portadas',
|
||
coverCacheStrategyDesc: 'Puedes elegir una estrategia para cada servidor: Lazy — carga las portadas cuando hacen falta; Aggressive — carga cuando hacen falta más precarga adicional en segundo plano.',
|
||
coverCacheStrategyLabel: 'Estrategia',
|
||
coverCacheStrategyServerLabel: 'Servidor',
|
||
coverCacheStrategyProgressLabel: 'Caché',
|
||
coverCacheStrategyActionsLabel: 'Acciones',
|
||
coverCacheStrategyLazy: 'Lazy',
|
||
coverCacheStrategyLazyDesc: 'Carga las portadas solo cuando hacen falta.',
|
||
coverCacheStrategyAggressive: 'Aggressive',
|
||
coverCacheStrategyAggressiveDesc: 'Comportamiento Lazy más precarga adicional en segundo plano.',
|
||
coverCacheStrategyDiskUsage: 'En disco: {{size}}',
|
||
coverCacheStrategyProgressValue: '{{percent}}% ({{done}}/{{total}}) · {{size}}',
|
||
coverCacheStrategyClearAction: 'Vaciar caché de portadas',
|
||
coverCacheStrategyClearTitle: '¿Vaciar caché de portadas?',
|
||
coverCacheStrategyClearDesc: 'Elimina todas las portadas en caché de {{server}}. Se volverán a descargar cuando hagan falta.',
|
||
coverCacheStrategyClearConfirm: 'Vaciar',
|
||
coverCacheStrategyClearCancel: 'Cancelar',
|
||
coverCacheStrategyClearSuccess: 'Caché de portadas vaciada para este servidor.',
|
||
coverCacheStrategyClearError: 'No se pudo vaciar la caché de portadas.',
|
||
coverCacheStrategyServerRemoved: 'servidor eliminado',
|
||
cacheClearBtn: 'Limpiar Caché',
|
||
cacheClearWarning: 'Borra todas las portadas e imágenes de artistas en caché. Se volverán a descargar cuando hagan falta.',
|
||
cacheClearConfirm: 'Limpiar caché de imágenes',
|
||
cacheClearCancel: 'Cancelar',
|
||
mediaDirTitle: 'Carpeta de medios',
|
||
mediaDirDesc: 'Carpeta raíz para reproducción local. Biblioteca offline: library/; búfer de cola: cache/.',
|
||
mediaDirDefault: 'Predeterminado (Datos de App)',
|
||
mediaDirChange: 'Cambiar carpeta',
|
||
mediaDirClear: 'Restablecer predeterminado',
|
||
mediaDirHint: 'Las nuevas descargas usarán esta ubicación. Los archivos existentes permanecen en su ruta hasta que los elimines.',
|
||
offlineStorageTitle: 'Almacenamiento offline',
|
||
offlineDirTitle: 'Biblioteca Offline (En App)',
|
||
offlineDirDesc: 'Ubicación de almacenamiento para pistas que haces disponibles offline dentro de Psysonic.',
|
||
offlineDirDefault: 'Predeterminado (Datos de App)',
|
||
offlineDirChange: 'Cambiar Directorio',
|
||
offlineDirClear: 'Restablecer a Predeterminado',
|
||
offlineDirHint: 'Las nuevas descargas usarán esta ubicación. Las descargas existentes permanecen en su ruta original.',
|
||
hotCacheTitle: 'Caché de reproducción activa',
|
||
hotCacheDisclaimer: 'Precarga las siguientes pistas en cola y mantiene las anteriores. Cuando está activado, usa espacio en disco y red.',
|
||
hotCacheDirDefault: 'Predeterminado (Datos de App)',
|
||
hotCacheDirChange: 'Cambiar carpeta',
|
||
hotCacheDirClear: 'Restablecer a predeterminado',
|
||
hotCacheDirHint: 'Cambiar la carpeta restablece el índice en-app; los archivos ya en disco permanecen donde están hasta que los elimines.',
|
||
hotCacheEnabled: 'Habilitar caché de reproducción activa',
|
||
hotCacheMaxMb: 'Tamaño máximo de caché',
|
||
hotCacheDebounce: 'Espera de cambio de cola',
|
||
hotCacheDebounceImmediate: 'Inmediato',
|
||
hotCacheDebounceSeconds: '{{n}} s',
|
||
hotCacheClearBtn: 'Limpiar caché activa',
|
||
audioOutputDevice: 'Dispositivo de salida de audio',
|
||
audioOutputDeviceDesc: 'Selecciona el dispositivo de audio que usa Psysonic. Los cambios surten efecto de inmediato y reinician la pista actual.',
|
||
audioOutputDeviceDefault: 'Predeterminado del sistema',
|
||
audioOutputDeviceRefresh: 'Actualizar lista de dispositivos',
|
||
audioOutputDeviceOsDefaultNow: 'salida actual del sistema',
|
||
audioOutputDeviceListError: 'No se pudo cargar la lista de dispositivos de audio.',
|
||
audioOutputDeviceNotInCurrentList: 'no está en la lista actual',
|
||
audioOutputDeviceMacNotice: 'En macOS, la reproducción actualmente sigue siempre al dispositivo de salida del sistema por razones técnicas. Cambia el destino mediante Ajustes del Sistema → Sonido o el icono del altavoz en la barra de menús. Motivo: CoreAudio activa una solicitud de permiso de micrófono al abrir un stream no-predeterminado — lo evitamos usando siempre la salida por defecto del sistema.',
|
||
hiResTitle: 'Reproducción Nativa Hi-Res',
|
||
hiResEnabled: 'Habilitar reproducción nativa hi-res',
|
||
hiResDesc: "Fuerza 44.1 kHz de salida por defecto para máxima estabilidad. Habilita solo si tu hardware y red soportan confiablemente altas tasas de muestreo (88.2 kHz+).",
|
||
showArtistImages: 'Mostrar Imágenes de Artistas',
|
||
showArtistImagesDesc: 'Carga y muestra imágenes de artistas en el resumen de Artistas. Desactivado por defecto para reducir I/O de disco del servidor y carga de red en bibliotecas grandes.',
|
||
showOrbitTrigger: 'Mostrar "Orbit" en la cabecera',
|
||
showOrbitTriggerDesc: 'El botón en la cabecera para iniciar o unirse a una sesión de escucha compartida. Ocúltalo si no usas Orbit — puedes volver a activarlo aquí.',
|
||
showTrayIcon: 'Mostrar Icono en Bandeja',
|
||
showTrayIconDesc: 'Muestra el icono de Psysonic en el área de notificación / barra de menú.',
|
||
minimizeToTray: 'Minimizar a Bandeja',
|
||
minimizeToTrayDesc: 'Al cerrar la ventana, mantener Psysonic ejecutándose en la bandeja del sistema en lugar de salir.',
|
||
clockFormat: 'Formato de hora',
|
||
clockFormatDesc: 'Formato del reloj utilizado por la ETA de la cola y la vista previa del temporizador de suspensión.',
|
||
clockFormatAuto: 'Automático (idioma del sistema)',
|
||
clockFormatTwentyFour: '24 horas',
|
||
clockFormatTwelve: '12 horas (AM/PM)',
|
||
preloadMiniPlayer: 'Precargar mini reproductor',
|
||
preloadMiniPlayerDesc: 'Crea la ventana del mini reproductor en segundo plano al iniciar la aplicación para que muestre contenido al instante la primera vez que se abre. Consume un poco más de memoria.',
|
||
discordRichPresence: 'Discord Rich Presence',
|
||
discordRichPresenceDesc: 'Muestra la pista actual en tu perfil de Discord. Requiere que Discord esté ejecutándose.',
|
||
discordRichPresenceNotice: 'Aviso: esta es la Discord Rich Presence integrada en Psysonic. Si prefieres usar el plugin oficial de Discord Rich Presence de Navidrome, deja esta función desactivada y activa en su lugar «Mostrar en Reproduciendo Ahora» más abajo en esta página.',
|
||
useCustomTitlebar: 'Barra de título personalizada',
|
||
useCustomTitlebarDesc: 'Reemplaza la barra de título del sistema con una integrada que coincide con el tema de la app. Desactiva para usar la barra nativa de GNOME/GTK.',
|
||
windowButtonStyle: 'Botones de ventana',
|
||
windowButtonStyleDesc: 'Elige el aspecto de los botones de minimizar, maximizar y cerrar.',
|
||
windowButtonsDots: 'Puntos',
|
||
windowButtonsDotsGlyph: 'Puntos + iconos',
|
||
windowButtonsFlat: 'Plano',
|
||
windowButtonsPill: 'Píldora',
|
||
windowButtonsOutline: 'Contorno',
|
||
windowButtonsGlyph: 'Minimalista',
|
||
showMinimizeButton: 'Mostrar botón de minimizar',
|
||
showMinimizeButtonDesc: 'Desactiva para mostrar solo maximizar y cerrar.',
|
||
linuxWebkitSmoothScroll: 'Rueda suave (Linux)',
|
||
linuxWebkitSmoothScrollDesc: 'Activado: inercia. Desactivado: pasos por línea (estilo GTK).',
|
||
linuxWebkitInputForceRepaint: 'Repintar los campos al enfocar (Linux)',
|
||
linuxWebkitInputForceRepaintDesc: 'Solución para WebKitGTK 2.50.x donde los campos de texto se congelan al pulsar. Las lupas parpadean brevemente al enfocar.',
|
||
linuxWaylandTextRender: 'Renderizado de texto Wayland (Linux)',
|
||
linuxWaylandTextRenderDesc:
|
||
'El suavizado en la interfaz se aplica al instante. La aceleración de hardware de WebKit (nítido/GPU) se guarda y aplica al reiniciar la app; cambiarla en vivo puede congelar WebKitGTK.',
|
||
linuxWaylandTextRenderBalanced: 'Equilibrado',
|
||
linuxWaylandTextRenderSharp: 'Nítido (más CPU)',
|
||
linuxWaylandTextRenderGpu: 'Prioridad GPU',
|
||
linuxWaylandTextRenderMinimal: 'Mínimo (suavizado CSS por defecto)',
|
||
discordCoverSource: 'Fuente de portada',
|
||
discordCoverSourceDesc: 'De dónde obtener la portada del álbum para tu perfil de Discord.',
|
||
discordCoverNone: 'Ninguna (solo icono de la app)',
|
||
discordCoverServer: 'Servidor (vía info del álbum)',
|
||
discordCoverApple: 'Apple Music',
|
||
discordOptions: 'Opciones avanzadas de Discord',
|
||
discordTemplates: 'Plantillas de texto personalizadas',
|
||
discordTemplatesDesc: 'Personaliza qué información se muestra en tu perfil de Discord. Variables: {title}, {artist}, {album}',
|
||
discordTemplateDetails: 'Línea principal (details)',
|
||
discordTemplateState: 'Línea secundaria (state)',
|
||
discordTemplateLargeText: 'Tooltip del álbum (largeText)',
|
||
discordTemplateName: 'Línea de la lista (name)',
|
||
nowPlayingEnabled: 'Mostrar en Reproduciendo Ahora',
|
||
nowPlayingEnabledDesc: 'Transmite tu pista actual a la vista de oyentes en vivo del servidor. Desactiva para dejar de enviar datos de reproducción.',
|
||
nowPlayingPluginNote: 'Necesario al usar el plugin oficial de Discord Rich Presence de Navidrome.',
|
||
enableBandsintown: 'Fechas de gira de Bandsintown',
|
||
enableBandsintownDesc: 'Muestra próximos conciertos del artista actual en la pestaña Info. Los datos se obtienen de la API pública de Bandsintown.',
|
||
lyricsServerFirst: 'Preferir letras del servidor',
|
||
lyricsServerFirstDesc: 'Verifica primero letras provistas por el servidor (etiquetas embebidas, archivos sidecar) antes de consultar LRCLIB. Desactiva para usar LRCLIB primero.',
|
||
enableNeteaselyrics: 'Letras de Netease Cloud Music',
|
||
enableNeteaselyricsDesc: 'Usa Netease Cloud Music como fuente de letras de último recurso cuando el servidor y LRCLIB no devuelvan nada. Mejor cobertura para música asiática e internacional.',
|
||
lyricsSourcesTitle: 'Fuentes de Letras',
|
||
lyricsSourcesDesc: 'Elige qué fuentes consultar para letras y en qué orden. Arrastra para reordenar. Las fuentes desactivadas se omiten completamente.',
|
||
lyricsSourceServer: 'Servidor',
|
||
lyricsSourceLrclib: 'LRCLIB',
|
||
lyricsSourceNetease: 'Netease Cloud Music',
|
||
lyricsYouLyPlus: 'YouLyPlus (Karaoke)',
|
||
lyricsYouLyPlusDesc: 'Sincronización palabra por palabra desde un backend comunitario (Apple Music, Spotify, Musixmatch, QQ). Se prueba primero; las fuentes activadas abajo actúan como respaldo.',
|
||
lyricsSourcesFallbackHint: 'Se usan como respaldo cuando YouLyPlus no encuentra nada. Arrastra para reordenar.',
|
||
lyricsSourcesPrimaryHint: 'Se prueban en orden. Arrastra para reordenar. Todo desactivado = letras desactivadas.',
|
||
lyricsStaticOnly: 'Mostrar letras como texto estático',
|
||
lyricsStaticOnlyDesc: 'Muestra las letras sincronizadas sin desplazamiento automático ni resaltado por palabra.',
|
||
downloadsTitle: 'Exportación ZIP y Archivado',
|
||
downloadsFolderDesc: 'Carpeta de destino para álbumes que descargas como ZIP a tu computadora.',
|
||
downloadsDefault: 'Carpeta de Descargas Predeterminada',
|
||
pickFolder: 'Seleccionar',
|
||
pickFolderTitle: 'Seleccionar Carpeta de Descarga',
|
||
clearFolder: 'Limpiar carpeta de descarga',
|
||
logout: 'Cerrar Sesión',
|
||
aboutTitle: 'Acerca de Psysonic',
|
||
aboutDesc: 'Un reproductor de música de escritorio moderno creado para Navidrome. Utiliza la API de Subsonic más extensiones específicas de Navidrome. Construido en Tauri v2 con motor de audio nativo en Rust — ligero y rápido, pero lleno de características: barra de progreso tipo onda, letras sincronizadas, integración Last.fm, ecualizador de 10 bandas, crossfade, reproducción gapless, Replay Gain, navegación por géneros, y una gran biblioteca de temas.',
|
||
aboutLicense: 'Licencia',
|
||
aboutLicenseText: 'GNU GPL v3 — libre para usar, modificar y distribuir bajo la misma licencia.',
|
||
aboutRepo: 'Código Fuente en GitHub',
|
||
aboutVersion: 'Versión',
|
||
aboutBuiltWith: 'Construido con Tauri · React · TypeScript · Rust/rodio',
|
||
aboutReleaseNotesLabel: 'Notas de la versión',
|
||
aboutReleaseNotesLink: 'Abrir las novedades de esta versión',
|
||
aboutContributorsLabel: 'Contribuidores',
|
||
showChangelogOnUpdate: "Mostrar 'Novedades' al actualizar",
|
||
showChangelogOnUpdateDesc: 'Muestra un discreto banner de changelog encima de Now Playing tras una actualización. Clic abre las notas de la versión; X lo oculta.',
|
||
libraryGridMaxColumnsTitle: 'Rejillas de tarjetas en la biblioteca',
|
||
libraryGridMaxColumnsPerfHint: 'Más columnas caben más mosaicos por fila y pueden aumentar el trabajo de diseño y pintado; se nota más en bibliotecas muy grandes o equipos lentos.',
|
||
libraryGridMaxColumnsRangeLabel: 'Máximo de columnas ({{min}}–{{max}})',
|
||
libraryGridMaxColumnsDesc: 'Se aplica a vistas de álbum, artista, lista de reproducción, radio, sin conexión y otras con tarjetas. Menos columnas = mosaicos más grandes y suele aligerar la CPU.',
|
||
libraryIndexTitle: 'Índice local de biblioteca (vista previa)',
|
||
libraryIndexDesc: 'Mantiene una copia local de la base de datos de pistas de cada servidor para que la navegación y la búsqueda sean rápidas y funcionen sin conexión. La sincronización inicial se ejecuta en todos los servidores configurados; los servidores sin conexión se reintentan automáticamente.',
|
||
libraryIndexDeltaHint: 'La sincronización delta en segundo plano comprueba los servidores vinculados cada 30 segundos y se ejecuta cuando corresponde — normalmente cada 5–45 minutos según el tamaño de la biblioteca y la carga.',
|
||
libraryIndexEnable: 'Activar índice local de biblioteca',
|
||
libraryIndexEnableAllDesc: 'Indexar y sincronizar todos los servidores configurados en segundo plano.',
|
||
libraryIndexNoServer: 'Añade un servidor primero.',
|
||
libraryIndexServerListTitle: 'Servidores indexados',
|
||
libraryIndexAllExcluded: 'Todos los servidores están excluidos de la sincronización. Vuelve a activar el índice o añade un servidor.',
|
||
libraryIndexServerOffline: 'Servidor sin conexión — sincronización aplazada',
|
||
libraryIndexServerDeferred: 'Aplazada',
|
||
libraryIndexServerSyncing: 'Sincronizando…',
|
||
libraryIndexFullResync: 'Resincronización completa',
|
||
libraryIndexDeltaSync: 'Sincronización delta',
|
||
libraryIndexExcludeServer: 'Excluir de la sincronización',
|
||
libraryIndexExcludingServer: 'Excluyendo…',
|
||
libraryIndexExcludedTitle: 'Excluidos de la sincronización',
|
||
libraryIndexIncludeServer: 'Incluir de nuevo',
|
||
libraryIndexIncludingServer: 'Incluyendo…',
|
||
libraryIndexStatus: 'Estado',
|
||
libraryIndexStatusIdle: 'Inactivo',
|
||
libraryIndexStatusProbing: 'Comprobando servidor…',
|
||
libraryIndexStatusInitial: 'Sincronización inicial…',
|
||
libraryIndexStatusReady: 'Listo ({{count}} pistas)',
|
||
libraryIndexStatusError: 'Error — consulta los registros',
|
||
libraryIndexProgressIngest: '{{count}} pistas indexadas…',
|
||
libraryIndexProgressVerify: '{{checked}} verificadas ({{deleted}} eliminadas)',
|
||
libraryIndexSyncNow: 'Sincronizar ahora',
|
||
libraryIndexVerify: 'Verificar integridad de la biblioteca',
|
||
libraryIndexCancel: 'Cancelar',
|
||
libraryIndexAutoReconcile: 'Reconciliación automática al bajar el recuento',
|
||
libraryIndexAutoReconcileDesc: 'Comprueba automáticamente pistas eliminadas cuando el servidor informa de menos de las esperadas.',
|
||
libraryIndexSyncError: 'Error de sincronización de biblioteca: {{error}}',
|
||
libraryIndexBindError: 'No se pudo activar el índice: {{error}}',
|
||
randomMixTitle: 'Lista negra de Mezcla Aleatoria',
|
||
luckyMixMenuTitle: 'Mostrar Mezcla Suerte en el menú',
|
||
luckyMixMenuDesc: 'Activa Mezcla Suerte en "Crear Mezcla" y como elemento de menú separado cuando la navegación dividida está activa. Solo visible cuando AudioMuse está activo en el servidor actual.',
|
||
randomMixBlacklistTitle: 'Palabras Clave de Filtro Personalizadas',
|
||
randomMixBlacklistDesc: 'Las canciones se excluyen cuando cualquier palabra clave coincide con su género, título, álbum o artista (activo cuando el checkbox de arriba está activado).',
|
||
randomMixBlacklistPlaceholder: 'Agregar palabra clave…',
|
||
randomMixBlacklistAdd: 'Agregar',
|
||
randomMixBlacklistEmpty: 'Aún no hay palabras clave personalizadas.',
|
||
randomMixHardcodedTitle: 'Palabras clave integradas (activas cuando el checkbox está activado)',
|
||
tabAudio: 'Audio',
|
||
tabStorage: 'Sin conexión y caché',
|
||
tabAppearance: 'Apariencia',
|
||
tabThemes: 'Temas',
|
||
themesYourThemesTitle: 'Tus temas',
|
||
themesCvdTooltip: 'Apto para daltonismo: deuteranopía, protanopía, tritanopía',
|
||
themeAnimationWarning: 'Este tema usa animaciones que pueden aumentar el uso de CPU en tu sistema (Nvidia/Linux o compositing desactivado).',
|
||
themeStoreTitle: 'Tienda de temas',
|
||
themeStoreSubmitText: '¿Has creado tu propio tema? Compártelo con la comunidad — más información en el repositorio de temas.',
|
||
themeStoreSubmitLink: 'Abrir el repositorio de temas',
|
||
themeStoreNetworkNotice: 'La tienda de temas carga el catálogo y las vistas previas desde GitHub. No se envían datos personales.',
|
||
themeStoreStatsNotice: 'Las fechas de última modificación se actualizan una vez al día (sobre las 04:17 UTC).',
|
||
themeStoreSearchPlaceholder: 'Buscar temas…',
|
||
themeStoreFilterMode: 'Filtrar por modo',
|
||
themeStoreModeAll: 'Todos',
|
||
themeStoreModeDark: 'Oscuro',
|
||
themeStoreModeLight: 'Claro',
|
||
themeStoreRefresh: 'Actualizar',
|
||
themeStoreLoading: 'Cargando temas…',
|
||
themeStoreError: 'No se pudo cargar la tienda de temas. Comprueba tu conexión.',
|
||
themeStoreRetry: 'Reintentar',
|
||
themeStoreOfflineTitle: 'La tienda de temas está sin conexión',
|
||
themeStoreEmpty: 'Ningún tema coincide con tus filtros.',
|
||
themeStoreActive: 'Activo',
|
||
themeStoreEnlarge: 'Ampliar vista previa',
|
||
themeStoreSortNewest: 'Más recientes',
|
||
themeStoreSortName: 'Alfabético',
|
||
themeStoreAnimAll: 'Todos',
|
||
themeStoreAnimAnimated: 'Animados',
|
||
themeStoreAnimStatic: 'Estáticos',
|
||
themeStoreByAuthor: 'por {{author}}',
|
||
themeStoreLastChanged: 'Última modificación',
|
||
themeMigrationNoticeTitle: 'Tema movido a la tienda',
|
||
themeMigrationNoticeBody: '{{themes}} ya no viene incluido en la app. Puedes reinstalarlo desde la Tienda de Temas (Ajustes → Temas) o elegir otro tema.',
|
||
themeMigrationNoticeOpen: 'Abrir Tienda de Temas',
|
||
themeStoreOffline: 'Mostrando el último catálogo en caché: parece que estás sin conexión.',
|
||
themeStoreInstall: 'Instalar',
|
||
themeStoreInstalling: 'Instalando…',
|
||
themeStoreApply: 'Aplicar',
|
||
themeStoreUpdate: 'Actualizar',
|
||
themeStoreUpdating: 'Actualizando…',
|
||
themeStoreUninstall: 'Desinstalar',
|
||
themeStoreInstallFailed: 'Error al instalar',
|
||
themeStorePagePrev: 'Página anterior',
|
||
themeStorePageNext: 'Página siguiente',
|
||
themeStorePageStatus: 'Página {{page}} de {{total}}',
|
||
themeImportTitle: 'Importar un tema',
|
||
themeImportButton: 'Importar tema…',
|
||
themeImporting: 'Importando…',
|
||
themeImportHint: 'Carga un tema empaquetado como .zip (manifest.json + theme.css).',
|
||
themeImportSuccess: '«{{name}}» importado.',
|
||
themeImportErrorTitle: 'No se pudo importar este tema',
|
||
themeImportErrorBody: 'No coincide con el formato de temas de Psysonic. Puede que se haya creado para otra versión o que el archivo esté incompleto: comprueba que elegiste el .zip correcto o pregunta al autor del tema.',
|
||
themeImportErrorDetails: 'Detalles técnicos',
|
||
themeImportConfirmTitle: '¿Instalar tema?',
|
||
themeImportConfirmBody: '¿Instalar «{{name}}» de {{author}}?',
|
||
themeImportConfirmRisk: 'Los temas importados no se revisan: instala solo temas en los que confíes.',
|
||
tabLibrary: 'Biblioteca',
|
||
tabServers: 'Servidores',
|
||
tabLyrics: 'Letras',
|
||
tabPersonalisation: 'Personalización',
|
||
tabIntegrations: 'Integraciones',
|
||
inputKeybindingsTitle: 'Atajos de teclado',
|
||
aboutContributorsCount_one: '{{count}} contribución',
|
||
aboutContributorsCount_other: '{{count}} contribuciones',
|
||
searchPlaceholder: 'Buscar en ajustes…',
|
||
searchNoResults: 'Ningún ajuste coincide con tu búsqueda.',
|
||
aboutMaintainersLabel: 'Mantenedores',
|
||
integrationsPrivacyTitle: 'Aviso de privacidad',
|
||
integrationsPrivacyBody: 'Todas las integraciones de esta pestaña son <strong>opt-in</strong> y, una vez activadas, envían datos a servicios externos o a tu servidor Navidrome. Last.fm recibe tu historial de escucha, Discord muestra la pista actual en tu perfil, Bandsintown se consulta por artista para obtener fechas de gira, y la compartición «En reproducción» publica tu pista actual para otros usuarios de tu servidor Navidrome. Si no quieres nada de esto, simplemente deja desactivada la sección correspondiente.',
|
||
homeCustomizerTitle: 'Escenario Principal',
|
||
queueModeTitle: 'Modo de visualización de la cola',
|
||
queueModeQueueSub: 'Muestra solo las próximas pistas. La pista actual permanece en el encabezado y sale de la lista al reproducirse.',
|
||
queueModePlaylistSub: 'Mantiene toda la cola en la lista, con la pista actual resaltada arriba; las reproducidas permanecen.',
|
||
queueModeTimelineSub: 'Centrar la pista actual, con el historial reproducido arriba y las próximas pistas (en orden aleatorio) debajo.',
|
||
queueToolbarTitle: 'Barra de herramientas de cola',
|
||
queueToolbarReset: 'Restablecer a predeterminado',
|
||
queueToolbarSeparator: 'Separador',
|
||
sidebarTitle: 'Barra Lateral',
|
||
sidebarReset: 'Restablecer a predeterminado',
|
||
artistLayoutTitle: 'Secciones de la página del artista',
|
||
artistLayoutDesc: 'Arrastra para reordenar, alterna para ocultar secciones individuales de la página del artista. Las secciones sin datos se omiten automáticamente.',
|
||
artistLayoutReset: 'Restablecer a predeterminado',
|
||
artistLayoutBio: 'Biografía del artista',
|
||
artistLayoutTopTracks: 'Mejores pistas',
|
||
artistLayoutSimilar: 'Artistas similares',
|
||
artistLayoutAlbums: 'Álbumes',
|
||
artistLayoutFeatured: 'También presente en',
|
||
playlistLayoutTitle: 'Diseño de la página de playlists',
|
||
playlistLayoutDesc: 'Mostrar u ocultar elementos individuales en la página de playlists.',
|
||
playlistLayoutReset: 'Restablecer a predeterminado',
|
||
playerBarTitle: 'Barra de reproducción',
|
||
playerBarReset: 'Restablecer a predeterminado',
|
||
playerBarStarRating: 'Calificación con estrellas',
|
||
playerBarFavorite: 'Favorito (corazón)',
|
||
playerBarLastfmLove: 'Botón Me gusta',
|
||
playerBarPlaybackRate: 'Velocidad de reproducción',
|
||
playerBarEqualizer: 'Ecualizador',
|
||
playerBarMiniPlayer: 'Mini reproductor',
|
||
playbackRateTitle: 'Velocidad de reproducción',
|
||
playbackRateEnabled: 'Activar velocidad de reproducción',
|
||
playbackRateEnabledDesc: 'Velocidad global para todas las pistas. No se aplica a la radio ni a las vistas previas.',
|
||
playbackRateStrategy: 'Estrategia',
|
||
playbackRateStrategySpeed: 'Velocidad',
|
||
playbackRateStrategyVarispeed: 'Con tono',
|
||
playbackRateStrategyVarispeedSemitones: 'Semitonos',
|
||
playbackRateStrategyPreserve: 'Tono',
|
||
playbackRateStrategySpeedTip: 'Cambia el tempo; el tono se corrige automáticamente.',
|
||
playbackRateStrategyVarispeedTip: 'La velocidad y el tono cambian juntos, como una cinta. Tono según la velocidad: 12 × log2(velocidad).',
|
||
playbackRateStrategyVarispeedSemitonesTip: 'Igual que « Con tono », pero ajustado directamente en semitonos. Velocidad = 2^(st/12).',
|
||
playbackRateStrategyPreserveTip: 'Tempo ajustable más un desplazamiento de tono manual (±12 st).',
|
||
playbackRateSpeed: 'Velocidad',
|
||
playbackRatePitch: 'Tono',
|
||
playbackRateDerivedPitch: 'Cambio de tono por velocidad: {{value}}',
|
||
playbackRateDerivedSpeed: 'Velocidad según semitonos: {{value}}',
|
||
playbackRateFineStep: 'Ajuste fino',
|
||
playbackRateFineStepDesc: 'Pasos de control más pequeños: 0,01× para la velocidad y 0,01 st para el tono.',
|
||
playbackRateAutoPitch: 'El tono se corrige automáticamente.',
|
||
playbackRateHint: '« Velocidad » mantiene el tono natural. « Con tono » cambia el tono con la velocidad. « Con tono (semitonos) » ajusta ese cambio directamente en semitonos. « Tono » añade un desplazamiento manual. Usa más CPU que « Con tono ». No para radio, vistas previas u Orbit.',
|
||
playbackRateNeutral: 'A 1,0× y sin desplazamiento de tono, la reproducción es normal.',
|
||
playbackRateOrbitPaused: 'No se aplica durante sesiones Orbit — la reproducción permanece a 1,0× para sincronizar.',
|
||
playbackRateOrbitPausedShort: 'Orbit: 1,0× para sincronizar.',
|
||
advancedMode: 'Avanzado',
|
||
advancedModeTooltip: 'Mostrar opciones avanzadas en todas las pestañas de ajustes. Aportes de la comunidad que no reflejan necesariamente la filosofía de diseño de los mantenedores de Psysonic.',
|
||
advancedBadge: 'Avanzado',
|
||
sidebarDrag: 'Arrastra para reordenar',
|
||
sidebarFixed: 'Siempre visible',
|
||
nowPlayingTopTitle: '"Reproduciendo Ahora" arriba',
|
||
nowPlayingTopDesc: 'Fijar la entrada "Reproduciendo Ahora" en la parte superior de la barra lateral en lugar de abajo.',
|
||
randomNavSplitTitle: 'Dividir navegación Mix',
|
||
randomNavSplitDesc: 'Mostrar "Mezcla Aleatoria", "Álbumes Aleatorios" y "Mezcla Suerte" como entradas separadas en la barra lateral en lugar del hub "Crear Mezcla".',
|
||
tabInput: 'Entrada',
|
||
tabUsers: 'Usuarios',
|
||
tabSystem: 'Sistema',
|
||
loggingTitle: 'Registro',
|
||
loggingModeDesc: 'Controla la verbosidad de los logs del backend en la terminal.',
|
||
loggingModeOff: 'Apagado',
|
||
loggingModeNormal: 'Normal',
|
||
loggingModeDebug: 'Depuración',
|
||
loggingExport: 'Exportar logs',
|
||
loggingExportSuccess: 'Logs exportados ({{count}} líneas).',
|
||
loggingExportError: 'No se pudieron exportar los logs.',
|
||
ratingsSectionTitle: 'Calificaciones',
|
||
ratingsSkipStarTitle: 'Saltar para 1 estrella',
|
||
ratingsSkipStarDesc:
|
||
'Después de varios saltos seguidos, asigna 1 estrella a la pista. Solo para pistas aún no calificadas.',
|
||
ratingsSkipStarThresholdLabel: 'Saltos',
|
||
ratingsMixFilterTitle: 'Filtrar por calificación',
|
||
ratingsMixFilterDesc:
|
||
'Filtra elementos de baja calificación en {{mix}} y {{albums}}. Click en la estrella seleccionada nuevamente para desactivar el umbral.',
|
||
ratingsMixMinSong: 'Canciones',
|
||
ratingsMixMinAlbum: 'Álbumes',
|
||
ratingsMixMinArtist: 'Artistas',
|
||
ratingsMixMinThresholdAria: 'Estrellas mínimas: {{label}}',
|
||
backupTitle: 'Respaldo y Restauración',
|
||
backupExport: 'Exportar configuración',
|
||
backupExportDesc: 'Guarda toda la configuración, perfiles de servidor, configuración Last.fm, tema, EQ y atajos de teclado a un archivo .psybkp. Las contraseñas se almacenan en texto plano — mantén el archivo seguro.',
|
||
backupImport: 'Importar configuración',
|
||
backupImportDesc: 'Restaura configuración desde un archivo .psybkp. La app se recargará después de importar.',
|
||
backupImportConfirm: 'Esto sobrescribirá toda la configuración actual. ¿Continuar?',
|
||
backupSuccess: 'Respaldo guardado',
|
||
backupImportSuccess: 'Configuración restaurada — recargando…',
|
||
backupImportError: 'Archivo de respaldo inválido o corrupto.',
|
||
shortcutsReset: 'Restablecer a predeterminados',
|
||
shortcutListening: 'Presiona una tecla…',
|
||
shortcutUnbound: '—',
|
||
globalShortcutsTitle: 'Atajos Globales',
|
||
globalShortcutsNote: 'Funcionan en todo el sistema incluso cuando Psysonic está en segundo plano. Requieren Ctrl, Alt o Super como modificador.',
|
||
shortcutClear: 'Limpiar',
|
||
shortcutPlayPause: 'Reproducir / Pausa',
|
||
shortcutNext: 'Siguiente pista',
|
||
shortcutPrev: 'Pista anterior',
|
||
shortcutVolumeUp: 'Subir volumen',
|
||
shortcutVolumeDown: 'Bajar volumen',
|
||
shortcutSeekForward: 'Avanzar 10s',
|
||
shortcutSeekBackward: 'Retroceder 10s',
|
||
shortcutToggleQueue: 'Alternar cola',
|
||
shortcutOpenFolderBrowser: 'Abrir {{folderBrowser}}',
|
||
shortcutFullscreenPlayer: 'Reproductor pantalla completa',
|
||
shortcutNativeFullscreen: 'Pantalla completa nativa',
|
||
shortcutOpenMiniPlayer: 'Abrir mini reproductor',
|
||
shortcutStartSearch: 'Iniciar una búsqueda',
|
||
shortcutStartAdvancedSearch: 'Iniciar una búsqueda avanzada',
|
||
shortcutToggleSidebar: 'Alternar barra lateral',
|
||
shortcutMuteSound: 'Silenciar',
|
||
shortcutToggleEqualizer: 'Abrir / alternar ecualizador',
|
||
shortcutToggleRepeat: 'Alternar repetición',
|
||
shortcutOpenNowPlaying: 'Abrir "Reproduciendo ahora"',
|
||
shortcutShowLyrics: 'Mostrar letra',
|
||
shortcutFavoriteCurrentTrack: 'Añadir pista actual a favoritos',
|
||
shortcutOpenHelp: 'Ayuda',
|
||
playbackTitle: 'Reproducción',
|
||
replayGain: 'Replay Gain',
|
||
replayGainDesc: 'Normalizar volumen de pistas usando metadatos ReplayGain',
|
||
replayGainMode: 'Modo',
|
||
replayGainAuto: 'Auto',
|
||
replayGainAutoDesc: 'Usa la ganancia del álbum cuando las pistas adyacentes en la cola son del mismo álbum, si no la ganancia de pista.',
|
||
replayGainTrack: 'Pista',
|
||
replayGainAlbum: 'Álbum',
|
||
replayGainPreGain: 'Pre-Ganancia (archivos etiquetados)',
|
||
replayGainPreGainDesc: 'Aumento universal añadido a cada cálculo de ReplayGain. Útil si tu biblioteca te parece globalmente demasiado baja.',
|
||
replayGainFallback: 'Respaldo (sin etiquetar / radio)',
|
||
replayGainFallbackDesc: 'Se aplica a pistas (y emisoras de radio) sin etiquetas ReplayGain, para que el contenido sin etiquetar no salte más fuerte que el resto.',
|
||
normalization: 'Normalización',
|
||
normalizationDesc: 'Iguala el volumen percibido entre pistas, álbumes y radio.',
|
||
normalizationOff: 'Apagado',
|
||
normalizationReplayGain: 'ReplayGain',
|
||
normalizationLufs: 'LUFS',
|
||
loudnessTargetLufs: 'LUFS objetivo',
|
||
loudnessTargetLufsDesc: 'Volumen de referencia al que se ajustan todas las pistas. Valor más bajo = salida más fuerte. Los servicios de streaming suelen rondar los -14 LUFS.',
|
||
loudnessPreAnalysisAttenuation: 'Atenuación antes de medir',
|
||
loudnessPreAnalysisAttenuationDesc:
|
||
'Atenúa hasta que la loudness del tema esté guardada. En streaming luego solo hay estimaciones aproximadas. 0 dB = ninguna; más negativo = más bajo. A la derecha se muestra el dB efectivo para el destino actual.',
|
||
loudnessPreAnalysisAttenuationRef: 'Efectivo {{eff}} dB con destino {{tgt}} LUFS.',
|
||
loudnessPreAnalysisAttenuationReset: 'Predeterminado',
|
||
loudnessFirstPlayNote:
|
||
'En la primera reproducción de un tema nuevo el volumen puede oscilar brevemente mientras se mide. La siguiente vez se usa la medición guardada y todo permanece estable. Las pistas próximas en la cola suelen analizarse durante la canción anterior, por lo que en la práctica esto rara vez ocurre.',
|
||
crossfade: 'Crossfade',
|
||
crossfadeDesc: 'Transición entre pistas',
|
||
crossfadeSecs: '{{n}} s',
|
||
crossfadeTrimSilence: 'Recortar el silencio entre pistas',
|
||
crossfadeTrimSilenceDesc: 'Omite el silencio al final de la pista actual y al inicio de la siguiente para que la transición se solape con la música, no con el vacío. Para resultados fiables, activa la Caché de reproducción activa para que la siguiente pista esté lista a tiempo',
|
||
autoDj: 'AutoDJ',
|
||
autoDjDesc: 'Sin duración fija: AutoDJ se adapta al audio real y se solapa con los fundidos e introducciones reales en lugar de un número fijo de segundos. Para resultados fiables, activa la Caché de reproducción activa.',
|
||
notWithGapless: 'No disponible mientras Gapless está activo',
|
||
notWithCrossfade: 'No disponible mientras Crossfade está activo',
|
||
gapless: 'Reproducción Gapless',
|
||
gaplessDesc: 'Precarga siguiente pista para eliminar silencios entre canciones',
|
||
preservePlayNextOrder: 'Mantener el orden de "Reproducir Siguiente"',
|
||
preservePlayNextOrderDesc: 'Los elementos añadidos a "Reproducir Siguiente" se encolan detrás de los anteriores en vez de saltar al principio.',
|
||
trackPreviewsTitle: 'Previsualizaciones de pistas',
|
||
trackPreviewsToggle: 'Activar previsualizaciones',
|
||
trackPreviewsDesc: 'Mostrar botones de Reproducir y Previsualización en las listas de pistas para una muestra rápida a mitad de canción.',
|
||
trackPreviewStart: 'Posición inicial',
|
||
trackPreviewStartDesc: 'Cuánto avanza la pista antes de iniciar la previsualización (% de la duración).',
|
||
trackPreviewDuration: 'Duración',
|
||
trackPreviewDurationDesc: 'Cuánto dura cada previsualización antes de detenerse.',
|
||
trackPreviewDurationSecs: '{{n}} s',
|
||
trackPreviewLocationsTitle: 'Dónde aparecen las previsualizaciones',
|
||
trackPreviewLocationsDesc: 'Elige en qué listas se muestran los botones de previsualización.',
|
||
trackPreviewLocation_suggestions: 'En sugerencias de listas',
|
||
trackPreviewLocation_albums: 'En listas de pistas de álbum',
|
||
trackPreviewLocation_playlists: 'En listas de reproducción',
|
||
trackPreviewLocation_favorites: 'En favoritos',
|
||
trackPreviewLocation_artist: 'En las mejores pistas del artista',
|
||
trackPreviewLocation_randomMix: 'En Random Mix',
|
||
nextTrackBufferingTitle: 'Buffer',
|
||
infiniteQueue: 'Cola Infinita',
|
||
infiniteQueueDesc: 'Agregar automáticamente pistas aleatorias cuando la cola termina',
|
||
experimental: 'Experimental',
|
||
sidebarLyricsStyle: 'Estilo de desplazamiento de letra',
|
||
sidebarLyricsStyleClassic: 'Clásico',
|
||
sidebarLyricsStyleClassicDesc: 'La línea activa se centra.',
|
||
sidebarLyricsStyleApple: 'Apple Music-like',
|
||
sidebarLyricsStyleAppleDesc: 'La línea activa se ancla arriba con animación suave.',
|
||
seekbarStyle: 'Estilo de Barra de Progreso',
|
||
seekbarStyleDesc: 'Elige la apariencia de la barra de progreso del reproductor',
|
||
seekbarTruewave: 'Forma de Onda Real',
|
||
seekbarPseudowave: 'Forma de Onda Pseudo',
|
||
seekbarLinedot: 'Línea y Punto',
|
||
seekbarBar: 'Barra',
|
||
seekbarThick: 'Barra Gruesa',
|
||
seekbarSegmented: 'Segmentada',
|
||
seekbarNeon: 'Brillo Neón',
|
||
seekbarPulsewave: 'Onda Pulsante',
|
||
seekbarParticletrail: 'Estela de Partículas',
|
||
seekbarLiquidfill: 'Llenado Líquido',
|
||
seekbarRetrotape: 'Cinta Retro',
|
||
themeSchedulerTitle: 'Cambio Automático de Tema',
|
||
themeSchedulerEnable: 'Habilitar Programador de Temas',
|
||
themeSchedulerEnableSub: 'Cambia automáticamente entre dos temas según la hora del día',
|
||
themeSchedulerDayTheme: 'Tema de Día',
|
||
themeSchedulerDayStart: 'Comienza de Día A las',
|
||
themeSchedulerNightTheme: 'Tema de Noche',
|
||
themeSchedulerNightStart: 'Comienza de Noche A las',
|
||
themeSchedulerActiveHint: 'El Programador de Temas está activo — los cambios de tema se manejan automáticamente.',
|
||
visualOptionsTitle: 'Opciones Visuales',
|
||
coverArtBackground: 'Fondo de Portada',
|
||
coverArtBackgroundSub: 'Mostrar portada desenfocada como fondo en encabezados de álbumes y listas de reproducción',
|
||
playlistCoverPhoto: 'Foto de Portada de Playlist',
|
||
playlistCoverPhotoSub: 'Mostrar cuadrícula de fotos de portada en la vista detallada de playlists',
|
||
showBitrate: 'Mostrar Bitrate',
|
||
showBitrateSub: 'Mostrar bitrate de audio en las listas de pistas',
|
||
floatingPlayerBar: 'Barra del Reproductor Flotante',
|
||
floatingPlayerBarSub: 'Mantener la barra del reproductor flotando sobre el contenido',
|
||
uiScaleTitle: 'Escala de Interfaz',
|
||
uiScaleLabel: 'Zoom',
|
||
confirmDeleteServerLibrary: '¿También quieres eliminar el índice local de biblioteca de este servidor? Pulsa Cancelar para conservar la copia en caché para uso sin conexión.',
|
||
waveformCacheClearBtn: 'Borrar caché de forma de onda',
|
||
waveformCacheCleared: 'Caché de forma de onda borrada ({{count}} filas).',
|
||
waveformCacheClearFailed: 'No se pudo borrar la caché de forma de onda.',
|
||
analyticsStrategyTitle: 'Estrategias de análisis',
|
||
analyticsStrategyDesc: 'Elige el estilo de análisis para la biblioteca.',
|
||
analyticsStrategyLabel: 'Estrategia',
|
||
analyticsStrategyLazy: 'Perezosa',
|
||
analyticsStrategyLazyDesc: 'Analiza solo cuando se reproduce una pista o entra en la caché caliente/sin conexión; uso mínimo de CPU y red.',
|
||
analyticsStrategyAdvanced: 'Agresiva',
|
||
analyticsStrategyAdvancedDesc: 'Escaneo en segundo plano más agresivo. Al terminar, obtienes BPM para todas las pistas y carga inmediata de loudness y forma de onda.',
|
||
analyticsStrategyPriorityTitle: 'Prioridad de análisis (siempre activa)',
|
||
analyticsStrategyPriorityHigh: '1 — Reproducción actual: descargar y analizar primero (bytes de reproducción, backfill de loudness o HTTP cuando haga falta).',
|
||
analyticsStrategyPriorityMiddle: '2 — Siguientes: ~5 pistas de cola y vecinas de caché caliente; analiza desde caché/descarga cuando sea posible, con backfill HTTP solo en fallo.',
|
||
analyticsStrategyPriorityLow: '3 — Lote de biblioteca (solo Agresiva): cola automática en segundo plano al final; se rellena por watermark (~3× workers) sin esperar a que quede vacía.',
|
||
analyticsStrategyServerLabel: 'Servidor',
|
||
analyticsStrategyProgressLabel: 'Progreso del análisis:',
|
||
analyticsStrategyProgressValue: '{{percent}}% ({{done}}/{{total}})',
|
||
analyticsStrategyActionsLabel: 'Acciones',
|
||
analyticsStrategyParallelismLabel: 'Workers en paralelo',
|
||
analyticsStrategyParallelismValue: '{{n}} hilos',
|
||
analyticsStrategyParallelismDesc: 'Cuántas pistas descargar y analizar al mismo tiempo (HTTP + CPU). Valores más altos aceleran el progreso, pero consumen más red y CPU.',
|
||
analyticsStrategyAdvancedWarning: 'La estrategia agresiva usa más CPU y ancho de banda mientras se ejecuta el lote de biblioteca en segundo plano.',
|
||
analyticsStrategyServerRemoved: 'Servidor eliminado',
|
||
analyticsStrategyClearAction: 'Borrar análisis',
|
||
analyticsStrategyClearTitle: '¿Borrar análisis?',
|
||
analyticsStrategyClearDesc: 'Borrar el análisis de {{server}} elimina las formas de onda y loudness en caché. Si vuelves a añadir este servidor, puede requerir una reindexación larga.',
|
||
analyticsStrategyClearCancel: 'Cancelar',
|
||
analyticsStrategyClearConfirm: 'Borrar análisis',
|
||
analyticsStrategyClearSuccess: 'Análisis borrado para el servidor eliminado.',
|
||
analyticsStrategyClearError: 'No se pudo borrar el análisis del servidor eliminado.',
|
||
analyticsStrategyProgressEmptyAfterFailed: '100% (0/0, bloqueado por pistas fallidas)',
|
||
analyticsFailedTracksOpenTitle: 'Pistas fallidas: {{count}}',
|
||
analyticsFailedTracksTitle: 'Pistas de análisis fallidas — {{server}}',
|
||
analyticsFailedTracksDesc: 'Estas pistas fallaron repetidamente en decode/enrichment y quedan excluidas del análisis automático en segundo plano.',
|
||
analyticsFailedTracksLoading: 'Cargando lista de pistas fallidas…',
|
||
analyticsFailedTracksEmpty: 'No hay pistas fallidas para este servidor.',
|
||
analyticsFailedTracksClose: 'Cerrar',
|
||
analyticsFailedTracksExport: 'Exportar lista',
|
||
analyticsFailedTracksExportSuccess: 'Lista de pistas fallidas exportada ({{count}}).',
|
||
analyticsFailedTracksExportError: 'No se pudo exportar la lista de pistas fallidas.',
|
||
analyticsFailedTracksRescan: 'Reanalizar pistas fallidas',
|
||
analyticsFailedTracksRescanSuccess: 'Reanálisis programado para {{count}} pistas fallidas.',
|
||
analyticsFailedTracksRescanError: 'No se pudo reanalizar las pistas fallidas.',
|
||
analyticsFailedTracksLoadError: 'No se pudo cargar las pistas fallidas.',
|
||
backupImportAny: 'Importar copia',
|
||
backupImportAnyConfirm: 'Importar el archivo de copia seleccionado y aplicar su contenido (ajustes, bases de datos de biblioteca o copia completa). ¿Continuar?',
|
||
backupModeFull: 'Completa',
|
||
backupModeLibrary: 'Bases de datos de biblioteca',
|
||
backupModeConfig: 'Configuración',
|
||
backupFullDesc: 'Archiva los ajustes y las bases de datos de la biblioteca en un único archivo de copia.',
|
||
backupFullExport: 'Exportar copia completa',
|
||
backupFullImport: 'Importar copia completa',
|
||
backupFullImportConfirm: 'Esto sobrescribirá los ajustes, reemplazará las bases de datos actuales y las moverá a .bak. ¿Continuar?',
|
||
backupFullExportSuccess: 'Copia completa exportada.',
|
||
backupFullImportSuccess: 'Copia completa importada — recargando…',
|
||
backupFullImportError: 'No se pudo importar la copia completa.',
|
||
backupLibraryExport: 'Exportar bases de datos',
|
||
backupLibraryExportDesc: 'Crea un archivo comprimido con instantáneas SQLite limpias de las bases de datos de biblioteca.',
|
||
backupLibraryImport: 'Importar bases de datos',
|
||
backupLibraryImportDesc: 'Restaura las bases de datos de biblioteca desde un archivo. Las bases actuales se mueven a .bak antes del cambio.',
|
||
backupLibraryImportConfirm: 'Esto reemplazará las bases de datos actuales y las moverá a .bak. ¿Continuar?',
|
||
backupLibraryExportSuccess: 'Copia de bases de datos exportada.',
|
||
backupLibraryImportSuccess: 'Copia de bases de datos importada.',
|
||
backupLibraryImportError: 'No se pudo importar la copia de bases de datos.',
|
||
backupOverlayExportTitle: 'Creando copia…',
|
||
backupOverlayImportTitle: 'Restaurando copia…',
|
||
backupOverlayHint: 'Puedes mantener la ventana abierta mientras procesamos archivos. La entrada está bloqueada temporalmente.',
|
||
};
|