feat: implement i18n — extract all UI strings, add EN + ES-MX translations, add language selector
Some checks failed
CI / Unit & Component Tests (push) Successful in 9m36s
CI / Backend Unit Tests (push) Successful in 2m15s
CI / E2E Tests (push) Failing after 14m41s

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:
Marcel
2026-03-19 12:39:36 +01:00
committed by marcel
parent db6dc28528
commit 0e76be5672
20 changed files with 733 additions and 199 deletions

View File

@@ -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}