feat: implement i18n — extract all UI strings, add EN + ES-MX translations, add language selector
Extract all hardcoded German strings from every .svelte file and component into Paraglide message keys. Add complete translations for all keys in messages/en.json (English) and messages/es.json (Spanish/Mexico). Changes: - messages/de.json: 100+ keys covering navigation, buttons, form labels, placeholders, section headings, empty states, and error messages - messages/en.json, messages/es.json: complete translations for all keys - project.inlang/settings.json: change baseLocale from "en" to "de" - +layout.svelte: add DE/EN/ES language selector in header using setLocale(); active language is bold, choice persists via Paraglide cookie strategy - All 10 route pages + 3 shared components: replace hardcoded German with m.key() - e2e/lang.spec.ts: E2E tests for language selector visibility, switching, persistence across navigation, and active state highlighting Closes #2 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #9.
This commit is contained in:
@@ -3,9 +3,15 @@
|
||||
import { enhance } from '$app/forms';
|
||||
import { page } from '$app/state';
|
||||
import { onMount } from 'svelte';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import { setLocale, getLocale } from '$lib/paraglide/runtime';
|
||||
|
||||
let { children } = $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(page.data.user?.groups.some((g: { permissions: string[] }) => g.permissions.includes('ADMIN')));
|
||||
|
||||
// Set after client-side hydration completes. Used by E2E tests to know the
|
||||
@@ -40,7 +46,7 @@
|
||||
? 'text-brand-navy bg-brand-purple/15 rounded'
|
||||
: 'text-gray-500 hover:text-brand-navy hover:bg-brand-sand/60 rounded'}"
|
||||
>
|
||||
Dokumente
|
||||
{m.nav_documents()}
|
||||
</a>
|
||||
|
||||
<a
|
||||
@@ -50,7 +56,7 @@
|
||||
? 'text-brand-navy bg-brand-purple/15 rounded'
|
||||
: 'text-gray-500 hover:text-brand-navy hover:bg-brand-sand/60 rounded'}"
|
||||
>
|
||||
Personen
|
||||
{m.nav_persons()}
|
||||
</a>
|
||||
|
||||
<a
|
||||
@@ -60,7 +66,7 @@
|
||||
? 'text-brand-navy bg-brand-purple/15 rounded'
|
||||
: 'text-gray-500 hover:text-brand-navy hover:bg-brand-sand/60 rounded'}"
|
||||
>
|
||||
Konversationen
|
||||
{m.nav_conversations()}
|
||||
</a>
|
||||
{#if isAdmin}
|
||||
<a
|
||||
@@ -70,25 +76,41 @@
|
||||
? 'text-brand-navy bg-brand-purple/15 rounded'
|
||||
: 'text-gray-500 hover:text-brand-navy hover:bg-brand-sand/60 rounded'}"
|
||||
>
|
||||
Admin
|
||||
{m.nav_admin()}
|
||||
</a>
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Right Side -->
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Language selector -->
|
||||
<div class="flex items-center gap-1 border-r border-gray-200 pr-3">
|
||||
{#each locales as locale}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => setLocale(localeMap[locale])}
|
||||
class="text-xs font-sans tracking-widest px-1.5 py-1 transition-colors
|
||||
{activeLocale === locale
|
||||
? 'font-bold text-brand-navy'
|
||||
: 'font-normal text-gray-400 hover:text-brand-navy'}"
|
||||
>
|
||||
{locale}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<form action="/logout" method="POST" use:enhance>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center gap-1.5 text-xs text-gray-400 hover:text-brand-navy font-bold uppercase font-sans tracking-widest px-3 py-2 transition-colors"
|
||||
>
|
||||
<img src="/degruyter-icons/Simple/Small-16px/SVG/Action/Account-SM.svg" alt="" aria-hidden="true" class="w-4 h-4 opacity-50" />
|
||||
Abmelden
|
||||
{m.nav_logout()}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user