- MusicBrainz Cache
+ {$t('settings.integrations.musicbrainzCache')}
{#if musicBrainzCacheStats}
{musicBrainzCacheStats.artists} artists, {musicBrainzCacheStats.relations} relations, {musicBrainzCacheStats.recordings} recordings
@@ -2809,7 +2809,7 @@
onclick={handleClearAllCaches}
disabled={isClearingAllCaches}
>
- {isClearingAllCaches ? $t('settings.storage.clearing') : 'Clear All'}
+ {isClearingAllCaches ? $t('settings.storage.clearing') : $t('actions.clearAll')}
{/if}
diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json
index 48d35935e..c0962bc2d 100644
--- a/src/lib/i18n/locales/en.json
+++ b/src/lib/i18n/locales/en.json
@@ -139,7 +139,29 @@
"releaseYear": "Release Year",
"artistName": "Artist Name",
"alphabetical": "Alphabetical",
- "group": "Group:"
+ "group": "Group:",
+ "backToLibrary": "Back to Library",
+ "yourCollection": "Your local music collection",
+ "playlistOfflineMode": "Playlist management is disabled in offline mode. You can still play your local tracks.",
+ "artworkOffline": "Artwork fetching unavailable offline",
+ "loadingLibrary": "Loading library...",
+ "failedLoadLibrary": "Failed to load library",
+ "backToActiveAlbums": "Back to Active Albums",
+ "noHiddenAlbums": "No hidden albums",
+ "noAlbumsInLibrary": "No albums in library",
+ "addFoldersHint": "Add folders and scan to build your library",
+ "clearAllFilters": "Clear all",
+ "offlineCache": "Offline cache",
+ "noAlbumsMatch": "No albums match your search",
+ "tryDifferentSearch": "Try a different artist or album name",
+ "noArtistsInLibrary": "No artists in library",
+ "noArtistsMatch": "No artists match your search",
+ "noAlbumsFound": "No albums found",
+ "selectArtist": "Select an artist to view their albums",
+ "noTracksInLibrary": "No tracks in library",
+ "editAlbumInfo": "Edit album info",
+ "hideAlbum": "Hide this album from library",
+ "hideAlbumHint": "Hidden albums can be viewed from Settings"
},
"album": {
"tracks": "tracks",
@@ -359,8 +381,10 @@
"edit": "Edit",
"close": "Close",
"open": "Open",
+ "show": "Show",
"refresh": "Refresh",
"retry": "Retry",
+ "repair": "Repair",
"clear": "Clear",
"clearAll": "Clear All",
"confirm": "Confirm",
@@ -388,6 +412,7 @@
"streamingQualityDesc": "Maximum quality for streaming and playback cache",
"qualityChangedCacheCleared": "Quality changed. Playback cache cleared.",
"outputDevice": "Output Device",
+ "outputDeviceDesc": "Select your preferred audio output device. Devices shown are from the selected backend.",
"audioDevice": "Audio Device",
"audioDeviceDesc": "Select your preferred audio output device. Devices shown are from the selected backend.",
"exclusiveMode": "Exclusive Mode",
@@ -400,6 +425,28 @@
"audioBackendDesc": "Choose audio system: Auto (recommended), PipeWire (modern), ALSA Direct (bit-perfect, exclusive), or PulseAudio (legacy).",
"currentSampleRate": "Current Sample Rate",
"noActivePlayback": "No active playback",
+ "loadingDevices": "Loading devices...",
+ "loadingAudioDevices": "Loading audio devices...",
+ "parsingHardware": "Parsing hardware information",
+ "limitQualityToDevice": "Limit Quality to Device",
+ "limitQualityToDeviceDesc": "When enabled, overrides the quality setting above if your audio device doesn't support the selected sample rate. Prevents resampling to guarantee bit-perfect playback.",
+ "audioBackend": "Audio Backend",
+ "audioBackendDesc": "Choose audio system: Auto (recommended), PipeWire (modern), ALSA Direct (bit-perfect, exclusive), or PulseAudio (legacy).",
+ "alsaPlugin": "ALSA Plugin",
+ "alsaPluginDesc": "hw: Bit-perfect, exclusive. plughw: Auto-convert. pcm: Most compatible.",
+ "hardwareVolume": "Enable Hardware Volume Control",
+ "hardwareVolumeDesc": "Experimental: Controls DAC volume via ALSA mixer. Some DACs don't support this - disable for maximum compatibility. If it fails, playback continues normally.",
+ "helpBitPerfect": "Help with bit-perfect device detection",
+ "flatpakWarningTitle": "Flatpak Limitation:",
+ "flatpakWarningDesc": "PipeWire cannot guarantee bit-perfect playback in sandboxed environments due to daemon access restrictions.",
+ "flatpakRecommended": "Recommended:",
+ "flatpakRecommendedDesc": "Switch to ALSA Direct backend for true bit-perfect audio.",
+ "streamUncached": "Stream Uncached Tracks",
+ "streamUncachedDesc": "Start playback faster when track is not in cache. Seeking may be limited during initial buffering.",
+ "initialBuffer": "Initial Buffer Size",
+ "initialBufferDesc": "Seconds to buffer before starting playback ({seconds}s)",
+ "streamingOnly": "Streaming Only",
+ "streamingOnlyDesc": "Disables temporary cache. Not recommended without a fast connection. Offline library tracks always play first.",
"streamBuffer": "Stream Buffering",
"streamBufferDesc": "Start playback faster when track is not in cache. Seeking may be limited during initial buffering.",
"streamBufferSeconds": "Seconds to buffer before starting playback ({seconds}s)",
@@ -489,10 +536,15 @@
"connect": "Connect",
"disconnect": "Disconnect",
"scrobbling": "Scrobbling",
- "configureApi": "Configure API"
+ "configureApi": "Configure API",
+ "musicbrainz": "MusicBrainz",
+ "musicbrainzDesc": "Enable artist relationships and enhanced metadata from MusicBrainz.",
+ "musicbrainzCache": "MusicBrainz Cache",
+ "listenbrainz": "ListenBrainz"
},
"storage": {
"title": "Storage",
+ "sectionSummary": "Queue, lyrics, artwork, and metadata caches",
"queueCacheNote": "Queue list storage. Clear if you have playback issues. (This is not the Offline Library cache.)",
"queueCacheStats": "{tracks} tracks using {used} / {max}",
"cachedTracks": "Cached Queue Tracks",
@@ -500,6 +552,25 @@
"clearCache": "Clear queue cache",
"clearing": "Clearing..."
},
+ "blacklist": {
+ "title": "Artist Blacklist",
+ "description": "Blacklisted artists are hidden from search results, radio, suggestions, and similar artists.",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "clearAll": "Clear All",
+ "noArtists": "No blacklisted artists",
+ "noArtistsHint": "To blacklist an artist, go to their page and click the ban icon.",
+ "artistCount": "{count} artists",
+ "loadingBlacklist": "Loading blacklist...",
+ "confirmClear": "Remove all artists from blacklist?",
+ "disabledWarning": "Blacklist is currently disabled. Artists below will appear in results.",
+ "searchPlaceholder": "Search blacklisted artists...",
+ "noResults": "No results for \"{query}\"",
+ "addedOn": "Added {date}",
+ "remove": "Remove from blacklist",
+ "confirmClearTitle": "Clear Blacklist?",
+ "confirmClearText": "This will remove all {count} artists from your blacklist. This action cannot be undone."
+ },
"offlineLibrary": {
"title": "Offline Library",
"disclaimer": "The offline library is a temporary playback cache for offline listening while you have a valid Qobuz subscription. You do not receive a license to keep or redistribute the content. If your subscription becomes invalid, QBZ will remove all cached content after 3 days.",
@@ -507,9 +578,23 @@
"cachedTracksDesc": "Cached tracks for offline playback",
"clearCache": "Clear All",
"openFolder": "Open Folder",
- "repair": "Repair",
+ "repair": "Repair Offline Library",
"repairDesc": "Fix offline markers lost during library scans",
- "openFolderDesc": "Open the cache folder in your file manager"
+ "manageCache": "Manage Offline Cache",
+ "manageCacheDesc": "Open the cache folder in your file manager",
+ "openFolderDesc": "Open the cache folder in your file manager",
+ "sectionSummary": "Cached tracks for offline playback",
+ "repairing": "Repairing..."
+ },
+ "updates": {
+ "title": "Updates",
+ "checkOnLaunch": "Check for new releases on launch",
+ "checkNow": "Check for updates now",
+ "checking": "Checking...",
+ "check": "Check",
+ "showWhatsNew": "Show what's new on new version launch",
+ "showChangelog": "Show changelog for current version",
+ "currentVersion": "Current version: v{version}"
},
"networkCacheWarning": "The selected folder is on a network drive.",
"networkCacheWarningDetail": "Cached tracks may not be available when the network is disconnected or in offline mode. Are you sure you want to use this location?",
@@ -623,12 +708,120 @@
"customGreeting": "Custom greeting",
"customGreetingDesc": "Use {name} for your name. Leave empty for time-based.",
"customGreetingPlaceholder": "Good vibes, {name}!",
+ "goodMorning": "Good morning, {name}",
+ "goodAfternoon": "Good afternoon, {name}",
+ "goodEvening": "Good evening, {name}",
+ "goodNight": "Good night, {name}",
"sections": "Sections",
"sectionsDesc": "Reorder with arrows. Toggle visibility with switch.",
"moveUp": "Move up",
"moveDown": "Move down",
"resetToDefaults": "Reset to defaults"
},
+ "toast": {
+ "loadingAlbum": "Loading album...",
+ "loadingArtist": "Loading artist...",
+ "loadingPlaylist": "Loading playlist...",
+ "lookingUp": "Looking up {name}...",
+ "failedLoadAlbum": "Failed to load album",
+ "failedLoadArtist": "Failed to load artist",
+ "failedLoadPlaylist": "Failed to load playlist",
+ "failedLoadMoreAlbums": "Failed to load more albums",
+ "failedAddToQueue": "Failed to add to queue",
+ "failedPlayTrack": "Failed to play track",
+ "queueCleared": "Queue cleared",
+ "queueEmpty": "Queue is empty",
+ "queueEnded": "Queue ended",
+ "failedClearQueue": "Failed to clear queue",
+ "shuffleEnabled": "Shuffle play enabled",
+ "shuffleDisabled": "Shuffle disabled",
+ "repeatOff": "Repeat: Off",
+ "repeatOne": "Repeat: One",
+ "repeatAll": "Repeat: All",
+ "infinitePlayEnabled": "Infinite play enabled",
+ "infinitePlayDisabled": "Infinite play disabled",
+ "linkCopied": "Link copied to clipboard",
+ "albumLinkCopied": "Album link copied to clipboard",
+ "playlistLinkCopied": "Playlist link copied to clipboard",
+ "trackLinkCopied": "Track link copied to clipboard",
+ "fetchingSonglink": "Fetching song.link...",
+ "fetchingAlbumLink": "Fetching Album.link...",
+ "songlinkCopied": "song.link copied to clipboard",
+ "albumLinkCopiedSonglink": "Album.link copied to clipboard",
+ "songlinkError": "song.link error: {error}",
+ "albumLinkError": "Album.link error: {error}",
+ "allTracksOffline": "All tracks already available offline",
+ "removedFromOffline": "Removed from offline library",
+ "failedRemoveOffline": "Failed to remove from offline library",
+ "failedOpenFolder": "Failed to open folder",
+ "failedOpenAlbumFolder": "Failed to open album folder",
+ "playlistNoTracks": "Playlist has no tracks",
+ "copiedToLibrary": "Copied to library",
+ "copyingToLibrary": "Copying to library...",
+ "playlistCopied": "Playlist copied to library",
+ "failedCopyPlaylist": "Failed to copy playlist: {error}",
+ "removedFromFavorites": "Removed from favorites",
+ "playlistRemovedFavorites": "Playlist removed from favorites",
+ "failedRemoveFavorites": "Failed to remove from favorites: {error}",
+ "failedUpdateFavorites": "Failed to update favorites",
+ "addedToFavorites": "Added to favorites",
+ "noAvailableTracks": "No available tracks in queue (offline mode)",
+ "failedReorderQueue": "Failed to reorder queue",
+ "trackNotInHistory": "Track not found in history",
+ "failedNavigateSource": "Failed to navigate to source",
+ "failedLookupMusician": "Could not look up musician info",
+ "couldNotFetchDetails": "Could not fetch details",
+ "failedPrepareOffline": "Failed to prepare for offline",
+ "failedRefreshOffline": "Failed to refresh offline copy",
+ "preparingTrackOffline": "Preparing \"{title}\" for offline",
+ "preparingTracksOffline": "Preparing {count} tracks from \"{album}\" for offline",
+ "refreshingTrackOffline": "Refreshing \"{title}\" for offline",
+ "refreshingAlbumOffline": "Refreshing all tracks from \"{album}\" for offline",
+ "failedLoadAlbumRefresh": "Failed to load album for offline refresh",
+ "playingTracksNext": "Playing {count} tracks next",
+ "addedTracksToQueue": "Added {count} tracks to queue",
+ "failedPreviousTrack": "Failed to go to previous track",
+ "failedNextTrack": "Failed to go to next track",
+ "tracksAddedToPlaylist": "Track(s) added to playlist",
+ "playlistCreated": "Playlist created",
+ "offlineModeStarted": "Started in offline mode",
+ "welcomeUser": "Welcome, {name}!",
+ "restoredTrack": "Restored: {title}",
+ "logoutSuccess": "Logged out successfully",
+ "failedLogout": "Failed to logout",
+ "localTrackSearch": "Local track - search for artist in Search",
+ "queuedPlayNext": "Queued to play next",
+ "failedQueueTrack": "Failed to queue track",
+ "addedToQueue": "Added to queue",
+ "offlineModeActivated": "Offline mode activated",
+ "backOnline": "Back online",
+ "syncedPlaylist": "Synced playlist \"{name}\"",
+ "failedSyncPlaylist": "Failed to sync playlist \"{name}\": {error}",
+ "playbackError": "Playback error: {error}",
+ "trackUnavailable": "\"{title}\" is no longer available",
+ "playing": "Playing: {title}",
+ "failedCopyLink": "Failed to copy link",
+ "failedShareQobuz": "Failed to share Qobuz link: {error}",
+ "playlistCreatedOffline": "Playlist created offline - will sync when back online",
+ "playlistImported": "Playlist imported",
+ "playlistImportFailed": "Playlist import failed",
+ "enterSearchTerms": "Enter album title or artist to search",
+ "searchFailed": "Search failed: {error}",
+ "appliedMetadata": "Applied metadata from {provider}",
+ "rateLimited": "Rate limited. Please wait a moment and try again.",
+ "failedFetchMetadata": "Failed to fetch metadata: {error}",
+ "failedOpenBrowser": "Failed to open browser",
+ "albumMetadataSaved": "Album metadata saved",
+ "removedMissingFiles": "Removed {count} tracks with missing files",
+ "noMissingFilesFound": "No missing files found",
+ "failedCleanupMissing": "Failed to cleanup missing files",
+ "metadataRefreshed": "Metadata refreshed from files",
+ "failedSaveTray": "Failed to save tray setting",
+ "failedSaveAutoplay": "Failed to save autoplay preference",
+ "failedSaveIconVisibility": "Failed to save icon visibility preference",
+ "failedRepairOffline": "Failed to repair offline library: {error}",
+ "failedOpenCacheFolder": "Failed to open cache folder"
+ },
"offline": {
"title": "Offline Mode",
"message": "Connect to the internet to access Qobuz™ content.",
diff --git a/src/lib/i18n/locales/es.json b/src/lib/i18n/locales/es.json
index 36ebbe051..bb9f21ae3 100644
--- a/src/lib/i18n/locales/es.json
+++ b/src/lib/i18n/locales/es.json
@@ -139,7 +139,29 @@
"releaseYear": "Año de Lanzamiento",
"artistName": "Nombre del Artista",
"alphabetical": "Alfabético",
- "group": "Agrupar:"
+ "group": "Agrupar:",
+ "backToLibrary": "Volver a la Biblioteca",
+ "yourCollection": "Tu colección de música local",
+ "playlistOfflineMode": "La gestión de playlists está deshabilitada en modo offline. Aún puedes reproducir tus pistas locales.",
+ "artworkOffline": "Obtención de carátulas no disponible offline",
+ "loadingLibrary": "Cargando biblioteca...",
+ "failedLoadLibrary": "Error al cargar la biblioteca",
+ "backToActiveAlbums": "Volver a Álbumes Activos",
+ "noHiddenAlbums": "No hay álbumes ocultos",
+ "noAlbumsInLibrary": "No hay álbumes en la biblioteca",
+ "addFoldersHint": "Agrega carpetas y escanea para construir tu biblioteca",
+ "clearAllFilters": "Limpiar todo",
+ "offlineCache": "Caché offline",
+ "noAlbumsMatch": "Ningún álbum coincide con tu búsqueda",
+ "tryDifferentSearch": "Prueba con un artista o álbum diferente",
+ "noArtistsInLibrary": "No hay artistas en la biblioteca",
+ "noArtistsMatch": "Ningún artista coincide con tu búsqueda",
+ "noAlbumsFound": "No se encontraron álbumes",
+ "selectArtist": "Selecciona un artista para ver sus álbumes",
+ "noTracksInLibrary": "No hay pistas en la biblioteca",
+ "editAlbumInfo": "Editar información del álbum",
+ "hideAlbum": "Ocultar este álbum de la biblioteca",
+ "hideAlbumHint": "Los álbumes ocultos se pueden ver desde Configuración"
},
"album": {
"tracks": "pistas",
@@ -359,8 +381,10 @@
"edit": "Editar",
"close": "Cerrar",
"open": "Abrir",
+ "show": "Mostrar",
"refresh": "Actualizar",
"retry": "Reintentar",
+ "repair": "Reparar",
"clear": "Limpiar",
"clearAll": "Limpiar Todo",
"confirm": "Confirmar",
@@ -388,6 +412,7 @@
"streamingQualityDesc": "Calidad máxima para streaming y caché de reproducción",
"qualityChangedCacheCleared": "Calidad cambiada. Caché de reproducción limpiado.",
"outputDevice": "Dispositivo de Salida",
+ "outputDeviceDesc": "Selecciona tu dispositivo de audio preferido. Se muestran los dispositivos del backend seleccionado.",
"audioDevice": "Dispositivo de Audio",
"audioDeviceDesc": "Selecciona tu dispositivo de salida de audio preferido. Los dispositivos mostrados son del backend seleccionado.",
"exclusiveMode": "Modo Exclusivo",
@@ -400,6 +425,28 @@
"audioBackendDesc": "Elige el sistema de audio: Auto (recomendado), PipeWire (moderno), ALSA Direct (bit-perfect, exclusivo), o PulseAudio (legado).",
"currentSampleRate": "Tasa de Muestreo Actual",
"noActivePlayback": "Sin reproducción activa",
+ "loadingDevices": "Cargando dispositivos...",
+ "loadingAudioDevices": "Cargando dispositivos de audio...",
+ "parsingHardware": "Analizando información de hardware",
+ "limitQualityToDevice": "Limitar Calidad al Dispositivo",
+ "limitQualityToDeviceDesc": "Cuando está activado, anula la configuración de calidad si tu dispositivo de audio no soporta la tasa de muestreo seleccionada. Previene el remuestreo para garantizar reproducción bit-perfect.",
+ "audioBackend": "Backend de Audio",
+ "audioBackendDesc": "Elige el sistema de audio: Auto (recomendado), PipeWire (moderno), ALSA Direct (bit-perfect, exclusivo), o PulseAudio (legacy).",
+ "alsaPlugin": "Plugin ALSA",
+ "alsaPluginDesc": "hw: Bit-perfect, exclusivo. plughw: Auto-conversión. pcm: Más compatible.",
+ "hardwareVolume": "Habilitar Control de Volumen por Hardware",
+ "hardwareVolumeDesc": "Experimental: Controla el volumen del DAC vía mixer ALSA. Algunos DACs no lo soportan - desactívalo para máxima compatibilidad. Si falla, la reproducción continúa normalmente.",
+ "helpBitPerfect": "Ayuda con detección de dispositivos bit-perfect",
+ "flatpakWarningTitle": "Limitación de Flatpak:",
+ "flatpakWarningDesc": "PipeWire no puede garantizar reproducción bit-perfect en entornos sandbox debido a restricciones de acceso al daemon.",
+ "flatpakRecommended": "Recomendado:",
+ "flatpakRecommendedDesc": "Cambiar al backend ALSA Direct para audio bit-perfect verdadero.",
+ "streamUncached": "Transmitir Pistas Sin Caché",
+ "streamUncachedDesc": "Inicia la reproducción más rápido cuando la pista no está en caché. La búsqueda puede estar limitada durante el buffering inicial.",
+ "initialBuffer": "Tamaño del Buffer Inicial",
+ "initialBufferDesc": "Segundos a almacenar en buffer antes de iniciar reproducción ({seconds}s)",
+ "streamingOnly": "Solo Streaming",
+ "streamingOnlyDesc": "Desactiva el caché temporal. No recomendado sin una conexión rápida. Las pistas de la biblioteca offline siempre se reproducen primero.",
"streamBuffer": "Buffering de Stream",
"streamBufferDesc": "Inicia la reproducción más rápido cuando la pista no está en caché. La búsqueda puede estar limitada durante el buffering inicial.",
"streamBufferSeconds": "Segundos a almacenar antes de iniciar reproducción ({seconds}s)",
@@ -489,10 +536,15 @@
"connect": "Conectar",
"disconnect": "Desconectar",
"scrobbling": "Scrobbling",
- "configureApi": "Configurar API"
+ "configureApi": "Configurar API",
+ "musicbrainz": "MusicBrainz",
+ "musicbrainzDesc": "Habilita relaciones de artistas y metadatos mejorados de MusicBrainz.",
+ "musicbrainzCache": "Caché de MusicBrainz",
+ "listenbrainz": "ListenBrainz"
},
"storage": {
"title": "Almacenamiento",
+ "sectionSummary": "Cachés de cola, letras, carátulas y metadatos",
"queueCacheNote": "Almacenamiento de la cola. Límpialo si tienes problemas de reproducción. (No es la Biblioteca Offline.)",
"queueCacheStats": "{tracks} pistas usando {used} / {max}",
"cachedTracks": "Pistas de la cola en caché",
@@ -500,6 +552,25 @@
"clearCache": "Limpiar caché de la cola",
"clearing": "Limpiando..."
},
+ "blacklist": {
+ "title": "Lista Negra de Artistas",
+ "description": "Los artistas en la lista negra están ocultos en resultados de búsqueda, radio, sugerencias y artistas similares.",
+ "enabled": "Activado",
+ "disabled": "Desactivado",
+ "clearAll": "Limpiar Todo",
+ "noArtists": "No hay artistas en la lista negra",
+ "noArtistsHint": "Para añadir un artista a la lista negra, ve a su página y haz clic en el ícono de prohibición.",
+ "artistCount": "{count} artistas",
+ "loadingBlacklist": "Cargando lista negra...",
+ "confirmClear": "¿Eliminar todos los artistas de la lista negra?",
+ "disabledWarning": "La lista negra está desactivada. Los artistas de abajo aparecerán en los resultados.",
+ "searchPlaceholder": "Buscar artistas en la lista negra...",
+ "noResults": "Sin resultados para \"{query}\"",
+ "addedOn": "Añadido {date}",
+ "remove": "Eliminar de la lista negra",
+ "confirmClearTitle": "¿Limpiar Lista Negra?",
+ "confirmClearText": "Esto eliminará los {count} artistas de tu lista negra. Esta acción no se puede deshacer."
+ },
"offlineLibrary": {
"title": "Biblioteca Offline",
"disclaimer": "La biblioteca offline es un caché temporal de reproducción para escucha sin conexión mientras tengas una suscripción válida de Qobuz. No recibes una licencia para conservar o redistribuir el contenido. Si tu suscripción deja de ser válida, QBZ eliminará todo el contenido en caché después de 3 días.",
@@ -507,9 +578,23 @@
"cachedTracksDesc": "Pistas en caché para reproducción offline",
"clearCache": "Limpiar Todo",
"openFolder": "Abrir Carpeta",
- "repair": "Reparar",
+ "repair": "Reparar Biblioteca Offline",
"repairDesc": "Corregir marcadores offline perdidos durante escaneos de biblioteca",
- "openFolderDesc": "Abrir la carpeta de caché en tu gestor de archivos"
+ "manageCache": "Administrar Caché Offline",
+ "manageCacheDesc": "Abrir la carpeta de caché en tu administrador de archivos",
+ "openFolderDesc": "Abrir la carpeta de caché en tu gestor de archivos",
+ "sectionSummary": "Pistas en caché para reproducción offline",
+ "repairing": "Reparando..."
+ },
+ "updates": {
+ "title": "Actualizaciones",
+ "checkOnLaunch": "Buscar nuevas versiones al iniciar",
+ "checkNow": "Buscar actualizaciones ahora",
+ "checking": "Verificando...",
+ "check": "Verificar",
+ "showWhatsNew": "Mostrar novedades al lanzar nueva versión",
+ "showChangelog": "Mostrar registro de cambios para la versión actual",
+ "currentVersion": "Versión actual: v{version}"
},
"networkCacheWarning": "La carpeta seleccionada está en una unidad de red.",
"networkCacheWarningDetail": "Las pistas en caché pueden no estar disponibles cuando la red esté desconectada o en modo offline. ¿Estás seguro de que quieres usar esta ubicación?",
@@ -623,12 +708,120 @@
"customGreeting": "Saludo personalizado",
"customGreetingDesc": "Usa {name} para tu nombre. Deja vacío para basado en hora.",
"customGreetingPlaceholder": "¡Buenas vibras, {name}!",
+ "goodMorning": "Buenos días, {name}",
+ "goodAfternoon": "Buenas tardes, {name}",
+ "goodEvening": "Buenas noches, {name}",
+ "goodNight": "Buenas noches, {name}",
"sections": "Secciones",
"sectionsDesc": "Reordena con flechas. Activa/desactiva con interruptor.",
"moveUp": "Subir",
"moveDown": "Bajar",
"resetToDefaults": "Restablecer valores"
},
+ "toast": {
+ "loadingAlbum": "Cargando álbum...",
+ "loadingArtist": "Cargando artista...",
+ "loadingPlaylist": "Cargando playlist...",
+ "lookingUp": "Buscando {name}...",
+ "failedLoadAlbum": "Error al cargar álbum",
+ "failedLoadArtist": "Error al cargar artista",
+ "failedLoadPlaylist": "Error al cargar playlist",
+ "failedLoadMoreAlbums": "Error al cargar más álbumes",
+ "failedAddToQueue": "Error al agregar a la cola",
+ "failedPlayTrack": "Error al reproducir pista",
+ "queueCleared": "Cola limpiada",
+ "queueEmpty": "La cola está vacía",
+ "queueEnded": "Cola terminada",
+ "failedClearQueue": "Error al limpiar cola",
+ "shuffleEnabled": "Reproducción aleatoria activada",
+ "shuffleDisabled": "Reproducción aleatoria desactivada",
+ "repeatOff": "Repetir: Desactivado",
+ "repeatOne": "Repetir: Una",
+ "repeatAll": "Repetir: Todas",
+ "infinitePlayEnabled": "Reproducción infinita activada",
+ "infinitePlayDisabled": "Reproducción infinita desactivada",
+ "linkCopied": "Enlace copiado al portapapeles",
+ "albumLinkCopied": "Enlace del álbum copiado",
+ "playlistLinkCopied": "Enlace de la playlist copiado",
+ "trackLinkCopied": "Enlace de la pista copiado",
+ "fetchingSonglink": "Obteniendo song.link...",
+ "fetchingAlbumLink": "Obteniendo Album.link...",
+ "songlinkCopied": "song.link copiado al portapapeles",
+ "albumLinkCopiedSonglink": "Album.link copiado al portapapeles",
+ "songlinkError": "Error en song.link: {error}",
+ "albumLinkError": "Error en Album.link: {error}",
+ "allTracksOffline": "Todas las pistas ya están disponibles offline",
+ "removedFromOffline": "Eliminado de la biblioteca offline",
+ "failedRemoveOffline": "Error al eliminar de biblioteca offline",
+ "failedOpenFolder": "Error al abrir carpeta",
+ "failedOpenAlbumFolder": "Error al abrir carpeta del álbum",
+ "playlistNoTracks": "La playlist no tiene pistas",
+ "copiedToLibrary": "Copiado a la biblioteca",
+ "copyingToLibrary": "Copiando a la biblioteca...",
+ "playlistCopied": "Playlist copiada a la biblioteca",
+ "failedCopyPlaylist": "Error al copiar playlist: {error}",
+ "removedFromFavorites": "Eliminado de favoritos",
+ "playlistRemovedFavorites": "Playlist eliminada de favoritos",
+ "failedRemoveFavorites": "Error al eliminar de favoritos: {error}",
+ "failedUpdateFavorites": "Error al actualizar favoritos",
+ "addedToFavorites": "Agregado a favoritos",
+ "noAvailableTracks": "Sin pistas disponibles en la cola (modo offline)",
+ "failedReorderQueue": "Error al reordenar cola",
+ "trackNotInHistory": "Pista no encontrada en historial",
+ "failedNavigateSource": "Error al navegar al origen",
+ "failedLookupMusician": "No se pudo buscar info del músico",
+ "couldNotFetchDetails": "No se pudieron obtener los detalles",
+ "failedPrepareOffline": "Error al preparar para offline",
+ "failedRefreshOffline": "Error al actualizar copia offline",
+ "preparingTrackOffline": "Preparando \"{title}\" para offline",
+ "preparingTracksOffline": "Preparando {count} pistas de \"{album}\" para offline",
+ "refreshingTrackOffline": "Actualizando \"{title}\" para offline",
+ "refreshingAlbumOffline": "Actualizando todas las pistas de \"{album}\" para offline",
+ "failedLoadAlbumRefresh": "Error al cargar álbum para actualizar offline",
+ "playingTracksNext": "Reproduciendo {count} pistas a continuación",
+ "addedTracksToQueue": "{count} pistas agregadas a la cola",
+ "failedPreviousTrack": "Error al ir a pista anterior",
+ "failedNextTrack": "Error al ir a siguiente pista",
+ "tracksAddedToPlaylist": "Pista(s) agregada(s) a la playlist",
+ "playlistCreated": "Playlist creada",
+ "offlineModeStarted": "Iniciado en modo offline",
+ "welcomeUser": "¡Bienvenido, {name}!",
+ "restoredTrack": "Restaurado: {title}",
+ "logoutSuccess": "Sesión cerrada exitosamente",
+ "failedLogout": "Error al cerrar sesión",
+ "localTrackSearch": "Pista local - busca el artista en Búsqueda",
+ "queuedPlayNext": "En cola para reproducir siguiente",
+ "failedQueueTrack": "Error al agregar pista a la cola",
+ "addedToQueue": "Agregado a la cola",
+ "offlineModeActivated": "Modo offline activado",
+ "backOnline": "De vuelta en línea",
+ "syncedPlaylist": "Playlist \"{name}\" sincronizada",
+ "failedSyncPlaylist": "Error al sincronizar playlist \"{name}\": {error}",
+ "playbackError": "Error de reproducción: {error}",
+ "trackUnavailable": "\"{title}\" ya no está disponible",
+ "playing": "Reproduciendo: {title}",
+ "failedCopyLink": "Error al copiar enlace",
+ "failedShareQobuz": "Error al compartir enlace de Qobuz: {error}",
+ "playlistCreatedOffline": "Playlist creada offline - se sincronizará cuando vuelvas a estar en línea",
+ "playlistImported": "Playlist importada",
+ "playlistImportFailed": "Error al importar playlist",
+ "enterSearchTerms": "Ingresa título del álbum o artista para buscar",
+ "searchFailed": "Error en búsqueda: {error}",
+ "appliedMetadata": "Metadatos aplicados de {provider}",
+ "rateLimited": "Límite de consultas. Por favor espera un momento e intenta de nuevo.",
+ "failedFetchMetadata": "Error al obtener metadatos: {error}",
+ "failedOpenBrowser": "Error al abrir navegador",
+ "albumMetadataSaved": "Metadatos del álbum guardados",
+ "removedMissingFiles": "{count} pistas con archivos faltantes eliminadas",
+ "noMissingFilesFound": "No se encontraron archivos faltantes",
+ "failedCleanupMissing": "Error al limpiar archivos faltantes",
+ "metadataRefreshed": "Metadatos actualizados desde archivos",
+ "failedSaveTray": "Error al guardar configuración de bandeja",
+ "failedSaveAutoplay": "Error al guardar preferencia de reproducción automática",
+ "failedSaveIconVisibility": "Error al guardar preferencia de visibilidad de iconos",
+ "failedRepairOffline": "Error al reparar biblioteca offline: {error}",
+ "failedOpenCacheFolder": "Error al abrir carpeta de caché"
+ },
"offline": {
"title": "Modo Offline",
"message": "Conecta a internet para acceder al contenido de Qobuz™.",
diff --git a/src/lib/stores/homeSettingsStore.ts b/src/lib/stores/homeSettingsStore.ts
index 0a418091d..ccf8bd3db 100644
--- a/src/lib/stores/homeSettingsStore.ts
+++ b/src/lib/stores/homeSettingsStore.ts
@@ -280,7 +280,7 @@ export function setCustomGreeting(text: string | null): void {
}
/**
- * Get greeting text for display
+ * Get greeting text for display (legacy - use getGreetingInfo for i18n support)
*/
export function getGreetingText(userName: string): string {
if (settings.greeting.customText) {
@@ -290,7 +290,45 @@ export function getGreetingText(userName: string): string {
}
/**
- * Get time-based greeting
+ * Get greeting info for i18n-aware display
+ * Returns either custom text or a translation key with params
+ */
+export function getGreetingInfo(userName: string): { type: 'custom'; text: string } | { type: 'key'; key: string; name: string } {
+ const firstName = getFirstName(userName);
+
+ if (settings.greeting.customText) {
+ return {
+ type: 'custom',
+ text: settings.greeting.customText.replace('{name}', firstName)
+ };
+ }
+
+ return {
+ type: 'key',
+ key: getTimeBasedGreetingKey(),
+ name: firstName
+ };
+}
+
+/**
+ * Get time-based greeting translation key
+ */
+export function getTimeBasedGreetingKey(): string {
+ const hour = new Date().getHours();
+
+ if (hour >= 5 && hour < 12) {
+ return 'homeSettings.goodMorning';
+ } else if (hour >= 12 && hour < 17) {
+ return 'homeSettings.goodAfternoon';
+ } else if (hour >= 17 && hour < 21) {
+ return 'homeSettings.goodEvening';
+ } else {
+ return 'homeSettings.goodNight';
+ }
+}
+
+/**
+ * Get time-based greeting (legacy)
*/
function getTimeBasedGreeting(userName: string): string {
const hour = new Date().getHours();
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 5729548a1..aca621b03 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -225,6 +225,9 @@
shareSonglinkTrack
} from '$lib/services/trackActions';
+ // Internationalization
+ import { t } from '$lib/i18n';
+
// App bootstrap
import { bootstrapApp } from '$lib/app/bootstrap';
@@ -558,7 +561,7 @@
async function handleAlbumClick(albumId: string) {
try {
- showToast('Loading album...', 'info');
+ showToast($t('toast.loadingAlbum'), 'info');
const album = await invoke
('get_album', { albumId });
console.log('Album details:', album);
@@ -574,7 +577,7 @@
}
} catch (err) {
console.error('Failed to load album:', err);
- showToast('Failed to load album', 'error');
+ showToast($t('toast.failedLoadAlbum'), 'error');
}
}
@@ -636,7 +639,7 @@
async function handleArtistClick(artistId: number) {
try {
- showToast('Loading artist...', 'info');
+ showToast($t('toast.loadingArtist'), 'info');
const artist = await invoke('get_artist_detail', { artistId });
console.log('Artist details:', artist);
@@ -645,7 +648,7 @@
hideToast();
} catch (err) {
console.error('Failed to load artist:', err);
- showToast('Failed to load artist', 'error');
+ showToast($t('toast.failedLoadArtist'), 'error');
}
}
@@ -662,7 +665,7 @@
* - Weak (1), None (0): Show Informational Modal
*/
async function handleMusicianClick(name: string, role: string) {
- showToast(`Looking up ${name}...`, 'info');
+ showToast($t('toast.lookingUp', { values: { name } }), 'info');
try {
const musician = await invoke('resolve_musician', { name, role });
console.log('Resolved musician:', musician);
@@ -693,7 +696,7 @@
}
} catch (err) {
console.error('Failed to resolve musician:', err);
- showToast('Could not look up musician info', 'error');
+ showToast($t('toast.failedLookupMusician'), 'error');
// Fallback: open modal with basic info
musicianModalData = {
name,
@@ -798,7 +801,7 @@
}
} catch (err) {
console.error('[ContextNav] Navigation failed:', err);
- showToast('Failed to navigate to source', 'error');
+ showToast($t('toast.failedNavigateSource'), 'error');
}
}
@@ -833,7 +836,7 @@
);
} catch (err) {
console.error('Failed to load more artist albums:', err);
- showToast('Failed to load more albums', 'error');
+ showToast($t('toast.failedLoadMoreAlbums'), 'error');
} finally {
isArtistAlbumsLoading = false;
}
@@ -858,7 +861,7 @@
return convertQobuzAlbum(album);
} catch (err) {
console.error('Failed to load album:', err);
- showToast('Failed to load album', 'error');
+ showToast($t('toast.failedLoadAlbum'), 'error');
return null;
}
}
@@ -928,7 +931,7 @@
artist_id: t.artistId ?? album.artistId
});
}
- showToast(`Playing ${album.tracks.length} tracks next`, 'success');
+ showToast($t('toast.playingTracksNext', { values: { count: album.tracks.length } }), 'success');
}
async function queueAlbumLaterById(albumId: string) {
@@ -953,24 +956,24 @@
const success = await addTracksToQueue(queueTracks);
if (success) {
- showToast(`Added ${queueTracks.length} tracks to queue`, 'success');
+ showToast($t('toast.addedTracksToQueue', { values: { count: queueTracks.length } }), 'success');
} else {
- showToast('Failed to add to queue', 'error');
+ showToast($t('toast.failedAddToQueue'), 'error');
}
}
function shareAlbumQobuzLinkById(albumId: string) {
const url = `https://play.qobuz.com/album/${albumId}`;
writeText(url);
- showToast('Album link copied to clipboard', 'success');
+ showToast($t('toast.albumLinkCopied'), 'success');
}
async function shareAlbumSonglinkById(albumId: string) {
try {
- showToast('Fetching Album.link...', 'info');
+ showToast($t('toast.fetchingAlbumLink'), 'info');
const album = await fetchAlbumDetail(albumId);
if (!album) {
- showToast('Could not fetch album details', 'error');
+ showToast($t('toast.couldNotFetchDetails'), 'error');
return;
}
const response = await invoke<{ pageUrl: string }>('share_album_songlink', {
@@ -980,10 +983,10 @@
artist: album.artist
});
writeText(response.pageUrl);
- showToast('Album.link copied to clipboard', 'success');
+ showToast($t('toast.albumLinkCopiedSonglink'), 'success');
} catch (err) {
console.error('Failed to get Album.link:', err);
- showToast(`Album.link error: ${err}`, 'error');
+ showToast($t('toast.albumLinkError', { values: { error: String(err) } }), 'error');
}
}
@@ -997,11 +1000,11 @@
});
if (tracksToDownload.length === 0) {
- showToast('All tracks already available offline', 'info');
+ showToast($t('toast.allTracksOffline'), 'info');
return;
}
- showToast(`Preparing ${tracksToDownload.length} tracks from "${album.title}" for offline`, 'info');
+ showToast($t('toast.preparingTracksOffline', { values: { count: tracksToDownload.length, album: album.title } }), 'info');
for (const track of tracksToDownload) {
try {
@@ -1055,7 +1058,7 @@
return playlist;
} catch (err) {
console.error('Failed to load playlist:', err);
- showToast('Failed to load playlist', 'error');
+ showToast($t('toast.failedLoadPlaylist'), 'error');
return null;
}
}
@@ -1063,7 +1066,7 @@
async function playPlaylistById(playlistId: number) {
const playlist = await fetchPlaylistData(playlistId);
if (!playlist?.tracks?.items?.length) {
- showToast('Playlist has no tracks', 'info');
+ showToast($t('toast.playlistNoTracks'), 'info');
return;
}
@@ -1111,7 +1114,7 @@
async function queuePlaylistNextById(playlistId: number) {
const playlist = await fetchPlaylistData(playlistId);
if (!playlist?.tracks?.items?.length) {
- showToast('Playlist has no tracks', 'info');
+ showToast($t('toast.playlistNoTracks'), 'info');
return;
}
@@ -1134,13 +1137,13 @@
artist_id: t.performer?.id
});
}
- showToast(`Playing ${tracks.length} tracks next`, 'success');
+ showToast($t('toast.playingTracksNext', { values: { count: tracks.length } }), 'success');
}
async function queuePlaylistLaterById(playlistId: number) {
const playlist = await fetchPlaylistData(playlistId);
if (!playlist?.tracks?.items?.length) {
- showToast('Playlist has no tracks', 'info');
+ showToast($t('toast.playlistNoTracks'), 'info');
return;
}
@@ -1162,39 +1165,39 @@
const success = await addTracksToQueue(queueTracks);
if (success) {
- showToast(`Added ${queueTracks.length} tracks to queue`, 'success');
+ showToast($t('toast.addedTracksToQueue', { values: { count: queueTracks.length } }), 'success');
} else {
- showToast('Failed to add to queue', 'error');
+ showToast($t('toast.failedAddToQueue'), 'error');
}
}
async function copyPlaylistToLibraryById(playlistId: number) {
try {
- showToast('Copying playlist to library...', 'info');
+ showToast($t('toast.copyingToLibrary'), 'info');
await invoke('subscribe_playlist', { playlistId });
sidebarRef?.refreshPlaylists();
- showToast('Playlist copied to library', 'success');
+ showToast($t('toast.playlistCopied'), 'success');
} catch (err) {
console.error('Failed to copy playlist:', err);
- showToast(`Failed to copy playlist: ${err}`, 'error');
+ showToast($t('toast.failedCopyPlaylist', { values: { error: String(err) } }), 'error');
}
}
function sharePlaylistQobuzLinkById(playlistId: number) {
const url = `https://play.qobuz.com/playlist/${playlistId}`;
writeText(url);
- showToast('Playlist link copied to clipboard', 'success');
+ showToast($t('toast.playlistLinkCopied'), 'success');
}
async function removePlaylistFavoriteById(playlistId: number) {
try {
await invoke('playlist_set_favorite', { playlistId, favorite: false });
- showToast('Playlist removed from favorites', 'success');
+ showToast($t('toast.playlistRemovedFavorites'), 'success');
sidebarRef?.refreshPlaylists();
sidebarRef?.refreshPlaylistSettings();
} catch (err) {
console.error('Failed to remove playlist favorite:', err);
- showToast(`Failed to remove from favorites: ${err}`, 'error');
+ showToast($t('toast.failedRemoveFavorites', { values: { error: String(err) } }), 'error');
}
}
@@ -1320,7 +1323,7 @@
async function toggleShuffle() {
const result = await queueToggleShuffle();
if (result.success) {
- showToast(result.enabled ? 'Shuffle enabled' : 'Shuffle disabled', 'info');
+ showToast(result.enabled ? $t('toast.shuffleEnabled') : $t('toast.shuffleDisabled'), 'info');
// Persist playback mode to session
saveSessionPlaybackMode(result.enabled, repeatMode);
}
@@ -1329,7 +1332,11 @@
async function toggleRepeat() {
const result = await queueToggleRepeat();
if (result.success) {
- const messages: Record = { off: 'Repeat off', all: 'Repeat all', one: 'Repeat one' };
+ const messages: Record = {
+ off: $t('toast.repeatOff'),
+ all: $t('toast.repeatAll'),
+ one: $t('toast.repeatOne')
+ };
showToast(messages[result.mode], 'info');
// Persist playback mode to session
saveSessionPlaybackMode(isShuffle, result.mode);
@@ -1342,9 +1349,9 @@
const result = await toggleTrackFavorite(currentTrack.id, isFavorite);
if (result.success) {
setIsFavorite(result.isFavorite);
- showToast(result.isFavorite ? 'Added to favorites' : 'Removed from favorites', 'success');
+ showToast(result.isFavorite ? $t('toast.addedToFavorites') : $t('toast.removedFromFavorites'), 'success');
} else {
- showToast('Failed to update favorites', 'error');
+ showToast($t('toast.failedUpdateFavorites'), 'error');
}
}
@@ -1376,7 +1383,7 @@
}
} catch (err) {
console.error('Failed to go to previous track:', err);
- showToast('Failed to go to previous track', 'error');
+ showToast($t('toast.failedPreviousTrack'), 'error');
} finally {
setIsSkipping(false);
}
@@ -1395,11 +1402,11 @@
// No next track - stop playback
await stopPlayback();
setIsPlaying(false);
- showToast('Queue ended', 'info');
+ showToast($t('toast.queueEnded'), 'info');
}
} catch (err) {
console.error('Failed to go to next track:', err);
- showToast('Failed to go to next track', 'error');
+ showToast($t('toast.failedNextTrack'), 'error');
} finally {
setIsSkipping(false);
}
@@ -1436,7 +1443,7 @@
if (skippedIds.has(track.id)) {
// Already tried this track, stop to prevent infinite loop
setQueueEnded(true);
- showToast('No available tracks in queue (offline mode)', 'info');
+ showToast($t('toast.noAvailableTracks'), 'info');
return;
}
skippedIds.add(track.id);
@@ -1488,7 +1495,7 @@
// Find the index in the queue
const queueState = await getBackendQueueState();
if (!queueState) {
- showToast('Failed to play track', 'error');
+ showToast($t('toast.failedPlayTrack'), 'error');
return;
}
@@ -1514,7 +1521,7 @@
}
} catch (err) {
console.error('Failed to play queue track:', err);
- showToast('Failed to play track', 'error');
+ showToast($t('toast.failedPlayTrack'), 'error');
}
}
@@ -1522,9 +1529,9 @@
async function handleClearQueue() {
const success = await clearQueue();
if (success) {
- showToast('Queue cleared', 'info');
+ showToast($t('toast.queueCleared'), 'info');
} else {
- showToast('Failed to clear queue', 'error');
+ showToast($t('toast.failedClearQueue'), 'error');
}
}
@@ -1532,7 +1539,7 @@
async function handleQueueReorder(fromIndex: number, toIndex: number) {
const success = await moveQueueTrack(fromIndex, toIndex);
if (!success) {
- showToast('Failed to reorder queue', 'error');
+ showToast($t('toast.failedReorderQueue'), 'error');
}
}
@@ -1555,7 +1562,7 @@
}
if (trackIds.length === 0) {
- showToast('Queue is empty', 'info');
+ showToast($t('toast.queueEmpty'), 'info');
return;
}
@@ -1574,7 +1581,7 @@
} catch {
// Ignore storage errors
}
- showToast(infinitePlayEnabled ? 'Infinite play enabled' : 'Infinite play disabled', 'info');
+ showToast(infinitePlayEnabled ? $t('toast.infinitePlayEnabled') : $t('toast.infinitePlayDisabled'), 'info');
}
// Play a track from history
@@ -1583,7 +1590,7 @@
// Get the full queue state to find the track in history
const queueState = await getBackendQueueState();
if (!queueState) {
- showToast('Failed to play track', 'error');
+ showToast($t('toast.failedPlayTrack'), 'error');
return;
}
@@ -1591,7 +1598,7 @@
const numericId = parseInt(trackId, 10);
const historyTrack = queueState.history.find(t => t.id === numericId);
if (!historyTrack) {
- showToast('Track not found in history', 'error');
+ showToast($t('toast.trackNotInHistory'), 'error');
return;
}
@@ -1609,7 +1616,7 @@
});
} catch (err) {
console.error('Failed to play history track:', err);
- showToast('Failed to play track', 'error');
+ showToast($t('toast.failedPlayTrack'), 'error');
}
}
@@ -1655,7 +1662,7 @@
// Play from random track (queue will be shuffled by backend)
await handleAlbumTrackPlay(randomTrack);
- showToast('Shuffle play enabled', 'info');
+ showToast($t('toast.shuffleEnabled'), 'info');
}
// Add all album tracks next in queue (after current track)
@@ -1689,7 +1696,7 @@
artist_id: t.artistId ?? selectedAlbum?.artistId
});
}
- showToast(`Playing ${playableTracks.length} tracks next`, 'success');
+ showToast($t('toast.playingTracksNext', { values: { count: playableTracks.length } }), 'success');
}
// Add all album tracks to end of queue
@@ -1722,9 +1729,9 @@
const success = await addTracksToQueue(queueTracks);
if (success) {
- showToast(`Added ${queueTracks.length} tracks to queue`, 'success');
+ showToast($t('toast.addedTracksToQueue', { values: { count: queueTracks.length } }), 'success');
} else {
- showToast('Failed to add to queue', 'error');
+ showToast($t('toast.failedAddToQueue'), 'error');
}
}
@@ -1732,7 +1739,7 @@
const album = await fetchAlbumDetail(albumId);
addAlbumToPlaylist(album);
}
-
+
function addAlbumToPlaylist(album: AlbumDetail | null) {
if (!album?.tracks?.length) return;
const trackIds = album.tracks.map(t => t.id);
@@ -1744,14 +1751,14 @@
if (!selectedAlbum?.id) return;
const url = `https://play.qobuz.com/album/${selectedAlbum.id}`;
writeText(url);
- showToast('Album link copied to clipboard', 'success');
+ showToast($t('toast.albumLinkCopied'), 'success');
}
// Share album via album.link
async function shareAlbumSonglink() {
if (!selectedAlbum?.id) return;
try {
- showToast('Fetching Album.link...', 'info');
+ showToast($t('toast.fetchingAlbumLink'), 'info');
const response = await invoke<{ pageUrl: string }>('share_album_songlink', {
upc: selectedAlbum.upc || null,
albumId: selectedAlbum.id,
@@ -1759,10 +1766,10 @@
artist: selectedAlbum.artist
});
writeText(response.pageUrl);
- showToast('Album.link copied to clipboard', 'success');
+ showToast($t('toast.albumLinkCopiedSonglink'), 'success');
} catch (err) {
console.error('Failed to get Album.link:', err);
- showToast(`Album.link error: ${err}`, 'error');
+ showToast($t('toast.albumLinkError', { values: { error: String(err) } }), 'error');
}
}
@@ -1788,20 +1795,20 @@
bitDepth: track.bitDepth,
sampleRate: track.samplingRate,
});
- showToast(`Preparing "${track.title}" for offline`, 'info');
+ showToast($t('toast.preparingTrackOffline', { values: { title: track.title } }), 'info');
} catch (err) {
console.error('Failed to cache for offline:', err);
- showToast('Failed to prepare for offline', 'error');
+ showToast($t('toast.failedPrepareOffline'), 'error');
}
}
async function handleTrackRemoveDownload(trackId: number) {
try {
await removeCachedTrack(trackId);
- showToast('Removed from offline library', 'info');
+ showToast($t('toast.removedFromOffline'), 'info');
} catch (err) {
console.error('Failed to remove from offline:', err);
- showToast('Failed to remove from offline library', 'error');
+ showToast($t('toast.failedRemoveOffline'), 'error');
}
}
@@ -1810,7 +1817,7 @@
await openTrackFolder(trackId);
} catch (err) {
console.error('Failed to open folder:', err);
- showToast('Failed to open folder', 'error');
+ showToast($t('toast.failedOpenFolder'), 'error');
}
}
@@ -1828,10 +1835,10 @@
bitDepth: 'bitDepth' in track ? track.bitDepth : undefined,
sampleRate: 'samplingRate' in track ? track.samplingRate : undefined,
});
- showToast(`Refreshing "${track.title}" for offline`, 'info');
+ showToast($t('toast.refreshingTrackOffline', { values: { title: track.title } }), 'info');
} catch (err) {
console.error('Failed to refresh offline copy:', err);
- showToast('Failed to refresh offline copy', 'error');
+ showToast($t('toast.failedRefreshOffline'), 'error');
}
}
@@ -1848,11 +1855,11 @@
});
if (tracksToDownload.length === 0) {
- showToast('All tracks already available offline', 'info');
+ showToast($t('toast.allTracksOffline'), 'info');
return;
}
- showToast(`Preparing ${tracksToDownload.length} tracks from "${selectedAlbum.title}" for offline`, 'info');
+ showToast($t('toast.preparingTracksOffline', { values: { count: tracksToDownload.length, album: selectedAlbum.title } }), 'info');
for (const track of tracksToDownload) {
try {
@@ -1875,19 +1882,19 @@
async function handleOpenAlbumFolder() {
if (!selectedAlbum) return;
-
+
try {
await openAlbumFolder(selectedAlbum.id);
} catch (err) {
console.error('Failed to open album folder:', err);
- showToast('Failed to open album folder', 'error');
+ showToast($t('toast.failedOpenAlbumFolder'), 'error');
}
}
async function handleReDownloadAlbum() {
if (!selectedAlbum) return;
- showToast(`Refreshing all tracks from "${selectedAlbum.title}" for offline`, 'info');
+ showToast($t('toast.refreshingAlbumOffline', { values: { album: selectedAlbum.title } }), 'info');
for (const track of selectedAlbum.tracks) {
try {
@@ -1913,7 +1920,7 @@
await openAlbumFolder(albumId);
} catch (err) {
console.error('Failed to open album folder:', err);
- showToast('Failed to open album folder', 'error');
+ showToast($t('toast.failedOpenAlbumFolder'), 'error');
}
}
@@ -1921,11 +1928,11 @@
try {
const album = await invoke('get_album', { albumId });
if (!album || !album.tracks || album.tracks.data.length === 0) {
- showToast('Failed to load album for offline refresh', 'error');
+ showToast($t('toast.failedLoadAlbumRefresh'), 'error');
return;
}
- showToast(`Refreshing all tracks from "${album.title}" for offline`, 'info');
+ showToast($t('toast.refreshingAlbumOffline', { values: { album: album.title } }), 'info');
for (const track of album.tracks.data) {
try {
@@ -1946,7 +1953,7 @@
}
} catch (err) {
console.error('Failed to load album:', err);
- showToast('Failed to load album for offline refresh', 'error');
+ showToast($t('toast.failedLoadAlbumRefresh'), 'error');
}
}
@@ -1974,10 +1981,10 @@
bitDepth: track.bitDepth,
sampleRate: track.samplingRate,
});
- showToast(`Preparing "${track.title}" for offline`, 'info');
+ showToast($t('toast.preparingTrackOffline', { values: { title: track.title } }), 'info');
} catch (err) {
console.error('Failed to prepare for offline:', err);
- showToast('Failed to prepare for offline', 'error');
+ showToast($t('toast.failedPrepareOffline'), 'error');
}
}
@@ -2090,9 +2097,9 @@
const isLocal = playlistModalTracksAreLocal;
if (playlistModalMode === 'addTrack') {
- showToast('Track(s) added to playlist', 'success');
+ showToast($t('toast.tracksAddedToPlaylist'), 'success');
} else {
- showToast('Playlist created', 'success');
+ showToast($t('toast.playlistCreated'), 'success');
}
sidebarRef?.refreshPlaylists();
sidebarRef?.refreshPlaylistSettings();
@@ -2143,12 +2150,12 @@
subscription: 'Local Library Only'
});
navigateTo('library');
- showToast('Started in offline mode', 'info');
+ showToast($t('toast.offlineModeStarted'), 'info');
}
async function handleLoginSuccess(info: UserInfo) {
setLoggedIn(info);
- showToast(`Welcome, ${info.userName}!`, 'success');
+ showToast($t('toast.welcomeUser', { values: { name: info.userName } }), 'success');
// Load favorites now that login is confirmed (sync with Qobuz)
loadFavorites(); // Track favorites
@@ -2276,10 +2283,10 @@
setLoggedOut();
currentTrack = null;
isPlaying = false;
- showToast('Logged out successfully', 'info');
+ showToast($t('toast.logoutSuccess'), 'info');
} catch (err) {
console.error('Logout error:', err);
- showToast('Failed to logout', 'error');
+ showToast($t('toast.failedLogout'), 'error');
}
}
@@ -3266,7 +3273,7 @@
lyricsActive={lyricsSidebarVisible}
onArtistClick={() => {
if (currentTrack?.isLocal) {
- showToast('Local track - search for artist in Search', 'info');
+ showToast($t('toast.localTrackSearch'), 'info');
} else if (currentTrack?.artistId) {
handleArtistClick(currentTrack.artistId);
}