refactor: extract LanguageSwitcher into a reusable component
Removes duplicated locale logic from +layout.svelte and AppNav.svelte. Context-specific sizing (text-xs/min-h-[44px]) stays in the wrapper via [&_button]: selectors so the component itself is layout-agnostic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
18
frontend/src/lib/components/LanguageSwitcher.svelte
Normal file
18
frontend/src/lib/components/LanguageSwitcher.svelte
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { setLocale, getLocale } from '$lib/paraglide/runtime';
|
||||||
|
|
||||||
|
const locales = ['DE', 'EN', 'ES'] as const;
|
||||||
|
const localeMap = { DE: 'de', EN: 'en', ES: 'es' } as const;
|
||||||
|
const activeLocale = $derived(getLocale().toUpperCase());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each locales as locale (locale)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => setLocale(localeMap[locale])}
|
||||||
|
class="font-sans tracking-widest transition-colors
|
||||||
|
{activeLocale === locale ? 'font-bold text-ink' : 'font-normal text-ink-3 hover:text-ink'}"
|
||||||
|
>
|
||||||
|
{locale}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
@@ -2,17 +2,13 @@
|
|||||||
import './layout.css';
|
import './layout.css';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { setLocale, getLocale } from '$lib/paraglide/runtime';
|
import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte';
|
||||||
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
||||||
import AppNav from './AppNav.svelte';
|
import AppNav from './AppNav.svelte';
|
||||||
import UserMenu from './UserMenu.svelte';
|
import UserMenu from './UserMenu.svelte';
|
||||||
|
|
||||||
let { children, data } = $props();
|
let { children, data } = $props();
|
||||||
|
|
||||||
const locales = ['DE', 'EN', 'ES'] as const;
|
|
||||||
const localeMap = { DE: 'de', EN: 'en', ES: 'es' } as const;
|
|
||||||
const activeLocale = $derived(getLocale().toUpperCase());
|
|
||||||
|
|
||||||
const isAdmin = $derived(
|
const isAdmin = $derived(
|
||||||
data?.user?.groups?.some((g: { permissions: string[] }) => g.permissions.includes('ADMIN'))
|
data?.user?.groups?.some((g: { permissions: string[] }) => g.permissions.includes('ADMIN'))
|
||||||
);
|
);
|
||||||
@@ -47,19 +43,10 @@ const userInitials = $derived.by(() => {
|
|||||||
<!-- Right Side -->
|
<!-- Right Side -->
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<!-- Language selector (desktop only — mobile lives in nav drawer) -->
|
<!-- Language selector (desktop only — mobile lives in nav drawer) -->
|
||||||
<div class="hidden items-center gap-1 border-r border-line pr-3 sm:flex">
|
<div
|
||||||
{#each locales as locale (locale)}
|
class="hidden items-center gap-1 border-r border-line pr-3 sm:flex [&_button]:px-1.5 [&_button]:py-1 [&_button]:text-xs"
|
||||||
<button
|
>
|
||||||
type="button"
|
<LanguageSwitcher />
|
||||||
onclick={() => setLocale(localeMap[locale])}
|
|
||||||
class="px-1.5 py-1 font-sans text-xs tracking-widest transition-colors
|
|
||||||
{activeLocale === locale
|
|
||||||
? 'font-bold text-ink'
|
|
||||||
: 'font-normal text-ink-3 hover:text-ink'}"
|
|
||||||
>
|
|
||||||
{locale}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Theme toggle -->
|
<!-- Theme toggle -->
|
||||||
|
|||||||
@@ -2,14 +2,10 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
import { setLocale, getLocale } from '$lib/paraglide/runtime';
|
import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte';
|
||||||
|
|
||||||
let { isAdmin = false }: { isAdmin?: boolean } = $props();
|
let { isAdmin = false }: { isAdmin?: boolean } = $props();
|
||||||
|
|
||||||
const locales = ['DE', 'EN', 'ES'] as const;
|
|
||||||
const localeMap = { DE: 'de', EN: 'en', ES: 'es' } as const;
|
|
||||||
const activeLocale = $derived(getLocale().toUpperCase());
|
|
||||||
|
|
||||||
let mobileNavOpen = $state(false);
|
let mobileNavOpen = $state(false);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -187,19 +183,10 @@ function handleOverlayKeydown(event: KeyboardEvent) {
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Language switcher -->
|
<!-- Language switcher -->
|
||||||
<div class="flex items-center gap-2 border-t border-line px-4 py-3">
|
<div
|
||||||
{#each locales as locale (locale)}
|
class="flex items-center gap-2 border-t border-line px-4 py-3 [&_button]:min-h-[44px] [&_button]:px-3 [&_button]:text-sm"
|
||||||
<button
|
>
|
||||||
type="button"
|
<LanguageSwitcher />
|
||||||
onclick={() => setLocale(localeMap[locale])}
|
|
||||||
class="min-h-[44px] px-3 font-sans text-sm tracking-widest transition-colors
|
|
||||||
{activeLocale === locale
|
|
||||||
? 'font-bold text-ink'
|
|
||||||
: 'font-normal text-ink-3 hover:text-ink'}"
|
|
||||||
>
|
|
||||||
{locale}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user