Add i18n support with English and Spanish locales
- Add svelte-i18n dependency - Create i18n configuration with locale persistence - Add complete translation files for EN and ES - Create root layout with theme CSS variables - Support 4 themes: dark (default), light, oled, warm - Add global styles and spacing system
This commit is contained in:
1 parent
8a094ef467
commit
ef92897d43
6 files changed
+3160
-1
No files matched your search
@@ -0,0 +1,39 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { init, register, getLocaleFromNavigator, locale } from 'svelte-i18n';
|
||||
|
||||
// Register locales
|
||||
register('en', () => import('./locales/en.json'));
|
||||
register('es', () => import('./locales/es.json'));
|
||||
|
||||
// Initialize i18n
|
||||
export function initI18n() {
|
||||
init({
|
||||
fallbackLocale: 'en',
|
||||
initialLocale: browser ? getStoredLocale() || getLocaleFromNavigator() : 'en',
|
||||
});
|
||||
}
|
||||
|
||||
// Get stored locale from localStorage
|
||||
function getStoredLocale(): string | null {
|
||||
if (!browser) return null;
|
||||
return localStorage.getItem('qbz-locale');
|
||||
}
|
||||
|
||||
// Set and persist locale
|
||||
export function setLocale(newLocale: string) {
|
||||
if (browser) {
|
||||
localStorage.setItem('qbz-locale', newLocale);
|
||||
}
|
||||
locale.set(newLocale);
|
||||
}
|
||||
|
||||
// Available locales
|
||||
export const locales = [
|
||||
{ code: 'en', name: 'English', nativeName: 'English' },
|
||||
{ code: 'es', name: 'Spanish', nativeName: 'Español' },
|
||||
] as const;
|
||||
|
||||
export type LocaleCode = (typeof locales)[number]['code'];
|
||||
|
||||
// Re-export svelte-i18n utilities for convenience
|
||||
export { t, locale, locales as availableLocales } from 'svelte-i18n';
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "QBZ-NIX",
|
||||
"tagline": "Hi-Fi Qobuz Client for Linux"
|
||||
},
|
||||
"nav": {
|
||||
"home": "Home",
|
||||
"search": "Search",
|
||||
"library": "Library",
|
||||
"albums": "Albums",
|
||||
"artists": "Artists",
|
||||
"tracks": "Tracks",
|
||||
"playlists": "Playlists",
|
||||
"newReleases": "New Releases",
|
||||
"radio": "Radio",
|
||||
"recentlyAdded": "Recently Added"
|
||||
},
|
||||
"home": {
|
||||
"title": "Home",
|
||||
"featuredForYou": "Featured for you",
|
||||
"recentlyPlayed": "Recently Played",
|
||||
"newReleases": "New Releases",
|
||||
"madeForYou": "Made for You",
|
||||
"topPicks": "Top Picks"
|
||||
},
|
||||
"search": {
|
||||
"title": "Search",
|
||||
"placeholder": "Search albums, artists, tracks...",
|
||||
"all": "All",
|
||||
"noResults": "No results found",
|
||||
"searching": "Searching..."
|
||||
},
|
||||
"album": {
|
||||
"tracks": "tracks",
|
||||
"track": "track",
|
||||
"by": "by",
|
||||
"released": "Released",
|
||||
"label": "Label",
|
||||
"genre": "Genre"
|
||||
},
|
||||
"artist": {
|
||||
"albums": "Albums",
|
||||
"popularTracks": "Popular Tracks",
|
||||
"similarArtists": "Similar Artists",
|
||||
"followers": "followers"
|
||||
},
|
||||
"playlist": {
|
||||
"tracks": "tracks",
|
||||
"createNew": "Create New...",
|
||||
"allPlaylists": "All Playlists",
|
||||
"myPlaylists": "My Playlists"
|
||||
},
|
||||
"player": {
|
||||
"nowPlaying": "Now Playing",
|
||||
"queue": "Queue",
|
||||
"lyrics": "Lyrics",
|
||||
"shuffle": "Shuffle",
|
||||
"repeat": "Repeat",
|
||||
"repeatOne": "Repeat One",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"play": "Play",
|
||||
"pause": "Pause",
|
||||
"volume": "Volume",
|
||||
"mute": "Mute"
|
||||
},
|
||||
"quality": {
|
||||
"mp3": "MP3",
|
||||
"cdQuality": "CD Quality",
|
||||
"hiRes": "Hi-Res",
|
||||
"ultraHiRes": "Ultra Hi-Res",
|
||||
"flac": "FLAC",
|
||||
"bitDepth": "{depth}-bit",
|
||||
"sampleRate": "{rate} kHz",
|
||||
"lossless": "Lossless"
|
||||
},
|
||||
"actions": {
|
||||
"play": "Play",
|
||||
"playAll": "Play All",
|
||||
"shuffle": "Shuffle",
|
||||
"addToQueue": "Add to Queue",
|
||||
"addToPlaylist": "Add to Playlist",
|
||||
"addToFavorites": "Add to Favorites",
|
||||
"removeFromFavorites": "Remove from Favorites",
|
||||
"download": "Download",
|
||||
"share": "Share",
|
||||
"viewAlbum": "View Album",
|
||||
"viewArtist": "View Artist",
|
||||
"more": "More",
|
||||
"back": "Back",
|
||||
"seeAll": "See All",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"account": {
|
||||
"title": "Account",
|
||||
"loggedInAs": "Logged in as",
|
||||
"subscription": "Subscription",
|
||||
"logout": "Logout",
|
||||
"login": "Login"
|
||||
},
|
||||
"audio": {
|
||||
"title": "Audio",
|
||||
"streamingQuality": "Streaming Quality",
|
||||
"preferHighest": "Prefer highest available",
|
||||
"outputDevice": "Output Device",
|
||||
"exclusiveMode": "Exclusive Mode",
|
||||
"exclusiveModeDesc": "Lock audio device for exclusive use",
|
||||
"dacPassthrough": "DAC Passthrough",
|
||||
"dacPassthroughDesc": "Bypass system mixer for bit-perfect output",
|
||||
"currentSampleRate": "Current Sample Rate"
|
||||
},
|
||||
"playback": {
|
||||
"title": "Playback",
|
||||
"gapless": "Gapless Playback",
|
||||
"crossfade": "Crossfade",
|
||||
"crossfadeSeconds": "{seconds}s",
|
||||
"normalizeVolume": "Normalize Volume"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
"theme": "Theme",
|
||||
"themes": {
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"oled": "OLED Black",
|
||||
"warm": "Warm"
|
||||
},
|
||||
"accentColor": "Accent Color",
|
||||
"compactMode": "Compact Mode",
|
||||
"language": "Language"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integrations",
|
||||
"lastfm": "Last.fm",
|
||||
"connected": "Connected",
|
||||
"notConnected": "Not connected",
|
||||
"connect": "Connect",
|
||||
"disconnect": "Disconnect",
|
||||
"scrobbling": "Scrobbling"
|
||||
},
|
||||
"storage": {
|
||||
"title": "Storage",
|
||||
"cacheSize": "Cache Size",
|
||||
"clearCache": "Clear Cache"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"loginButton": "Sign In",
|
||||
"loggingIn": "Signing in...",
|
||||
"loginError": "Login failed. Please check your credentials.",
|
||||
"welcomeBack": "Welcome back"
|
||||
},
|
||||
"errors": {
|
||||
"generic": "Something went wrong",
|
||||
"networkError": "Network error. Please check your connection.",
|
||||
"notFound": "Not found",
|
||||
"unauthorized": "Please log in to continue",
|
||||
"trackNotAvailable": "This track is not available for streaming",
|
||||
"qualityNotAvailable": "Requested quality is not available"
|
||||
},
|
||||
"time": {
|
||||
"hours": "h",
|
||||
"minutes": "min",
|
||||
"seconds": "s"
|
||||
},
|
||||
"empty": {
|
||||
"noAlbums": "No albums yet",
|
||||
"noArtists": "No artists yet",
|
||||
"noTracks": "No tracks yet",
|
||||
"noPlaylists": "No playlists yet",
|
||||
"noFavorites": "No favorites yet",
|
||||
"queueEmpty": "Queue is empty"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "QBZ-NIX",
|
||||
"tagline": "Cliente Hi-Fi de Qobuz para Linux"
|
||||
},
|
||||
"nav": {
|
||||
"home": "Inicio",
|
||||
"search": "Buscar",
|
||||
"library": "Biblioteca",
|
||||
"albums": "Álbumes",
|
||||
"artists": "Artistas",
|
||||
"tracks": "Canciones",
|
||||
"playlists": "Playlists",
|
||||
"newReleases": "Novedades",
|
||||
"radio": "Radio",
|
||||
"recentlyAdded": "Añadido recientemente"
|
||||
},
|
||||
"home": {
|
||||
"title": "Inicio",
|
||||
"featuredForYou": "Sugerencias destacadas para ti",
|
||||
"recentlyPlayed": "Escuchado recientemente",
|
||||
"newReleases": "Nuevos lanzamientos",
|
||||
"madeForYou": "Hecho para ti",
|
||||
"topPicks": "Selecciones destacadas"
|
||||
},
|
||||
"search": {
|
||||
"title": "Buscar",
|
||||
"placeholder": "Buscar álbumes, artistas, canciones...",
|
||||
"all": "Todo",
|
||||
"noResults": "No se encontraron resultados",
|
||||
"searching": "Buscando..."
|
||||
},
|
||||
"album": {
|
||||
"tracks": "canciones",
|
||||
"track": "canción",
|
||||
"by": "de",
|
||||
"released": "Publicado",
|
||||
"label": "Sello",
|
||||
"genre": "Género"
|
||||
},
|
||||
"artist": {
|
||||
"albums": "Álbumes",
|
||||
"popularTracks": "Canciones populares",
|
||||
"similarArtists": "Artistas similares",
|
||||
"followers": "seguidores"
|
||||
},
|
||||
"playlist": {
|
||||
"tracks": "canciones",
|
||||
"createNew": "Crear nueva...",
|
||||
"allPlaylists": "Todas las playlists",
|
||||
"myPlaylists": "Mis playlists"
|
||||
},
|
||||
"player": {
|
||||
"nowPlaying": "Reproduciendo ahora",
|
||||
"queue": "Cola",
|
||||
"lyrics": "Letra",
|
||||
"shuffle": "Aleatorio",
|
||||
"repeat": "Repetir",
|
||||
"repeatOne": "Repetir una",
|
||||
"previous": "Anterior",
|
||||
"next": "Siguiente",
|
||||
"play": "Reproducir",
|
||||
"pause": "Pausar",
|
||||
"volume": "Volumen",
|
||||
"mute": "Silenciar"
|
||||
},
|
||||
"quality": {
|
||||
"mp3": "MP3",
|
||||
"cdQuality": "Calidad CD",
|
||||
"hiRes": "Hi-Res",
|
||||
"ultraHiRes": "Ultra Hi-Res",
|
||||
"flac": "FLAC",
|
||||
"bitDepth": "{depth}-bit",
|
||||
"sampleRate": "{rate} kHz",
|
||||
"lossless": "Sin pérdida"
|
||||
},
|
||||
"actions": {
|
||||
"play": "Reproducir",
|
||||
"playAll": "Reproducir todo",
|
||||
"shuffle": "Aleatorio",
|
||||
"addToQueue": "Añadir a la cola",
|
||||
"addToPlaylist": "Añadir a playlist",
|
||||
"addToFavorites": "Añadir a favoritos",
|
||||
"removeFromFavorites": "Quitar de favoritos",
|
||||
"download": "Descargar",
|
||||
"share": "Compartir",
|
||||
"viewAlbum": "Ver álbum",
|
||||
"viewArtist": "Ver artista",
|
||||
"more": "Más",
|
||||
"back": "Atrás",
|
||||
"seeAll": "Ver todo",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar",
|
||||
"edit": "Editar"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configuración",
|
||||
"account": {
|
||||
"title": "Cuenta",
|
||||
"loggedInAs": "Conectado como",
|
||||
"subscription": "Suscripción",
|
||||
"logout": "Cerrar sesión",
|
||||
"login": "Iniciar sesión"
|
||||
},
|
||||
"audio": {
|
||||
"title": "Audio",
|
||||
"streamingQuality": "Calidad de streaming",
|
||||
"preferHighest": "Preferir la más alta disponible",
|
||||
"outputDevice": "Dispositivo de salida",
|
||||
"exclusiveMode": "Modo exclusivo",
|
||||
"exclusiveModeDesc": "Bloquear dispositivo de audio para uso exclusivo",
|
||||
"dacPassthrough": "Passthrough DAC",
|
||||
"dacPassthroughDesc": "Bypass del mixer del sistema para salida bit-perfect"
|
||||
},
|
||||
"playback": {
|
||||
"title": "Reproducción",
|
||||
"gapless": "Reproducción sin pausas",
|
||||
"crossfade": "Crossfade",
|
||||
"crossfadeSeconds": "{seconds}s",
|
||||
"normalizeVolume": "Normalizar volumen"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Apariencia",
|
||||
"theme": "Tema",
|
||||
"themes": {
|
||||
"dark": "Oscuro",
|
||||
"light": "Claro",
|
||||
"oled": "OLED Negro",
|
||||
"warm": "Cálido"
|
||||
},
|
||||
"accentColor": "Color de acento",
|
||||
"compactMode": "Modo compacto",
|
||||
"language": "Idioma"
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Integraciones",
|
||||
"lastfm": "Last.fm",
|
||||
"connected": "Conectado",
|
||||
"notConnected": "No conectado",
|
||||
"connect": "Conectar",
|
||||
"disconnect": "Desconectar",
|
||||
"scrobbling": "Scrobbling"
|
||||
},
|
||||
"storage": {
|
||||
"title": "Almacenamiento",
|
||||
"cacheSize": "Tamaño de caché",
|
||||
"clearCache": "Limpiar caché"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"login": "Iniciar sesión",
|
||||
"email": "Correo electrónico",
|
||||
"password": "Contraseña",
|
||||
"loginButton": "Entrar",
|
||||
"loggingIn": "Iniciando sesión...",
|
||||
"loginError": "Error al iniciar sesión. Verifica tus credenciales.",
|
||||
"welcomeBack": "Bienvenido de nuevo"
|
||||
},
|
||||
"errors": {
|
||||
"generic": "Algo salió mal",
|
||||
"networkError": "Error de red. Verifica tu conexión.",
|
||||
"notFound": "No encontrado",
|
||||
"unauthorized": "Inicia sesión para continuar",
|
||||
"trackNotAvailable": "Esta canción no está disponible para streaming",
|
||||
"qualityNotAvailable": "La calidad solicitada no está disponible"
|
||||
},
|
||||
"time": {
|
||||
"hours": "h",
|
||||
"minutes": "min",
|
||||
"seconds": "s"
|
||||
},
|
||||
"empty": {
|
||||
"noAlbums": "Sin álbumes aún",
|
||||
"noArtists": "Sin artistas aún",
|
||||
"noTracks": "Sin canciones aún",
|
||||
"noPlaylists": "Sin playlists aún",
|
||||
"noFavorites": "Sin favoritos aún",
|
||||
"queueEmpty": "La cola está vacía"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { initI18n } from '$lib/i18n';
|
||||
import { isLoading } from 'svelte-i18n';
|
||||
|
||||
// Props
|
||||
let { children }: { children: Snippet } = $props();
|
||||
|
||||
// Initialize i18n
|
||||
initI18n();
|
||||
|
||||
// Wait for translations to load
|
||||
let ready = $derived(!$isLoading);
|
||||
</script>
|
||||
|
||||
{#if ready}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
:global(*) {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:global(html, body) {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:global(body) {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-primary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* CSS Variables - Dark Theme (Default) */
|
||||
:global(:root) {
|
||||
/* Backgrounds */
|
||||
--bg-primary: #0f0f0f;
|
||||
--bg-secondary: #1a1a1a;
|
||||
--bg-tertiary: #2a2a2a;
|
||||
--bg-hover: #1f1f1f;
|
||||
|
||||
/* Text */
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #cccccc;
|
||||
--text-muted: #888888;
|
||||
--text-disabled: #555555;
|
||||
|
||||
/* Accent */
|
||||
--accent-primary: #4285F4;
|
||||
--accent-hover: #5a9bf4;
|
||||
--accent-active: #3275e4;
|
||||
|
||||
/* Borders */
|
||||
--border-subtle: #2a2a2a;
|
||||
--border-strong: #3a3a3a;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 12px;
|
||||
--spacing-lg: 16px;
|
||||
--spacing-xl: 24px;
|
||||
--spacing-2xl: 32px;
|
||||
--spacing-3xl: 48px;
|
||||
|
||||
/* Layout */
|
||||
--sidebar-width: 240px;
|
||||
--now-playing-height: 80px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease-out;
|
||||
--transition-normal: 200ms ease-out;
|
||||
--transition-slow: 300ms ease-out;
|
||||
}
|
||||
|
||||
/* Light Theme */
|
||||
:global([data-theme="light"]) {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f5f5f5;
|
||||
--bg-tertiary: #e8e8e8;
|
||||
--bg-hover: #f0f0f0;
|
||||
--text-primary: #0f0f0f;
|
||||
--text-secondary: #444444;
|
||||
--text-muted: #666666;
|
||||
--text-disabled: #999999;
|
||||
--border-subtle: #e0e0e0;
|
||||
--border-strong: #cccccc;
|
||||
}
|
||||
|
||||
/* OLED Black Theme */
|
||||
:global([data-theme="oled"]) {
|
||||
--bg-primary: #000000;
|
||||
--bg-secondary: #0a0a0a;
|
||||
--bg-tertiary: #1a1a1a;
|
||||
--bg-hover: #111111;
|
||||
}
|
||||
|
||||
/* Warm Theme */
|
||||
:global([data-theme="warm"]) {
|
||||
--bg-primary: #1a1814;
|
||||
--bg-secondary: #242018;
|
||||
--bg-tertiary: #2e2820;
|
||||
--bg-hover: #282218;
|
||||
--accent-primary: #d4a574;
|
||||
--accent-hover: #e0b888;
|
||||
--accent-active: #c49460;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid var(--bg-tertiary);
|
||||
border-top-color: var(--accent-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
:global(::-webkit-scrollbar) {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
:global(::-webkit-scrollbar-track) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:global(::-webkit-scrollbar-thumb) {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
:global(::-webkit-scrollbar-thumb:hover) {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Selection */
|
||||
:global(::selection) {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Focus visible */
|
||||
:global(:focus-visible) {
|
||||
outline: 2px solid var(--accent-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Button reset */
|
||||
:global(button) {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Link reset */
|
||||
:global(a) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Input reset */
|
||||
:global(input, textarea, select) {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user