wordeck/apps/web/src/lib/i18n/en.ts
Till JS fd86d968a4 Phase 9h: A11y-Pass
Globaler :focus-visible-Outline (var(--color-primary), 2px) — Tailwind 4
strippt die Browser-Defaults, ohne Fokus-Ring sind Tastatur-Nutzer blind.
.sr-only-Utility (Standard-Rezept) und .skip-link in app.css. prefers-
reduced-motion: schaltet alle Transitions/Animationen auf 0.01ms.

Layout: Skip-Link "Zum Inhalt springen" → #main, main bekommt tabindex="-1"
und id, html-lang wird via $effect reaktiv mit i18n.current synchronisiert
(Initial-SSR rendert "de", Browser zieht nach).

Header: nav-aria-label aus i18n (common.main_nav), Locale-Switcher-Label
aus common.language_switcher. ToastStack: role=region + aria-live=polite,
einzelne Toasts role=alert (error) bzw. status (success/warning), Schließen-
Button-Label i18n-konform.

Hover-only Delete-Buttons (Decks-Liste, Deck-Detail-Karten) bekommen
focus-visible:opacity-100 — bisher waren sie für Tastatur-Nutzer
unsichtbar. opacity-0 statt hidden, damit Tab-Order intakt bleibt.

svelte-check 379 files 0 errors, prod-Build sauber.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:24:33 +02:00

217 lines
7.6 KiB
TypeScript

