i18n: Add greeting and toast translations
- Add time-based greeting translations (goodMorning, goodAfternoon, goodEvening, goodNight) to homeSettings section - Update homeSettingsStore to export getGreetingInfo for i18n support - Update HomeView.svelte to use translated greetings - Add comprehensive toast translations (~60 keys) for all user-facing notifications including queue, playback, offline, favorites, etc. - Update +page.svelte to use $t() for all showToast calls - Complete Spanish translations for all new keys
This commit is contained in:
1 parent
a742c22c8f
commit
2dc9989d22
5 files changed
+356
-89
No files matched your search
@@ -12,7 +12,7 @@
|
||||
import {
|
||||
subscribe as subscribeHomeSettings,
|
||||
getSettings,
|
||||
getGreetingText,
|
||||
getGreetingInfo,
|
||||
type HomeSettings,
|
||||
type HomeSectionId
|
||||
} from '$lib/stores/homeSettingsStore';
|
||||
@@ -127,8 +127,14 @@
|
||||
let homeSettings = $state<HomeSettings>(getSettings());
|
||||
let isSettingsModalOpen = $state(false);
|
||||
|
||||
// Computed greeting
|
||||
const greetingText = $derived(getGreetingText(userName));
|
||||
// Computed greeting with i18n support
|
||||
const greetingText = $derived.by(() => {
|
||||
const info = getGreetingInfo(userName);
|
||||
if (info.type === 'custom') {
|
||||
return info.text;
|
||||
}
|
||||
return $t(info.key, { name: info.name });
|
||||
});
|
||||
|
||||
|
||||
// Check if a section is visible
|
||||
|
||||
@@ -562,12 +562,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.",
|
||||
|
||||
@@ -562,12 +562,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™.",
|
||||
|
||||
@@ -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();
|
||||
|
||||
+91
-84
@@ -224,6 +224,9 @@
|
||||
shareSonglinkTrack
|
||||
} from '$lib/services/trackActions';
|
||||
|
||||
// Internationalization
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
// App bootstrap
|
||||
import { bootstrapApp } from '$lib/app/bootstrap';
|
||||
|
||||
@@ -556,7 +559,7 @@
|
||||
|
||||
async function handleAlbumClick(albumId: string) {
|
||||
try {
|
||||
showToast('Loading album...', 'info');
|
||||
showToast($t('toast.loadingAlbum'), 'info');
|
||||
const album = await invoke<QobuzAlbum>('get_album', { albumId });
|
||||
console.log('Album details:', album);
|
||||
|
||||
@@ -572,7 +575,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load album:', err);
|
||||
showToast('Failed to load album', 'error');
|
||||
showToast($t('toast.failedLoadAlbum'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,7 +637,7 @@
|
||||
|
||||
async function handleArtistClick(artistId: number) {
|
||||
try {
|
||||
showToast('Loading artist...', 'info');
|
||||
showToast($t('toast.loadingArtist'), 'info');
|
||||
const artist = await invoke<QobuzArtist>('get_artist_detail', { artistId });
|
||||
console.log('Artist details:', artist);
|
||||
|
||||
@@ -643,7 +646,7 @@
|
||||
hideToast();
|
||||
} catch (err) {
|
||||
console.error('Failed to load artist:', err);
|
||||
showToast('Failed to load artist', 'error');
|
||||
showToast($t('toast.failedLoadArtist'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,7 +663,7 @@
|
||||
* - Weak (1), None (0): Show Informational Modal
|
||||
*/
|
||||
async function handleMusicianClick(name: string, role: string) {
|
||||
showToast(`Looking up ${name}...`, 'info');
|
||||
showToast($t('toast.lookingUp', { name }), 'info');
|
||||
try {
|
||||
const musician = await invoke<ResolvedMusician>('resolve_musician', { name, role });
|
||||
console.log('Resolved musician:', musician);
|
||||
@@ -691,7 +694,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,
|
||||
@@ -796,7 +799,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[ContextNav] Navigation failed:', err);
|
||||
showToast('Failed to navigate to source', 'error');
|
||||
showToast($t('toast.failedNavigateSource'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,7 +834,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;
|
||||
}
|
||||
@@ -856,7 +859,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;
|
||||
}
|
||||
}
|
||||
@@ -926,7 +929,7 @@
|
||||
artist_id: t.artistId ?? album.artistId
|
||||
});
|
||||
}
|
||||
showToast(`Playing ${album.tracks.length} tracks next`, 'success');
|
||||
showToast($t('toast.playingTracksNext', { count: album.tracks.length }), 'success');
|
||||
}
|
||||
|
||||
async function queueAlbumLaterById(albumId: string) {
|
||||
@@ -951,24 +954,24 @@
|
||||
|
||||
const success = await addTracksToQueue(queueTracks);
|
||||
if (success) {
|
||||
showToast(`Added ${queueTracks.length} tracks to queue`, 'success');
|
||||
showToast($t('toast.addedTracksToQueue', { 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', {
|
||||
@@ -978,10 +981,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', { error: String(err) }), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -995,11 +998,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', { count: tracksToDownload.length, album: album.title }), 'info');
|
||||
|
||||
for (const track of tracksToDownload) {
|
||||
try {
|
||||
@@ -1053,7 +1056,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;
|
||||
}
|
||||
}
|
||||
@@ -1061,7 +1064,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;
|
||||
}
|
||||
|
||||
@@ -1109,7 +1112,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;
|
||||
}
|
||||
|
||||
@@ -1132,13 +1135,13 @@
|
||||
artist_id: t.performer?.id
|
||||
});
|
||||
}
|
||||
showToast(`Playing ${tracks.length} tracks next`, 'success');
|
||||
showToast($t('toast.playingTracksNext', { 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;
|
||||
}
|
||||
|
||||
@@ -1160,39 +1163,39 @@
|
||||
|
||||
const success = await addTracksToQueue(queueTracks);
|
||||
if (success) {
|
||||
showToast(`Added ${queueTracks.length} tracks to queue`, 'success');
|
||||
showToast($t('toast.addedTracksToQueue', { 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', { 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', { error: String(err) }), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1312,7 +1315,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);
|
||||
}
|
||||
@@ -1321,7 +1324,11 @@
|
||||
async function toggleRepeat() {
|
||||
const result = await queueToggleRepeat();
|
||||
if (result.success) {
|
||||
const messages: Record<RepeatMode, string> = { off: 'Repeat off', all: 'Repeat all', one: 'Repeat one' };
|
||||
const messages: Record<RepeatMode, string> = {
|
||||
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);
|
||||
@@ -1334,9 +1341,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1368,7 +1375,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);
|
||||
}
|
||||
@@ -1387,11 +1394,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);
|
||||
}
|
||||
@@ -1428,7 +1435,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);
|
||||
@@ -1480,7 +1487,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;
|
||||
}
|
||||
|
||||
@@ -1506,7 +1513,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to play queue track:', err);
|
||||
showToast('Failed to play track', 'error');
|
||||
showToast($t('toast.failedPlayTrack'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1514,9 +1521,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1524,7 +1531,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,7 +1554,7 @@
|
||||
}
|
||||
|
||||
if (trackIds.length === 0) {
|
||||
showToast('Queue is empty', 'info');
|
||||
showToast($t('toast.queueEmpty'), 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1566,7 +1573,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
|
||||
@@ -1575,7 +1582,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;
|
||||
}
|
||||
|
||||
@@ -1583,7 +1590,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;
|
||||
}
|
||||
|
||||
@@ -1601,7 +1608,7 @@
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to play history track:', err);
|
||||
showToast('Failed to play track', 'error');
|
||||
showToast($t('toast.failedPlayTrack'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1634,7 +1641,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)
|
||||
@@ -1660,7 +1667,7 @@
|
||||
artist_id: t.artistId ?? selectedAlbum?.artistId
|
||||
});
|
||||
}
|
||||
showToast(`Playing ${selectedAlbum.tracks.length} tracks next`, 'success');
|
||||
showToast($t('toast.playingTracksNext', { count: selectedAlbum.tracks.length }), 'success');
|
||||
}
|
||||
|
||||
// Add all album tracks to end of queue
|
||||
@@ -1685,9 +1692,9 @@
|
||||
|
||||
const success = await addTracksToQueue(queueTracks);
|
||||
if (success) {
|
||||
showToast(`Added ${queueTracks.length} tracks to queue`, 'success');
|
||||
showToast($t('toast.addedTracksToQueue', { count: queueTracks.length }), 'success');
|
||||
} else {
|
||||
showToast('Failed to add to queue', 'error');
|
||||
showToast($t('toast.failedAddToQueue'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1695,7 +1702,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);
|
||||
@@ -1707,14 +1714,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,
|
||||
@@ -1722,10 +1729,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', { error: String(err) }), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1751,20 +1758,20 @@
|
||||
bitDepth: track.bitDepth,
|
||||
sampleRate: track.samplingRate,
|
||||
});
|
||||
showToast(`Preparing "${track.title}" for offline`, 'info');
|
||||
showToast($t('toast.preparingTrackOffline', { 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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1773,7 +1780,7 @@
|
||||
await openTrackFolder(trackId);
|
||||
} catch (err) {
|
||||
console.error('Failed to open folder:', err);
|
||||
showToast('Failed to open folder', 'error');
|
||||
showToast($t('toast.failedOpenFolder'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1791,10 +1798,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', { 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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1811,11 +1818,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', { count: tracksToDownload.length, album: selectedAlbum.title }), 'info');
|
||||
|
||||
for (const track of tracksToDownload) {
|
||||
try {
|
||||
@@ -1838,19 +1845,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', { album: selectedAlbum.title }), 'info');
|
||||
|
||||
for (const track of selectedAlbum.tracks) {
|
||||
try {
|
||||
@@ -1876,7 +1883,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1884,11 +1891,11 @@
|
||||
try {
|
||||
const album = await invoke<QobuzAlbum>('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', { album: album.title }), 'info');
|
||||
|
||||
for (const track of album.tracks.data) {
|
||||
try {
|
||||
@@ -1909,7 +1916,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load album:', err);
|
||||
showToast('Failed to load album for offline refresh', 'error');
|
||||
showToast($t('toast.failedLoadAlbumRefresh'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1937,10 +1944,10 @@
|
||||
bitDepth: track.bitDepth,
|
||||
sampleRate: track.samplingRate,
|
||||
});
|
||||
showToast(`Preparing "${track.title}" for offline`, 'info');
|
||||
showToast($t('toast.preparingTrackOffline', { 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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2053,9 +2060,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();
|
||||
@@ -2106,12 +2113,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', { name: info.userName }), 'success');
|
||||
|
||||
// Load favorites now that login is confirmed (sync with Qobuz)
|
||||
loadFavorites(); // Track favorites
|
||||
@@ -2239,10 +2246,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3222,7 +3229,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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user