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

@@ -2,6 +2,7 @@
import { goto } from '$app/navigation';
import PersonTypeahead from '$lib/components/PersonTypeahead.svelte';
import { untrack } from 'svelte';
import { m } from '$lib/paraglide/messages.js';
let { data } = $props();
@@ -39,9 +40,9 @@
<div class="max-w-5xl mx-auto py-10 px-4">
<!-- Page Header -->
<div class="mb-8 border-b border-brand-navy/10 pb-4">
<h1 class="text-3xl font-serif font-medium text-brand-navy">Konversationen</h1>
<h1 class="text-3xl font-serif font-medium text-brand-navy">{m.conv_heading()}</h1>
<p class="text-brand-navy/60 font-sans text-sm mt-2">
Verfolgen Sie den Schriftverkehr zwischen zwei Personen chronologisch.
{m.conv_subtitle()}
</p>
</div>
@@ -54,7 +55,7 @@
>
<PersonTypeahead
name="senderId"
label="Person A (Absender)"
label={m.conv_label_person_a()}
bind:value={senderId}
initialName={data.initialValues.senderName}
onchange={() => applyFilters()}
@@ -67,7 +68,7 @@
>
<PersonTypeahead
name="receiverId"
label="Person B (Empfänger)"
label={m.conv_label_person_b()}
bind:value={receiverId}
initialName={data.initialValues.receiverName}
onchange={() => applyFilters()}
@@ -81,7 +82,7 @@
<label
for="dateFrom"
class="block text-xs font-bold uppercase tracking-widest text-gray-500 mb-2"
>Zeitraum von</label
>{m.conv_label_from()}</label
>
<input
id="dateFrom"
@@ -97,7 +98,7 @@
<label
for="dateTo"
class="block text-xs font-bold uppercase tracking-widest text-gray-500 mb-2"
>Zeitraum bis</label
>{m.conv_label_to()}</label
>
<input
id="dateTo"
@@ -114,8 +115,8 @@
onclick={toggleSort}
class="w-full flex items-center justify-center h-[42px] border border-brand-sand text-xs font-bold uppercase tracking-wide text-brand-navy hover:bg-brand-navy hover:text-white transition-colors"
>
<span class="mr-2">Sortierung:</span>
<span>{sortDir === 'DESC' ? 'Neueste zuerst' : 'Älteste zuerst'}</span>
<span class="mr-2">{m.conv_sort_label()}</span>
<span>{sortDir === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()}</span>
<svg
class="w-4 h-4 ml-2 transform {sortDir === 'ASC'
? 'rotate-180'
@@ -147,15 +148,15 @@
/></svg
>
</div>
<p class="text-brand-navy font-serif text-lg">Wählen Sie zwei Personen aus</p>
<p class="text-gray-500 font-sans text-sm mt-1">Die Korrespondenz wird hier angezeigt.</p>
<p class="text-brand-navy font-serif text-lg">{m.conv_empty_heading()}</p>
<p class="text-gray-500 font-sans text-sm mt-1">{m.conv_empty_text()}</p>
</div>
{:else if data.documents.length === 0}
<div
class="flex flex-col items-center justify-center py-24 bg-white border border-brand-sand rounded-sm text-center shadow-sm"
>
<p class="text-brand-navy font-serif">Keine Dokumente gefunden.</p>
<p class="text-gray-400 text-sm mt-2">Versuchen Sie, den Zeitraum anzupassen.</p>
<p class="text-brand-navy font-serif">{m.conv_no_results_heading()}</p>
<p class="text-gray-400 text-sm mt-2">{m.conv_no_results_text()}</p>
</div>
{:else}
<!-- CHAT CONTAINER -->