import type { TranslationNode } from './de.ts';
export const en: TranslationNode = {
app: {
name: 'Cards',
title_suffix: 'Cards',
},
nav: {
decks: 'Decks',
study: 'Study',
import: 'Import',
stats: 'Stats',
login_dev: 'Login (dev)',
account: 'Account',
},
landing: {
welcome: 'Spaced-repetition flashcards.',
intro:
'Cardecky is the federated flashcard app of mana e.V. — FSRS scheduler, cloze cards, Anki import.',
cta_login: 'Login (dev)',
dev_user_prompt: 'User ID (dev):',
},
decks: {
title: 'Decks',
new: 'New deck',
empty: 'No decks yet.',
empty_cta: 'Create first deck',
loading: 'Loading…',
error: 'Error: {msg}',
card_count: '{n} cards',
card_count_one: '1 card',
due_count: '{n} due',
delete_confirm:
'Really delete deck "{name}"? All cards + review data will be lost.',
deleted: 'Deck "{name}" deleted',
delete_failed: 'Delete failed: {msg}',
},
deck_detail: {
back: '← Back to decks',
study_button: 'Study',
new_card: 'New card',
empty: 'No cards in this deck.',
empty_cta: 'Create first card →',
card_summary_due: '{cards} · {due} due',
card_delete_aria: 'Delete card',
card_delete_label: 'Delete',
card_delete_confirm: 'Really delete card? Reviews will be deleted with it.',
},
deck_new: {
title: 'New deck',
name_label: 'Name',
description_label: 'Description (optional)',
color_label: 'Color (optional)',
create: 'Create deck',
creating: 'Creating…',
cancel: 'Cancel',
create_failed: 'Create failed: {msg}',
},
card_new: {
title: 'New card',
back: '← Back',
deck_label: 'Deck',
type_label: 'Type',
type_basic: 'Basic (front → back)',
type_basic_reverse: 'Basic + Reverse (front ↔ back, 2 reviews)',
type_cloze: 'Cloze (fill-in-the-blank, 1 review per cluster)',
front_label: 'Front (Markdown)',
back_label: 'Back (Markdown)',
back_placeholder: 'Answer',
front_placeholder: '# Markdown is allowed\n**bold**, _italic_, `code`',
preview_label: 'Preview',
cloze_text_label: 'Text with blanks (Markdown)',
cloze_text_placeholder: 'The capital of {{c1::France}} is {{c2::Paris}}.',
cloze_help: '{{c1::Answer}} defines a blank. Each cluster ID (c1, c2, …) becomes its own review.',
cloze_no_clusters: 'At least one {{cN::…}} cluster is required.',
cloze_clusters_detected: '{n} clusters detected: c{ids} → {n} reviews.',
cloze_preview_label: 'Preview (c{first} masked)',
cloze_extra_label: 'Extra (optional)',
cloze_extra_placeholder: 'Additional context, shown below the answer.',
create: 'Create card',
creating: 'Saving…',
cancel: 'Cancel',
create_failed: 'Create failed: {msg}',
toast_basic: 'Card created',
toast_basic_reverse: '2 reviews initialized (front→back, back→front)',
toast_cloze: '{n} reviews initialized (1 per cluster)',
decks_load_failed: 'Could not load decks: {msg}',
},
card_edit: {
title: 'Edit card',
back: '← Back to deck',
type_locked_help: 'The card type cannot be changed — the reviews table depends on it.',
save: 'Save',
saving: 'Saving…',
cancel: 'Cancel',
delete: 'Delete',
deleting: 'Deleting…',
delete_confirm: 'Really delete card? Reviews will be deleted with it.',
updated: 'Card updated',
save_failed: 'Save failed: {msg}',
delete_failed: 'Delete failed: {msg}',
deleted: 'Card deleted',
},
study: {
title: 'Study',
empty: 'No decks.',
none_due: 'Nothing due right now.',
study_now: 'Study now',
due_count: '{n} due',
},
study_session: {
back: '← Overview',
all_done: 'Done! All due cards reviewed.',
stats: 'Reviews: {reviewed} · Again: {again}',
reveal: 'Show answer',
reveal_hint: 'Space / Enter to reveal',
grade_again: 'Again',
grade_hard: 'Hard',
grade_good: 'Good',
grade_easy: 'Easy',
grade_hint: '1=Again · 2=Hard · 3=Good · 4=Easy',
loading: 'Loading…',
error: 'Error: {msg}',
},
import: {
title: 'Import',
intro: 'Import decks and cards from an Anki file (.apkg or .colpkg). FSRS history is not carried over — all cards start as "new".',
what_works_title: 'What is imported',
what_works_decks: 'Decks (Anki hierarchy Foo::Bar becomes Foo / Bar).',
what_works_basic: 'Basic + Basic-Reverse: front/back directly.',
what_works_cloze: 'Cloze: {{c1::…}} is created with sub-index per cluster.',
what_skipped_title: 'What is not imported',
what_skipped_media: 'Images + audio (will arrive with the platform integration in a later phase).',
what_skipped_history: 'FSRS learning history (Anki reviews are deliberately reset).',
what_skipped_addons: 'Add-on specific card types (image-occlusion etc.).',
anki_label: 'Import from Anki',
dropzone: '📦 Drop .apkg file here or click',
dropzone_hint: 'Basic, Basic + Reverse, Cloze · Images + audio are not imported in this phase.',
parsing: 'Reading {file}…',
preview_found: 'Found in',
preview_decks_one: '1 deck',
preview_decks: '{n} decks',
preview_cards_one: '1 card',
preview_cards: '{n} cards',
preview_breakdown: '({basic} basic, {basic_reverse} basic-reverse, {cloze} cloze)',
preview_media: '{n} media files (will NOT be imported in this phase)',
preview_skipped: '{n} skipped (unknown type)',
preview_warnings: 'Notes ({n})',
cancel: 'Cancel',
import_now: 'Import',
stage_decks: 'Creating decks · {current} / {total}',
stage_cards: 'Importing cards · {current} / {total}',
stage_done: 'Done.',
done_summary_one: '✓ {cards} cards in 1 deck.',
done_summary: '✓ {cards} cards in {decks} decks.',
done_failures: '{n} errors',
done_more: 'Another file',
error_label: 'Error: {msg}',
retry: 'Try again',
},
inbox_banner: {
label: '📥 Inbox',
count_one: '1 incoming card from other apps',
count: '{n} incoming cards from other apps',
cta: '— sort →',
},
account: {
title: 'Account',
user_id_label: 'User ID',
logout: 'Sign out',
phase2_hint:
'Phase 2 note: identity is currently a dev stub (sessionStorage). With auth federation, this switches to a mana-auth login against auth.mana.how.',
export_title: 'Data export',
export_intro:
'Download all your Cards data as JSON — decks, cards, reviews, study sessions, tags, media refs, import jobs. GDPR Art. 15/20.',
export_button: 'Export data',
export_loading: 'Loading…',
export_done: 'Export downloaded: {decks} decks, {cards} cards, {reviews} reviews.',
export_failed: 'Export failed: {msg}',
delete_title: 'Delete account',
delete_intro:
'Irreversibly deletes all your Cards data. GDPR Art. 17. Other mana apps (Memoro, Who, …) keep their data independently — if you want to delete there too, do it there or use the Verein-wide GDPR collective request via mana-admin.',
delete_button: 'Delete all Cards data',
delete_loading: 'Deleting…',
delete_confirm:
'ALL your Cards data will be deleted irreversibly. Type DELETE to confirm.',
delete_confirm_word: 'DELETE',
delete_done: 'Deleted: {decks} decks, {imports} import jobs.',
delete_failed: 'Delete failed: {msg}',
},
stats: {
title: 'Stats',
generated_at: 'As of {date}',
decks: 'Decks',
cards: 'Cards',
reviews: 'Reviews',
due_now: 'Due now',
days_title: 'Active days',
streak: 'Streak: {n} days · last 7 days: {total} reviews',
streak_one: 'Streak: 1 day · last 7 days: {total} reviews',
fsrs_title: 'FSRS status',
fsrs_intro: 'Distribution of your card reviews across FSRS states.',
fsrs_new: 'New',
fsrs_learning: 'Learning',
fsrs_review: 'Review',
fsrs_relearning: 'Relearning',
loading: 'Loading…',
error: 'Error: {msg}',
},
common: {
empty: '(empty)',
skip_to_content: 'Skip to content',
main_nav: 'Main navigation',
notifications: 'Notifications',
language_switcher: 'Switch language',
},
};