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}