i18n(favorites): translate empty states, loading, and headers
- Add t import and use translations for all empty state messages - Translate loading states (favorites, playlists, albums) - Translate section headers (Discography, EPs & Singles, Live Albums) - Translate button titles (Play All, Shuffle, More, Search, Clear) - Add hint translations for liking items to favorites - Update getSortLabel to use sort.* translation keys - Update getTabLabel to use favorites.tabLabels.* keys cc
This commit is contained in:
1 parent
9a053c9219
commit
1e2d074065
3 files changed
+127
-53
No files matched your search
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { invoke, convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Heart, Play, Disc3, Mic2, Music, Search, X, LayoutGrid, List, ChevronDown, ListMusic, Edit3, Star, Folder, Library, CloudDownload, Shuffle, MoreHorizontal, PanelLeftClose, Loader2 } from 'lucide-svelte';
|
||||
import AlbumCard from '../AlbumCard.svelte';
|
||||
import TrackRow from '../TrackRow.svelte';
|
||||
@@ -166,12 +168,9 @@
|
||||
let activeTab = $state<TabType>('tracks');
|
||||
let preferencesLoaded = $state(false);
|
||||
|
||||
const tabLabels: Record<TabType, string> = {
|
||||
tracks: 'Tracks',
|
||||
albums: 'Albums',
|
||||
artists: 'Artists',
|
||||
playlists: 'Playlists',
|
||||
};
|
||||
function getTabTranslationKey(tab: TabType): string {
|
||||
return `favorites.tabLabels.${tab}`;
|
||||
}
|
||||
|
||||
let favoriteAlbums = $state<FavoriteAlbum[]>([]);
|
||||
let favoriteTracks = $state<FavoriteTrack[]>([]);
|
||||
@@ -346,12 +345,13 @@
|
||||
const sortedLiveAlbums = $derived(sortQobuzAlbums(groupedArtistAlbumsByCategory.liveAlbums, liveAlbumsSortMode));
|
||||
|
||||
function getSortLabel(mode: AlbumSortMode): string {
|
||||
const translate = get(t);
|
||||
switch (mode) {
|
||||
case 'default': return 'Default';
|
||||
case 'newest': return 'Newest';
|
||||
case 'oldest': return 'Oldest';
|
||||
case 'title-asc': return 'A-Z';
|
||||
case 'title-desc': return 'Z-A';
|
||||
case 'default': return translate('sort.default');
|
||||
case 'newest': return translate('sort.newest');
|
||||
case 'oldest': return translate('sort.oldest');
|
||||
case 'title-asc': return translate('sort.titleAsc');
|
||||
case 'title-desc': return translate('sort.titleDesc');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@
|
||||
}
|
||||
|
||||
function getTabLabel(tab: TabType): string {
|
||||
return tabLabels[tab] || tab.charAt(0).toUpperCase() + tab.slice(1);
|
||||
return get(t)(getTabTranslationKey(tab));
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -1185,20 +1185,20 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="header-content">
|
||||
<h1>Favorites</h1>
|
||||
<h1>{$t('favorites.title')}</h1>
|
||||
{#if activeTab === 'tracks' && !loading && filteredTracks.length > 0}
|
||||
<div class="header-actions">
|
||||
<button class="action-btn-circle primary" onclick={handlePlayAllTracks} title="Play All">
|
||||
<button class="action-btn-circle primary" onclick={handlePlayAllTracks} title={$t('actions.playAll')}>
|
||||
<Play size={20} fill="currentColor" color="currentColor" />
|
||||
</button>
|
||||
<button class="action-btn-circle" onclick={handleShuffleAllTracks} title="Shuffle">
|
||||
<button class="action-btn-circle" onclick={handleShuffleAllTracks} title={$t('actions.shuffle')}>
|
||||
<Shuffle size={18} />
|
||||
</button>
|
||||
<div class="context-menu-wrapper">
|
||||
<button
|
||||
class="action-btn-circle"
|
||||
onclick={() => showTracksContextMenu = !showTracksContextMenu}
|
||||
title="More options"
|
||||
title={$t('actions.more')}
|
||||
>
|
||||
<MoreHorizontal size={18} />
|
||||
</button>
|
||||
@@ -1206,10 +1206,10 @@
|
||||
<div class="context-menu-backdrop" onclick={() => showTracksContextMenu = false} role="presentation"></div>
|
||||
<div class="context-menu">
|
||||
<button class="context-menu-item" onclick={() => { handlePlayAllTracksNext(); showTracksContextMenu = false; }}>
|
||||
Play Next
|
||||
{$t('actions.playNext')}
|
||||
</button>
|
||||
<button class="context-menu-item" onclick={() => { handlePlayAllTracksLater(); showTracksContextMenu = false; }}>
|
||||
Add to Queue
|
||||
{$t('actions.addToQueue')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1217,7 +1217,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="edit-btn" onclick={() => editModalOpen = true} title="Edit Favorites settings">
|
||||
<button class="edit-btn" onclick={() => editModalOpen = true} title={$t('favorites.editSettings')}>
|
||||
<Edit3 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -1238,7 +1238,7 @@
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
{#if !searchExpanded}
|
||||
<button class="search-icon-btn" onclick={() => searchExpanded = true} title="Search">
|
||||
<button class="search-icon-btn" onclick={() => searchExpanded = true} title={$t('nav.search')}>
|
||||
<Search size={16} />
|
||||
</button>
|
||||
{:else}
|
||||
@@ -1246,14 +1246,14 @@
|
||||
<Search size={16} class="search-icon-inline" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder={`Search ${getTabLabel(activeTab).toLowerCase()}...`}
|
||||
placeholder={$t('placeholders.search')}
|
||||
value={getCurrentSearchValue()}
|
||||
oninput={(e) => setCurrentSearchValue(e.currentTarget.value)}
|
||||
class="search-input-inline"
|
||||
autofocus
|
||||
/>
|
||||
{#if getCurrentSearchValue()}
|
||||
<button class="search-clear-btn" onclick={clearCurrentSearch} title="Clear">
|
||||
<button class="search-clear-btn" onclick={clearCurrentSearch} title={$t('actions.clear')}>
|
||||
<X size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
@@ -1506,28 +1506,28 @@
|
||||
<ViewTransition duration={200} distance={12} direction="down">
|
||||
<div class="loading" class:fading={spinnerFading}>
|
||||
<div class="spinner"></div>
|
||||
<p>Loading favorites...</p>
|
||||
<p>{$t('favorites.loadingFavorites')}</p>
|
||||
</div>
|
||||
</ViewTransition>
|
||||
{/key}
|
||||
{:else if error}
|
||||
<div class="error">
|
||||
<p>Failed to load favorites</p>
|
||||
<p>{$t('favorites.failedLoadFavorites')}</p>
|
||||
<p class="error-detail">{error}</p>
|
||||
<button class="retry-btn" onclick={() => loadTabIfNeeded(activeTab)}>Retry</button>
|
||||
<button class="retry-btn" onclick={() => loadTabIfNeeded(activeTab)}>{$t('actions.retry')}</button>
|
||||
</div>
|
||||
{:else if activeTab === 'tracks'}
|
||||
<ViewTransition duration={200} distance={12} direction="up">
|
||||
{#if favoriteTracks.length === 0}
|
||||
<div class="empty">
|
||||
<Music size={48} />
|
||||
<p>No favorite tracks yet</p>
|
||||
<p class="empty-hint">Like tracks to see them here</p>
|
||||
<p>{$t('favorites.noFavoriteTracks')}</p>
|
||||
<p class="empty-hint">{$t('favorites.likeTracksHint')}</p>
|
||||
</div>
|
||||
{:else if filteredTracks.length === 0}
|
||||
<div class="empty">
|
||||
<Search size={48} />
|
||||
<p>No tracks match "{trackSearch}"</p>
|
||||
<p>{$t('favorites.noTracksMatch', { values: { query: trackSearch } })}</p>
|
||||
</div>
|
||||
{:else}
|
||||
{@const groupedTracks = trackGroupingEnabled
|
||||
@@ -1584,13 +1584,13 @@
|
||||
{#if favoriteAlbums.length === 0}
|
||||
<div class="empty">
|
||||
<Disc3 size={48} />
|
||||
<p>No favorite albums yet</p>
|
||||
<p class="empty-hint">Like albums to see them here</p>
|
||||
<p>{$t('favorites.noFavoriteAlbums')}</p>
|
||||
<p class="empty-hint">{$t('favorites.likeAlbumsHint')}</p>
|
||||
</div>
|
||||
{:else if filteredAlbums.length === 0}
|
||||
<div class="empty">
|
||||
<Search size={48} />
|
||||
<p>No albums match "{albumSearch}"</p>
|
||||
<p>{$t('favorites.noAlbumsMatch', { values: { query: albumSearch } })}</p>
|
||||
</div>
|
||||
{:else if albumGroupingEnabled}
|
||||
{@const groupedAlbums = groupAlbums(filteredAlbums, albumGroupMode)}
|
||||
@@ -1746,13 +1746,13 @@
|
||||
{#if favoriteArtists.length === 0}
|
||||
<div class="empty">
|
||||
<Mic2 size={48} />
|
||||
<p>No favorite artists yet</p>
|
||||
<p class="empty-hint">Like artists to see them here</p>
|
||||
<p>{$t('favorites.noFavoriteArtists')}</p>
|
||||
<p class="empty-hint">{$t('favorites.likeArtistsHint')}</p>
|
||||
</div>
|
||||
{:else if filteredArtists.length === 0}
|
||||
<div class="empty">
|
||||
<Search size={48} />
|
||||
<p>No artists match "{artistSearch}"</p>
|
||||
<p>{$t('favorites.noArtistsMatch', { values: { query: artistSearch } })}</p>
|
||||
</div>
|
||||
{:else if artistViewMode === 'sidepanel'}
|
||||
<!-- Two-column sidepanel view -->
|
||||
@@ -1795,7 +1795,7 @@
|
||||
<span class="artist-name-text" class:animating={isHovered && hasOverflow}>{artist.name}</span>
|
||||
</div>
|
||||
{#if artist.albums_count}
|
||||
<div class="artist-list-meta">{artist.albums_count} albums</div>
|
||||
<div class="artist-list-meta">{$t('library.albumCount', { values: { count: artist.albums_count } })}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
@@ -1809,22 +1809,22 @@
|
||||
{#if !selectedFavoriteArtist}
|
||||
<div class="artist-albums-empty">
|
||||
<Mic2 size={48} />
|
||||
<p>Select an artist to see their albums</p>
|
||||
<p>{$t('favorites.selectArtistHint')}</p>
|
||||
</div>
|
||||
{:else if loadingArtistAlbums}
|
||||
<div class="artist-albums-loading">
|
||||
<Loader2 size={32} class="spinner-icon" />
|
||||
<p>Loading albums...</p>
|
||||
<p>{$t('favorites.loadingAlbums')}</p>
|
||||
</div>
|
||||
{:else if artistAlbumsError}
|
||||
<div class="artist-albums-error">
|
||||
<p>Failed to load albums</p>
|
||||
<p>{$t('favorites.failedLoadAlbums')}</p>
|
||||
<p class="error-detail">{artistAlbumsError}</p>
|
||||
</div>
|
||||
{:else if totalDisplayedAlbums === 0}
|
||||
<div class="artist-albums-empty">
|
||||
<Disc3 size={48} />
|
||||
<p>No albums found</p>
|
||||
<p>{$t('favorites.noAlbumsFound')}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="artist-albums-scroll">
|
||||
@@ -1832,8 +1832,8 @@
|
||||
{#if sortedDiscography.length > 0}
|
||||
<div class="artist-albums-section">
|
||||
<div class="artist-albums-section-header">
|
||||
<span class="section-title">Discography</span>
|
||||
<span class="section-count">{sortedDiscography.length} albums</span>
|
||||
<span class="section-title">{$t('artist.discography')}</span>
|
||||
<span class="section-count">{$t('library.albumCount', { values: { count: sortedDiscography.length } })}</span>
|
||||
<div class="section-sort-wrapper">
|
||||
<button
|
||||
class="section-sort-btn"
|
||||
@@ -1884,7 +1884,7 @@
|
||||
{#if sortedEpsSingles.length > 0}
|
||||
<div class="artist-albums-section">
|
||||
<div class="artist-albums-section-header">
|
||||
<span class="section-title">EPs & Singles</span>
|
||||
<span class="section-title">{$t('artist.epsSingles')}</span>
|
||||
<span class="section-count">{sortedEpsSingles.length}</span>
|
||||
<div class="section-sort-wrapper">
|
||||
<button
|
||||
@@ -1936,7 +1936,7 @@
|
||||
{#if sortedLiveAlbums.length > 0}
|
||||
<div class="artist-albums-section">
|
||||
<div class="artist-albums-section-header">
|
||||
<span class="section-title">Live Albums</span>
|
||||
<span class="section-title">{$t('artist.liveAlbums')}</span>
|
||||
<span class="section-count">{sortedLiveAlbums.length}</span>
|
||||
<div class="section-sort-wrapper">
|
||||
<button
|
||||
@@ -2074,7 +2074,7 @@
|
||||
<ViewTransition duration={200} distance={12} direction="down">
|
||||
<div class="loading" class:fading={spinnerFading}>
|
||||
<div class="spinner"></div>
|
||||
<p>Loading playlists...</p>
|
||||
<p>{$t('favorites.loadingPlaylists')}</p>
|
||||
</div>
|
||||
</ViewTransition>
|
||||
{/key}
|
||||
@@ -2083,13 +2083,13 @@
|
||||
{#if favoritePlaylists.length === 0}
|
||||
<div class="empty">
|
||||
<ListMusic size={48} />
|
||||
<p>No favorite playlists yet</p>
|
||||
<p class="empty-hint">Click the heart icon on playlists to add them here</p>
|
||||
<p>{$t('favorites.noFavoritePlaylists')}</p>
|
||||
<p class="empty-hint">{$t('favorites.likePlaylistsHint')}</p>
|
||||
</div>
|
||||
{:else if filteredPlaylists.length === 0}
|
||||
<div class="empty">
|
||||
<Search size={48} />
|
||||
<p>No playlists match "{playlistSearch}"</p>
|
||||
<p>{$t('favorites.noPlaylistsMatch', { values: { query: playlistSearch } })}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="playlist-grid">
|
||||
|
||||
@@ -264,7 +264,27 @@
|
||||
"albums": "Albums",
|
||||
"tracks": "Tracks",
|
||||
"artists": "Artists",
|
||||
"playlists": "Playlists",
|
||||
"noFavorites": "No favorites yet",
|
||||
"noFavoriteTracks": "No favorite tracks yet",
|
||||
"noFavoriteAlbums": "No favorite albums yet",
|
||||
"noFavoriteArtists": "No favorite artists yet",
|
||||
"noFavoritePlaylists": "No favorite playlists yet",
|
||||
"noTracksMatch": "No tracks match \"{query}\"",
|
||||
"noAlbumsMatch": "No albums match \"{query}\"",
|
||||
"noArtistsMatch": "No artists match \"{query}\"",
|
||||
"noPlaylistsMatch": "No playlists match \"{query}\"",
|
||||
"noAlbumsFound": "No albums found",
|
||||
"likeTracksHint": "Like tracks to see them here",
|
||||
"likeAlbumsHint": "Like albums to see them here",
|
||||
"likeArtistsHint": "Like artists to see them here",
|
||||
"likePlaylistsHint": "Click the heart icon on playlists to add them here",
|
||||
"selectArtistHint": "Select an artist to see their albums",
|
||||
"loadingAlbums": "Loading albums...",
|
||||
"failedLoadAlbums": "Failed to load albums",
|
||||
"failedLoadFavorites": "Failed to load favorites",
|
||||
"loadingFavorites": "Loading favorites...",
|
||||
"loadingPlaylists": "Loading playlists...",
|
||||
"addedToFavorites": "Added to favorites",
|
||||
"removedFromFavorites": "Removed from favorites",
|
||||
"editSettings": "Edit Favorites Settings",
|
||||
@@ -286,6 +306,23 @@
|
||||
"saving": "Saving...",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"placeholders": {
|
||||
"search": "Search...",
|
||||
"searchDevices": "Search devices...",
|
||||
"searchGenres": "Search genres...",
|
||||
"searchFolders": "Search folders...",
|
||||
"searchPlaylists": "Search playlists...",
|
||||
"searchAlbums": "Search albums...",
|
||||
"searchInPage": "Search in this page...",
|
||||
"searchInPlaylist": "Search in playlist...",
|
||||
"filterFolders": "Filter folders...",
|
||||
"myPlaylist": "My Playlist",
|
||||
"addDescription": "Add a description...",
|
||||
"enterFolderName": "Enter folder name",
|
||||
"enterApiKey": "Enter your API key",
|
||||
"enterSharedSecret": "Enter your shared secret",
|
||||
"pasteToken": "Paste your ListenBrainz token"
|
||||
},
|
||||
"player": {
|
||||
"nowPlaying": "Now Playing",
|
||||
"queue": "Queue",
|
||||
|
||||
@@ -264,19 +264,39 @@
|
||||
"albums": "Álbumes",
|
||||
"tracks": "Pistas",
|
||||
"artists": "Artistas",
|
||||
"playlists": "Listas",
|
||||
"noFavorites": "Aún no hay favoritos",
|
||||
"noFavoriteTracks": "Aún no hay pistas favoritas",
|
||||
"noFavoriteAlbums": "Aún no hay álbumes favoritos",
|
||||
"noFavoriteArtists": "Aún no hay artistas favoritos",
|
||||
"noFavoritePlaylists": "Aún no hay listas favoritas",
|
||||
"noTracksMatch": "Ninguna pista coincide con \"{query}\"",
|
||||
"noAlbumsMatch": "Ningún álbum coincide con \"{query}\"",
|
||||
"noArtistsMatch": "Ningún artista coincide con \"{query}\"",
|
||||
"noPlaylistsMatch": "Ninguna lista coincide con \"{query}\"",
|
||||
"noAlbumsFound": "No se encontraron álbumes",
|
||||
"likeTracksHint": "Da me gusta a pistas para verlas aquí",
|
||||
"likeAlbumsHint": "Da me gusta a álbumes para verlos aquí",
|
||||
"likeArtistsHint": "Da me gusta a artistas para verlos aquí",
|
||||
"likePlaylistsHint": "Haz clic en el corazón de las listas para agregarlas aquí",
|
||||
"selectArtistHint": "Selecciona un artista para ver sus álbumes",
|
||||
"loadingAlbums": "Cargando álbumes...",
|
||||
"failedLoadAlbums": "Error al cargar álbumes",
|
||||
"failedLoadFavorites": "Error al cargar favoritos",
|
||||
"loadingFavorites": "Cargando favoritos...",
|
||||
"loadingPlaylists": "Cargando listas de reproducción...",
|
||||
"addedToFavorites": "Agregado a favoritos",
|
||||
"removedFromFavorites": "Eliminado de favoritos",
|
||||
"editSettings": "Editar configuracion de Favoritos",
|
||||
"settingsModalTitle": "Configuracion de Favoritos",
|
||||
"editSettings": "Editar configuración de Favoritos",
|
||||
"settingsModalTitle": "Configuración de Favoritos",
|
||||
"iconSectionTitle": "Icono",
|
||||
"iconSectionDescription": "Elige un icono para el encabezado de la pagina de Favoritos",
|
||||
"iconSectionDescription": "Elige un icono para el encabezado de la página de Favoritos",
|
||||
"uploadCustomImage": "Subir imagen personalizada",
|
||||
"tabOrderSectionTitle": "Orden de pestanas",
|
||||
"tabOrderSectionDescription": "Reordena las pestanas en la navegacion de Favoritos",
|
||||
"tabOrderSectionTitle": "Orden de pestañas",
|
||||
"tabOrderSectionDescription": "Reordena las pestañas en la navegación de Favoritos",
|
||||
"tabLabels": {
|
||||
"tracks": "Pistas",
|
||||
"albums": "Albumes",
|
||||
"albums": "Álbumes",
|
||||
"artists": "Artistas",
|
||||
"playlists": "Listas"
|
||||
},
|
||||
@@ -286,6 +306,23 @@
|
||||
"saving": "Guardando...",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"placeholders": {
|
||||
"search": "Buscar...",
|
||||
"searchDevices": "Buscar dispositivos...",
|
||||
"searchGenres": "Buscar géneros...",
|
||||
"searchFolders": "Buscar carpetas...",
|
||||
"searchPlaylists": "Buscar listas...",
|
||||
"searchAlbums": "Buscar álbumes...",
|
||||
"searchInPage": "Buscar en esta página...",
|
||||
"searchInPlaylist": "Buscar en la lista...",
|
||||
"filterFolders": "Filtrar carpetas...",
|
||||
"myPlaylist": "Mi Lista",
|
||||
"addDescription": "Agregar una descripción...",
|
||||
"enterFolderName": "Ingresa el nombre de la carpeta",
|
||||
"enterApiKey": "Ingresa tu API key",
|
||||
"enterSharedSecret": "Ingresa tu shared secret",
|
||||
"pasteToken": "Pega tu token de ListenBrainz"
|
||||
},
|
||||
"player": {
|
||||
"nowPlaying": "Reproduciendo",
|
||||
"queue": "Cola",
|
||||
|
||||
Reference in new issue
Block a user