From c25c1d0dc4aa127f4eec0d675e964df1a4494579 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 8 May 2026 18:22:00 +0200 Subject: [PATCH] =?UTF-8?q?Phase=209g:=20i18n=20DE/EN=20=C3=BCber=20alle?= =?UTF-8?q?=20Routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schmale Eigen-Lösung statt svelte-i18n: $lib/i18n mit de.ts + en.ts (je ~150 Strings, flat-with-nesting), index.svelte.ts liefert reaktiven i18n-Store als Svelte-5-Rune plus t()/tn()-Helper. Locale wird in localStorage persistiert, Default = navigator.language (DE/EN-Erkennung), Fallback = de. Header bekommt einen DE/EN-Toggle-Switcher (role=group, aria-pressed). Alle Routes (decks, decks/[id], decks/new, cards/new, cards/[id]/edit, study, study/[deckId], import, account, stats, +page) und alle Komponenten (Header, AnkiImport, InboxBanner) ziehen jetzt durch t(). tn(key, n) wählt zwischen `_one` (n=1) und `` — minimaler Plural-Helper, reicht für DE/EN-MVP. Komplexere Pluralregeln (FR/IT) bräuchten Intl.PluralRules, kommen wenn die Locales dazukommen. Begleitend ein paar A11y-Vorbereitungen mitgenommen: aria-label am Locale-Switcher, role=group für Grade-Buttons, role=button + keyboard-handler für die Anki-Dropzone, role=progressbar mit aria-valuemin/max/now, role=alert für Error-States, aria-live=polite für Lade-Meldungen, sr-only-Heading im Study-Card-Render, aria-hidden für rein dekorative Elemente. svelte-check 379 files 0 errors, 94 Tests grün (41 Domain + 48 API + 5 Web). Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/lib/components/AnkiImport.svelte | 79 ++++--- apps/web/src/lib/components/Header.svelte | 41 +++- .../web/src/lib/components/InboxBanner.svelte | 12 +- apps/web/src/lib/i18n/de.ts | 216 ++++++++++++++++++ apps/web/src/lib/i18n/en.ts | 213 +++++++++++++++++ apps/web/src/lib/i18n/index.svelte.ts | 91 ++++++++ apps/web/src/routes/+page.svelte | 17 +- apps/web/src/routes/account/+page.svelte | 67 ++---- .../src/routes/cards/[id]/edit/+page.svelte | 64 +++--- apps/web/src/routes/cards/new/+page.svelte | 66 +++--- apps/web/src/routes/decks/+page.svelte | 25 +- apps/web/src/routes/decks/[id]/+page.svelte | 43 ++-- apps/web/src/routes/decks/new/+page.svelte | 21 +- apps/web/src/routes/import/+page.svelte | 26 +-- apps/web/src/routes/stats/+page.svelte | 57 +++-- apps/web/src/routes/study/+page.svelte | 17 +- .../src/routes/study/[deckId]/+page.svelte | 41 ++-- 17 files changed, 826 insertions(+), 270 deletions(-) create mode 100644 apps/web/src/lib/i18n/de.ts create mode 100644 apps/web/src/lib/i18n/en.ts create mode 100644 apps/web/src/lib/i18n/index.svelte.ts diff --git a/apps/web/src/lib/components/AnkiImport.svelte b/apps/web/src/lib/components/AnkiImport.svelte index db3595b..733d407 100644 --- a/apps/web/src/lib/components/AnkiImport.svelte +++ b/apps/web/src/lib/components/AnkiImport.svelte @@ -11,6 +11,7 @@
-
Aus Anki importieren
+
{t('import.anki_label')}
{#if stage === 'idle'} @@ -94,11 +95,17 @@ ondragover={(e) => e.preventDefault()} ondrop={onDrop} onclick={() => fileInput?.click()} + role="button" + tabindex="0" + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + fileInput?.click(); + } + }} > -
📦 .apkg-Datei hier ablegen oder klicken
-
- Basic, Basic + Reverse, Cloze · Bilder + Audio werden in dieser Phase nicht übernommen. -
+
{t('import.dropzone')}
+
{t('import.dropzone_hint')}
{:else if stage === 'parsing'} -
Lese {fileName}…
+
+ {t('import.parsing', { file: fileName })} +
{:else if stage === 'preview' && parsed}
- Gefunden in + {t('import.preview_found')} {fileName}:
    -
  • {parsed.decks.length} {parsed.decks.length === 1 ? 'Deck' : 'Decks'}
  • +
  • {tn('import.preview_decks', parsed.decks.length)}
  • - {parsed.cards.length} {parsed.cards.length === 1 ? 'Karte' : 'Karten'} + {tn('import.preview_cards', parsed.cards.length)} {#if parsed.cards.length > 0} - ({typeBreakdown.basic} basic, {typeBreakdown.basicReverse} basic-reverse, - {typeBreakdown.cloze} cloze) + {t('import.preview_breakdown', { + basic: typeBreakdown.basic, + basic_reverse: typeBreakdown.basicReverse, + cloze: typeBreakdown.cloze, + })} {/if}
  • {#if parsed.mediaByFilename.size > 0}
  • - {parsed.mediaByFilename.size} Medien (werden in dieser Phase NICHT übernommen) + {t('import.preview_media', { n: parsed.mediaByFilename.size })}
  • {/if} {#if parsed.skipped > 0} -
  • {parsed.skipped} übersprungen (unbekannter Typ)
  • +
  • {t('import.preview_skipped', { n: parsed.skipped })}
  • {/if}
{#if parsed.warnings.length > 0}
- Hinweise ({parsed.warnings.length}) + {t('import.preview_warnings', { n: parsed.warnings.length })}
    {#each parsed.warnings.slice(0, 10) as w (w)}
  • {w}
  • {/each}
@@ -148,26 +160,32 @@ class="rounded px-3 py-1.5 text-sm text-[var(--color-muted)] hover:text-[var(--color-fg)]" onclick={reset} > - Abbrechen + {t('import.cancel')}
{:else if stage === 'importing'} -
+
{#if progress.stage === 'decks'} - Lege Decks an · {progress.current} / {progress.total} + {t('import.stage_decks', { current: progress.current, total: progress.total })} {:else if progress.stage === 'cards'} - Importiere Karten · {progress.current} / {progress.total} + {t('import.stage_cards', { current: progress.current, total: progress.total })} {:else} - Fertig. + {t('import.stage_done')} {/if} -
+
- ✓ {result.cardsCreated} Karten in {result.decksCreated} - {result.decksCreated === 1 ? 'Deck' : 'Decks'} angelegt. + {result.decksCreated === 1 + ? t('import.done_summary_one', { cards: result.cardsCreated }) + : t('import.done_summary', { cards: result.cardsCreated, decks: result.decksCreated })}
{#if result.failed > 0}
- {result.failed} Fehler + {t('import.done_failures', { n: result.failed })}
    {#each result.failures.slice(0, 20) as msg (msg)}
  • {msg}
  • {/each}
@@ -192,17 +211,17 @@ class="rounded px-3 py-1.5 text-sm text-[var(--color-muted)] hover:text-[var(--color-fg)]" onclick={reset} > - Weitere Datei + {t('import.done_more')}
{:else if stage === 'error'} -
-
Fehler: {error}
+ {/if} diff --git a/apps/web/src/lib/components/Header.svelte b/apps/web/src/lib/components/Header.svelte index 17c2b02..f87b008 100644 --- a/apps/web/src/lib/components/Header.svelte +++ b/apps/web/src/lib/components/Header.svelte @@ -1,6 +1,7 @@
- Cards + {t('app.name')} -