Some checks are pending
CI / validate (push) Waiting to run
Karten mit ≥4 Lapses werden im Stats-Endpoint als `leech_cards` geliefert (mit Front-Snippet, Deck-Name, Lapses-Count, sortiert desc, max 20). Stats-Page zeigt eine rote „Schwierige Karten"- Sektion mit Link in den Card-Editor. * apps/api/src/routes/me.ts: GROUP BY card → SUM(lapses) ≥ 4 Filter, frontSnippetFor()-Helper für alle 6 Card-Types (basic, basic-reverse, cloze, image-occlusion, audio-front, typing, multiple-choice). Cloze-Markup wird gestrippt damit der Snippet UI-tauglich ist. * apps/web Stats-Page: neue CardSurface mit Warning-Icon, scrollbare Liste mit Front + Deck + Lapses-Badge, Empty-State. * i18n in DE/EN/FR/IT/ES (5 Strings + plural-Form). 104/104 Tests grün (kein neuer Test — bestehende /me/stats-Tests covern die Aggregations-Form), web check 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
335 lines
12 KiB
TypeScript
335 lines
12 KiB
TypeScript
import type { TranslationNode } from './de.ts';
|
|
|
|
export const en: TranslationNode = {
|
|
app: {
|
|
name: 'Cards',
|
|
title_suffix: 'Cards',
|
|
},
|
|
nav: {
|
|
decks: 'Decks',
|
|
study: 'Study',
|
|
explore: 'Library',
|
|
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',
|
|
card_count_more: '{n} more cards in the stack',
|
|
card_count_more_one: '1 more card in the stack',
|
|
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.',
|
|
fan_aria: 'Fanned cards from stack "{name}"',
|
|
card_open: 'Open card — {type}',
|
|
export_csv: 'CSV',
|
|
print_cards: 'Print',
|
|
},
|
|
deck_stack: {
|
|
aria_label: 'Stack "{name}" — {cards} cards, {due} due',
|
|
},
|
|
deck_edit: {
|
|
title: 'Edit deck',
|
|
back: '← Back to deck',
|
|
name_label: 'Name',
|
|
description_label: 'Description (optional)',
|
|
color_label: 'Color',
|
|
save: 'Save',
|
|
saving: 'Saving…',
|
|
cancel: 'Cancel',
|
|
save_failed: 'Save failed: {msg}',
|
|
saved: 'Deck saved',
|
|
},
|
|
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. Optional hint: {{c1::Answer::Hint}} — the hint replaces „…" in the prompt.',
|
|
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)',
|
|
toast_image_occlusion: '{n} reviews initialized (1 per mask)',
|
|
type_image_occlusion: 'Image-Occlusion (image + N masks)',
|
|
type_typing: 'Typing (text input, fuzzy match)',
|
|
type_multiple_choice: 'Multiple-Choice (4 options, AI distractors)',
|
|
type_audio_front: 'Audio-Front (listen + answer)',
|
|
answer_label: 'Answer (Markdown)',
|
|
answer_placeholder: 'Correct answer',
|
|
distractor_pool_label: 'Distractor pool (optional)',
|
|
distractor_pool_placeholder: 'One entry per line — fallback when the deck is too small for AI distractors',
|
|
audio_ref_label: 'Audio reference (media_ref)',
|
|
audio_ref_placeholder: 'e.g. abc123.mp3',
|
|
audio_upload_btn: '🎵 Upload audio file',
|
|
audio_uploading: 'Uploading…',
|
|
audio_upload_failed: 'Upload failed: {msg}',
|
|
audio_replace: 'Replace',
|
|
typing_aliases_label: 'Aliases (optional)',
|
|
typing_aliases_hint: 'Comma-separated alternative answers — e.g. Paris, Paris (France)',
|
|
back_audio_label: 'Answer text (Markdown)',
|
|
toast_typing: 'Typing card created',
|
|
toast_multiple_choice: 'Multiple-choice card created',
|
|
toast_audio_front: 'Audio-front card created',
|
|
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}',
|
|
manage_link: 'Manage cards →',
|
|
},
|
|
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_works_media: 'Images + audio (embedded as Markdown / <audio> tag).',
|
|
what_skipped_title: 'What is not imported',
|
|
what_skipped_media: '— (Images + audio are imported since Sprint 9k, see above)',
|
|
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 imported too (limit 25 MB per file).',
|
|
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 be uploaded',
|
|
preview_skipped: '{n} skipped (unknown type)',
|
|
preview_warnings: 'Notes ({n})',
|
|
cancel: 'Cancel',
|
|
import_now: 'Import',
|
|
stage_media: 'Uploading media · {current} / {total}',
|
|
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_dupes: '{n} duplicates skipped (same content already exists).',
|
|
done_media: '{uploaded} media uploaded, {failed} failed.',
|
|
done_failures: '{n} errors',
|
|
done_more: 'Another file',
|
|
error_label: 'Error: {msg}',
|
|
retry: 'Try again',
|
|
tab_anki: 'Anki',
|
|
tab_csv: 'CSV',
|
|
tab_quizlet: 'Tab format',
|
|
csv_label: 'Import CSV file',
|
|
csv_dropzone: '📄 Drop CSV file here or click',
|
|
csv_dropzone_hint:
|
|
'Format: type,front,back — header row is optional. Supported: basic, basic-reverse, cloze.',
|
|
csv_deck_label: 'Deck name',
|
|
csv_deck_placeholder: 'My imported deck',
|
|
csv_done_summary: '✓ {cards} cards added to "{deck}".',
|
|
csv_format_title: 'CSV format',
|
|
csv_format_example:
|
|
'type,front,back\nbasic,What is 2+2?,4\nbasic-reverse,Paris,Capital of France\ncloze,"The {{c1::mitochondria}} are the powerhouse",',
|
|
csv_format_note: 'Without a type column all cards are created as basic.',
|
|
quizlet_label: 'Import tab-separated format',
|
|
quizlet_hint:
|
|
'Works with Quizlet (via browser extension), Excel, Notion, Google Sheets, or any tool that can copy tab-separated rows.',
|
|
quizlet_paste_label: 'Paste text (term Tab definition)',
|
|
quizlet_placeholder: 'Term\tDefinition',
|
|
quizlet_deck_label: 'Deck name',
|
|
quizlet_deck_placeholder: 'My imported deck',
|
|
quizlet_preview: 'Preview',
|
|
quizlet_how_title: 'Where to get this format',
|
|
quizlet_how_1: 'Excel / Google Sheets: select two columns, copy → paste here.',
|
|
quizlet_how_2: 'Notion table: select columns, copy → paste here.',
|
|
quizlet_how_3:
|
|
'Quizlet: export is paywalled. Free workaround: install the "Quizlet Exporter" browser extension → export as tab CSV → paste here.',
|
|
},
|
|
print: {
|
|
title: 'Print {deck}',
|
|
loading: 'Loading…',
|
|
btn: 'Print',
|
|
back: '← Back',
|
|
n_cards_one: '1 card',
|
|
n_cards: '{n} cards',
|
|
front: 'Front',
|
|
back_side: 'Back',
|
|
},
|
|
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',
|
|
edit_profile: 'Edit profile',
|
|
name_label: 'Name',
|
|
name_placeholder: 'Your name',
|
|
save: 'Save',
|
|
cancel: 'Cancel',
|
|
profile_saved: 'Profile saved',
|
|
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',
|
|
activity_title: 'Activity history',
|
|
activity_desc: '{weeks} weeks · each cell = 1 day',
|
|
retention_title: 'Retention',
|
|
retention_desc: 'Share of reviews without a lapse.',
|
|
retention_reps: '{reps} reviews',
|
|
retention_lapses: '{lapses} lapses',
|
|
retention_none: 'No reviews yet.',
|
|
forecast_title: 'Next 7 days',
|
|
forecast_desc: 'Cards due per day.',
|
|
forecast_empty: 'Nothing scheduled.',
|
|
leeches_title: 'Tough cards',
|
|
leeches_desc: 'Cards with {threshold}+ lapses — candidates to reword or split.',
|
|
leeches_none: 'No leeches — clean slate.',
|
|
leeches_lapses: '{n} lapses',
|
|
leeches_lapses_one: '{n} lapse',
|
|
loading: 'Loading…',
|
|
error: 'Error: {msg}',
|
|
},
|
|
common: {
|
|
empty: '(empty)',
|
|
skip_to_content: 'Skip to content',
|
|
main_nav: 'Main navigation',
|
|
notifications: 'Notifications',
|
|
language_switcher: 'Switch language',
|
|
language_desc: 'Choose the language of the app.',
|
|
},
|
|
image_occlusion: {
|
|
image_label: 'Choose image',
|
|
uploading: 'Uploading image…',
|
|
not_an_image: 'File is not an image.',
|
|
canvas_aria: 'Image canvas — drag to create masks',
|
|
draw_hint: 'Drag a rectangle on the image to create a mask.',
|
|
label_placeholder: 'Label (optional)',
|
|
delete_mask: 'Delete mask',
|
|
no_image_selected: 'Choose an image first.',
|
|
no_masks: 'Create at least one mask.',
|
|
},
|
|
};
|