diff --git a/COMMANDS.md b/COMMANDS.md index 8b09289a0..2da4bdd08 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -9,6 +9,8 @@ pnpm dev:chat:app pnpm dev:picture:app +pnpm dev:manacore:app + Übersicht aller wichtigen Befehle zum Starten, Stoppen und Verwalten der Apps. diff --git a/apps/chat/apps/web/src/lib/components/LanguageSelector.svelte b/apps/chat/apps/web/src/lib/components/LanguageSelector.svelte index 8b7cd2b55..5630e7582 100644 --- a/apps/chat/apps/web/src/lib/components/LanguageSelector.svelte +++ b/apps/chat/apps/web/src/lib/components/LanguageSelector.svelte @@ -1,20 +1,19 @@ - + diff --git a/apps/chat/apps/web/src/routes/(protected)/+layout.svelte b/apps/chat/apps/web/src/routes/(protected)/+layout.svelte index fe5b63795..7dc8c44b4 100644 --- a/apps/chat/apps/web/src/routes/(protected)/+layout.svelte +++ b/apps/chat/apps/web/src/routes/(protected)/+layout.svelte @@ -2,6 +2,7 @@ import { onMount } from 'svelte'; import { goto } from '$app/navigation'; import { page } from '$app/stores'; + import { locale } from 'svelte-i18n'; import { authStore } from '$lib/stores/auth.svelte'; import { theme } from '$lib/stores/theme'; import { THEME_DEFINITIONS } from '@manacore/shared-theme'; @@ -12,6 +13,8 @@ import { PillNavigation } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; import { getPillAppItems } from '@manacore/shared-branding'; + import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; + import { setLocale, supportedLocales } from '$lib/i18n'; import type { LayoutData } from './$types'; // App switcher items @@ -49,6 +52,16 @@ // Current theme variant label let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label); + // Language selector items + let currentLocale = $derived($locale || 'de'); + function handleLocaleChange(newLocale: string) { + setLocale(newLocale as any); + } + let languageItems = $derived( + getLanguageDropdownItems(supportedLocales, currentLocale, handleLocaleChange) + ); + let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // Navigation items for Chat (settings moved to user dropdown) const navItems: PillNavItem[] = [ { href: '/chat', label: 'Chat', icon: 'home' }, @@ -180,7 +193,9 @@ {currentThemeVariantLabel} themeMode={theme.mode} onThemeModeChange={handleThemeModeChange} - showLanguageSwitcher={false} + showLanguageSwitcher={true} + {languageItems} + {currentLanguageLabel} showLogout={true} onLogout={handleLogout} primaryColor="#3b82f6" diff --git a/apps/manacore/apps/web/src/lib/components/LanguageSelector.svelte b/apps/manacore/apps/web/src/lib/components/LanguageSelector.svelte index 01544fd58..5630e7582 100644 --- a/apps/manacore/apps/web/src/lib/components/LanguageSelector.svelte +++ b/apps/manacore/apps/web/src/lib/components/LanguageSelector.svelte @@ -1,21 +1,19 @@ - + diff --git a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte index bf0ec1668..d7c562299 100644 --- a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte @@ -3,9 +3,12 @@ import { page } from '$app/stores'; import type { Snippet } from 'svelte'; import { onMount } from 'svelte'; + import { locale } from 'svelte-i18n'; import { PillNavigation } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; import { THEME_DEFINITIONS } from '@manacore/shared-theme'; + import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; + import { setLocale, supportedLocales } from '$lib/i18n'; import { theme } from '$lib/stores/theme'; import { authStore } from '$lib/stores/authStore.svelte'; import { @@ -47,6 +50,16 @@ // Current theme variant label let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label); + // Language selector items + let currentLocale = $derived($locale || 'de'); + function handleLocaleChange(newLocale: string) { + setLocale(newLocale as any); + } + let languageItems = $derived( + getLanguageDropdownItems(supportedLocales, currentLocale, handleLocaleChange) + ); + let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // User email for user dropdown let userEmail = $derived(authStore.user?.email); @@ -167,7 +180,9 @@ {currentThemeVariantLabel} themeMode={theme.mode} onThemeModeChange={handleThemeModeChange} - showLanguageSwitcher={false} + showLanguageSwitcher={true} + {languageItems} + {currentLanguageLabel} showLogout={true} primaryColor="#6366f1" showAppSwitcher={true} diff --git a/apps/manadeck/apps/web/src/lib/components/LanguageSelector.svelte b/apps/manadeck/apps/web/src/lib/components/LanguageSelector.svelte index f740acefa..5630e7582 100644 --- a/apps/manadeck/apps/web/src/lib/components/LanguageSelector.svelte +++ b/apps/manadeck/apps/web/src/lib/components/LanguageSelector.svelte @@ -1,21 +1,19 @@ - + diff --git a/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte b/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte index adcab661d..bd73f2e96 100644 --- a/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { onMount } from 'svelte'; + import { locale } from 'svelte-i18n'; import { authStore } from '$lib/stores/authStore.svelte'; import { theme } from '$lib/stores/theme'; import { @@ -11,6 +12,8 @@ import { PillNavigation } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; import { THEME_DEFINITIONS } from '@manacore/shared-theme'; + import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; + import { setLocale, supportedLocales } from '$lib/i18n'; let { children } = $props(); @@ -50,6 +53,16 @@ // Current theme variant label let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label); + // Language selector items + let currentLocale = $derived($locale || 'de'); + function handleLocaleChange(newLocale: string) { + setLocale(newLocale as any); + } + let languageItems = $derived( + getLanguageDropdownItems(supportedLocales, currentLocale, handleLocaleChange) + ); + let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // Navigation shortcuts (Ctrl+1-5) const navRoutes = navItems.map((item) => item.href); @@ -165,7 +178,9 @@ {currentThemeVariantLabel} themeMode={theme.mode} onThemeModeChange={handleThemeModeChange} - showLanguageSwitcher={false} + showLanguageSwitcher={true} + {languageItems} + {currentLanguageLabel} primaryColor="#6366f1" /> diff --git a/apps/picture/apps/web/src/lib/components/LanguageSelector.svelte b/apps/picture/apps/web/src/lib/components/LanguageSelector.svelte index f6101b6c6..5630e7582 100644 --- a/apps/picture/apps/web/src/lib/components/LanguageSelector.svelte +++ b/apps/picture/apps/web/src/lib/components/LanguageSelector.svelte @@ -1,20 +1,19 @@ - + diff --git a/apps/picture/apps/web/src/routes/app/+layout.svelte b/apps/picture/apps/web/src/routes/app/+layout.svelte index dfb6cc830..37403a29e 100644 --- a/apps/picture/apps/web/src/routes/app/+layout.svelte +++ b/apps/picture/apps/web/src/routes/app/+layout.svelte @@ -2,9 +2,12 @@ import { authStore } from '$lib/stores/auth.svelte'; import { goto } from '$app/navigation'; import { page } from '$app/stores'; + import { locale } from 'svelte-i18n'; import { PillNavigation } from '@manacore/shared-ui'; import type { PillNavItem, PillNavElement, PillDropdownItem } from '@manacore/shared-ui'; import { THEME_DEFINITIONS } from '@manacore/shared-theme'; + import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; + import { setLocale, supportedLocales } from '$lib/i18n'; import KeyboardShortcutsModal from '$lib/components/ui/KeyboardShortcutsModal.svelte'; import { theme } from '$lib/stores/theme'; import { isUIVisible, toggleUI, showKeyboardShortcuts } from '$lib/stores/ui'; @@ -97,6 +100,16 @@ // Current theme variant label let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label); + // Language selector items + let currentLocale = $derived($locale || 'de'); + function handleLocaleChange(newLocale: string) { + setLocale(newLocale as any); + } + let languageItems = $derived( + getLanguageDropdownItems(supportedLocales, currentLocale, handleLocaleChange) + ); + let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // Elements (divider + view mode tabs) let elements: PillNavElement[] = $derived([ { type: 'divider' as const }, @@ -209,7 +222,9 @@ {currentThemeVariantLabel} themeMode={theme.mode} onThemeModeChange={handleThemeModeChange} - showLanguageSwitcher={false} + showLanguageSwitcher={true} + {languageItems} + {currentLanguageLabel} primaryColor="#3b82f6" /> {/if} diff --git a/apps/presi/apps/web/src/lib/components/LanguageSelector.svelte b/apps/presi/apps/web/src/lib/components/LanguageSelector.svelte index 072a68eac..5630e7582 100644 --- a/apps/presi/apps/web/src/lib/components/LanguageSelector.svelte +++ b/apps/presi/apps/web/src/lib/components/LanguageSelector.svelte @@ -1,20 +1,19 @@ - + diff --git a/apps/presi/apps/web/src/routes/+layout.svelte b/apps/presi/apps/web/src/routes/+layout.svelte index d7c24dd04..364acc7b2 100644 --- a/apps/presi/apps/web/src/routes/+layout.svelte +++ b/apps/presi/apps/web/src/routes/+layout.svelte @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { onMount } from 'svelte'; + import { locale } from 'svelte-i18n'; import { PillNavigation } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; import { auth } from '$lib/stores/auth.svelte'; @@ -11,6 +12,8 @@ isSidebarMode as sidebarModeStore, isNavCollapsed as collapsedStore, } from '$lib/stores/navigation'; + import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; + import { setLocale, supportedLocales } from '$lib/i18n'; import '../app.css'; let { children } = $props(); @@ -40,6 +43,16 @@ // Current theme variant label let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label); + // Language selector items + let currentLocale = $derived($locale || 'de'); + function handleLocaleChange(newLocale: string) { + setLocale(newLocale as any); + } + let languageItems = $derived( + getLanguageDropdownItems(supportedLocales, currentLocale, handleLocaleChange) + ); + let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // Navigation items for Presi const navItems: PillNavItem[] = [ { href: '/', label: 'Decks', icon: 'document' }, @@ -173,7 +186,9 @@ {currentThemeVariantLabel} themeMode={theme.mode} onThemeModeChange={handleThemeModeChange} - showLanguageSwitcher={false} + showLanguageSwitcher={true} + {languageItems} + {currentLanguageLabel} showLogout={true} onLogout={handleLogout} primaryColor="#64748b" diff --git a/apps/zitare/apps/web/src/lib/components/LanguageSelector.svelte b/apps/zitare/apps/web/src/lib/components/LanguageSelector.svelte index 97183be0b..5630e7582 100644 --- a/apps/zitare/apps/web/src/lib/components/LanguageSelector.svelte +++ b/apps/zitare/apps/web/src/lib/components/LanguageSelector.svelte @@ -1,20 +1,19 @@ - + diff --git a/apps/zitare/apps/web/src/routes/+layout.svelte b/apps/zitare/apps/web/src/routes/+layout.svelte index 6117ce893..49df3f13e 100644 --- a/apps/zitare/apps/web/src/routes/+layout.svelte +++ b/apps/zitare/apps/web/src/routes/+layout.svelte @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { onMount } from 'svelte'; + import { locale } from 'svelte-i18n'; import { PillNavigation } from '@manacore/shared-ui'; import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; import { theme } from '$lib/stores/theme'; @@ -11,6 +12,8 @@ isSidebarMode as sidebarModeStore, isNavCollapsed as collapsedStore, } from '$lib/stores/navigation'; + import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; + import { setLocale, supportedLocales } from '$lib/i18n'; import ToastContainer from '$lib/components/ToastContainer.svelte'; import '../app.css'; @@ -46,6 +49,16 @@ // Current theme variant label let currentThemeVariantLabel = $derived(THEME_DEFINITIONS[theme.variant].label); + // Language selector items + let currentLocale = $derived($locale || 'de'); + function handleLocaleChange(newLocale: string) { + setLocale(newLocale as any); + } + let languageItems = $derived( + getLanguageDropdownItems(supportedLocales, currentLocale, handleLocaleChange) + ); + let currentLanguageLabel = $derived(getCurrentLanguageLabel(currentLocale)); + // Navigation items for Zitare const navItems: PillNavItem[] = [ { href: '/', label: 'Start', icon: 'home' }, @@ -175,7 +188,9 @@ {currentThemeVariantLabel} themeMode={theme.mode} onThemeModeChange={handleThemeModeChange} - showLanguageSwitcher={false} + showLanguageSwitcher={true} + {languageItems} + {currentLanguageLabel} showLogout={authStore.isAuthenticated} onLogout={handleLogout} primaryColor="#f59e0b" diff --git a/packages/shared-i18n/src/index.ts b/packages/shared-i18n/src/index.ts index dae0bd33b..983fd7a43 100644 --- a/packages/shared-i18n/src/index.ts +++ b/packages/shared-i18n/src/index.ts @@ -34,6 +34,10 @@ export { formatRelativeTime, getPluralCategory, interpolate, + // PillDropdown language helpers + type LanguageDropdownItem, + getLanguageDropdownItems, + getCurrentLanguageLabel, } from './utils'; // Common translations diff --git a/packages/shared-i18n/src/utils.ts b/packages/shared-i18n/src/utils.ts index 6b3bd0e48..3b9c454b3 100644 --- a/packages/shared-i18n/src/utils.ts +++ b/packages/shared-i18n/src/utils.ts @@ -2,7 +2,7 @@ * i18n utility functions */ -import { type LanguageCode, isLanguageSupported } from './languages'; +import { type LanguageCode, isLanguageSupported, getLanguageInfo } from './languages'; /** * Detect user's preferred locale from browser @@ -241,3 +241,50 @@ export function interpolate(text: string, values: Record void; + active?: boolean; +} + +/** + * Create PillDropdown items for language selection + * @param supportedLocales - Array of supported locale codes (e.g., ['de', 'en', 'it', 'fr', 'es']) + * @param currentLocale - Currently selected locale + * @param onLocaleChange - Callback when locale changes + * @returns Array of items compatible with PillDropdown + */ +export function getLanguageDropdownItems( + supportedLocales: readonly string[], + currentLocale: string, + onLocaleChange: (locale: string) => void +): LanguageDropdownItem[] { + return supportedLocales.map((locale) => { + const info = getLanguageInfo(locale); + return { + id: locale, + label: info ? `${info.emoji} ${info.nativeName}` : locale.toUpperCase(), + onClick: () => onLocaleChange(locale), + active: currentLocale === locale, + }; + }); +} + +/** + * Get current language label for PillDropdown trigger + * @param currentLocale - Currently selected locale + * @returns Label with flag emoji and language code (e.g., "🇩🇪 DE") + */ +export function getCurrentLanguageLabel(currentLocale: string): string { + const info = getLanguageInfo(currentLocale); + if (info) { + return `${info.emoji} ${currentLocale.toUpperCase()}`; + } + return currentLocale.toUpperCase(); +} diff --git a/packages/shared-ui/src/navigation/PillNavigation.svelte b/packages/shared-ui/src/navigation/PillNavigation.svelte index a9a1fa6e6..6510fc3dd 100644 --- a/packages/shared-ui/src/navigation/PillNavigation.svelte +++ b/packages/shared-ui/src/navigation/PillNavigation.svelte @@ -338,7 +338,6 @@ items={languageItems} direction="down" label={currentLanguageLabel} - icon="globe" /> {/if}