mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
i18n(quiz): translate EditView via $_() — header, meta inputs, question list, new-question form
- Header: back-button aria + "Quiz" label, Spielen play button
- Empty: "Quiz nicht gefunden."
- Meta-section: Titel/Beschreibung/Kategorie/Tags placeholders, Sichtbarkeit row label, untitled fallback
- Question list: "Fragen ({n})" heading, empty state, type-pill, edit/delete title+aria, "Frage löschen?" confirm
- Question types routed through $_('quiz.question_types.' + q.type); QUESTION_TYPE_LABELS constant kept in types.ts for non-Svelte callers
- New-question section: edit/new heading, cancel button, type select (4 options), question/correct-answer/expected-input fields, options-label (multi/single variant), correct-toggle title+aria, "Antwort {n}" placeholder, remove aria, "Antwort hinzufügen", explanation field, save/add submit button
- truefalse default options ("Wahr"/"Falsch") now i18n'd
Baselines: hardcoded 1218 → 1205 (13 cleared); missing-keys baseline +1 (quiz.question_types.* dynamic key).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0fbef25565
commit
84bc904775
3 changed files with 64 additions and 44 deletions
|
|
@ -6,10 +6,10 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { useQuiz, useQuestions, blankOption } from './queries';
|
import { useQuiz, useQuestions, blankOption } from './queries';
|
||||||
import { quizzesStore } from './stores/quizzes.svelte';
|
import { quizzesStore } from './stores/quizzes.svelte';
|
||||||
import { QUESTION_TYPE_LABELS } from './types';
|
|
||||||
import type { QuestionType, QuestionOption, QuizQuestion } from './types';
|
import type { QuestionType, QuestionOption, QuizQuestion } from './types';
|
||||||
import { ArrowLeft, Plus, Trash, Check, Play, PencilSimple, X } from '@mana/shared-icons';
|
import { ArrowLeft, Plus, Trash, Check, Play, PencilSimple, X } from '@mana/shared-icons';
|
||||||
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
|
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
quizId: string;
|
quizId: string;
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
async function saveMeta() {
|
async function saveMeta() {
|
||||||
if (!quiz) return;
|
if (!quiz) return;
|
||||||
await quizzesStore.updateQuiz(quiz.id, {
|
await quizzesStore.updateQuiz(quiz.id, {
|
||||||
title: metaTitle.trim() || 'Unbenannt',
|
title: metaTitle.trim() || $_('quiz.edit_view.untitled_fallback'),
|
||||||
description: metaDescription.trim() || null,
|
description: metaDescription.trim() || null,
|
||||||
category: metaCategory.trim() || null,
|
category: metaCategory.trim() || null,
|
||||||
tags: metaTags
|
tags: metaTags
|
||||||
|
|
@ -71,8 +71,8 @@
|
||||||
function defaultOptions(type: QuestionType): QuestionOption[] {
|
function defaultOptions(type: QuestionType): QuestionOption[] {
|
||||||
if (type === 'truefalse') {
|
if (type === 'truefalse') {
|
||||||
return [
|
return [
|
||||||
{ id: 't', text: 'Wahr', isCorrect: true },
|
{ id: 't', text: $_('quiz.edit_view.truefalse_true'), isCorrect: true },
|
||||||
{ id: 'f', text: 'Falsch', isCorrect: false },
|
{ id: 'f', text: $_('quiz.edit_view.truefalse_false'), isCorrect: false },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (type === 'text') return [];
|
if (type === 'text') return [];
|
||||||
|
|
@ -166,7 +166,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteQuestion(id: string) {
|
async function deleteQuestion(id: string) {
|
||||||
if (!confirm('Frage löschen?')) return;
|
if (!confirm($_('quiz.edit_view.confirm_delete_question'))) return;
|
||||||
await quizzesStore.deleteQuestion(id);
|
await quizzesStore.deleteQuestion(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,8 +181,9 @@
|
||||||
|
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<button class="back" onclick={() => goto('/quiz')} aria-label="Zurück">
|
<button class="back" onclick={() => goto('/quiz')} aria-label={$_('quiz.edit_view.back_aria')}>
|
||||||
<ArrowLeft size={18} /> Quiz
|
<ArrowLeft size={18} />
|
||||||
|
{$_('quiz.edit_view.back_label')}
|
||||||
</button>
|
</button>
|
||||||
{#if quiz}
|
{#if quiz}
|
||||||
<button
|
<button
|
||||||
|
|
@ -190,13 +191,14 @@
|
||||||
disabled={questions.length === 0}
|
disabled={questions.length === 0}
|
||||||
onclick={() => goto(`/quiz/${quiz.id}/play`)}
|
onclick={() => goto(`/quiz/${quiz.id}/play`)}
|
||||||
>
|
>
|
||||||
<Play size={14} weight="fill" /> Spielen
|
<Play size={14} weight="fill" />
|
||||||
|
{$_('quiz.edit_view.action_play')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if !quiz}
|
{#if !quiz}
|
||||||
<p class="empty">Quiz nicht gefunden.</p>
|
<p class="empty">{$_('quiz.edit_view.empty_quiz')}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<section class="meta-section">
|
<section class="meta-section">
|
||||||
<input
|
<input
|
||||||
|
|
@ -204,13 +206,13 @@
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={metaTitle}
|
bind:value={metaTitle}
|
||||||
onblur={saveMeta}
|
onblur={saveMeta}
|
||||||
placeholder="Titel"
|
placeholder={$_('quiz.edit_view.placeholder_title')}
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
class="desc-input"
|
class="desc-input"
|
||||||
bind:value={metaDescription}
|
bind:value={metaDescription}
|
||||||
onblur={saveMeta}
|
onblur={saveMeta}
|
||||||
placeholder="Beschreibung (optional)"
|
placeholder={$_('quiz.edit_view.placeholder_description')}
|
||||||
rows="2"
|
rows="2"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="meta-row">
|
<div class="meta-row">
|
||||||
|
|
@ -219,18 +221,18 @@
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={metaCategory}
|
bind:value={metaCategory}
|
||||||
onblur={saveMeta}
|
onblur={saveMeta}
|
||||||
placeholder="Kategorie"
|
placeholder={$_('quiz.edit_view.placeholder_category')}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
class="small-input"
|
class="small-input"
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={metaTags}
|
bind:value={metaTags}
|
||||||
onblur={saveMeta}
|
onblur={saveMeta}
|
||||||
placeholder="Tags (Komma-getrennt)"
|
placeholder={$_('quiz.edit_view.placeholder_tags')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="visibility-row">
|
<div class="visibility-row">
|
||||||
<span class="visibility-label">Sichtbarkeit</span>
|
<span class="visibility-label">{$_('quiz.edit_view.label_visibility')}</span>
|
||||||
<VisibilityPicker
|
<VisibilityPicker
|
||||||
level={quiz.visibility ?? 'space'}
|
level={quiz.visibility ?? 'space'}
|
||||||
onChange={handleVisibilityChange}
|
onChange={handleVisibilityChange}
|
||||||
|
|
@ -240,28 +242,28 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="questions-section">
|
<section class="questions-section">
|
||||||
<h2>Fragen ({questions.length})</h2>
|
<h2>{$_('quiz.edit_view.section_questions', { values: { n: questions.length } })}</h2>
|
||||||
{#if questions.length === 0}
|
{#if questions.length === 0}
|
||||||
<p class="empty">Noch keine Fragen — füge unten eine hinzu.</p>
|
<p class="empty">{$_('quiz.edit_view.empty_questions')}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<ol class="question-list">
|
<ol class="question-list">
|
||||||
{#each questions as q, i (q.id)}
|
{#each questions as q, i (q.id)}
|
||||||
<li class="question-item" class:editing={editingId === q.id}>
|
<li class="question-item" class:editing={editingId === q.id}>
|
||||||
<div class="q-header">
|
<div class="q-header">
|
||||||
<span class="q-num">{i + 1}</span>
|
<span class="q-num">{i + 1}</span>
|
||||||
<span class="q-type">{QUESTION_TYPE_LABELS[q.type]}</span>
|
<span class="q-type">{$_('quiz.question_types.' + q.type)}</span>
|
||||||
<button
|
<button
|
||||||
class="icon-btn"
|
class="icon-btn"
|
||||||
title="Bearbeiten"
|
title={$_('quiz.edit_view.action_edit')}
|
||||||
aria-label="Bearbeiten"
|
aria-label={$_('quiz.edit_view.action_edit')}
|
||||||
onclick={() => startEdit(q)}
|
onclick={() => startEdit(q)}
|
||||||
>
|
>
|
||||||
<PencilSimple size={14} />
|
<PencilSimple size={14} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="icon-btn"
|
class="icon-btn"
|
||||||
title="Löschen"
|
title={$_('quiz.edit_view.action_delete')}
|
||||||
aria-label="Löschen"
|
aria-label={$_('quiz.edit_view.action_delete')}
|
||||||
onclick={() => deleteQuestion(q.id)}
|
onclick={() => deleteQuestion(q.id)}
|
||||||
>
|
>
|
||||||
<Trash size={14} />
|
<Trash size={14} />
|
||||||
|
|
@ -284,46 +286,61 @@
|
||||||
<div class="new-header">
|
<div class="new-header">
|
||||||
<h2>
|
<h2>
|
||||||
{editingId
|
{editingId
|
||||||
? `Frage ${questions.findIndex((x) => x.id === editingId) + 1} bearbeiten`
|
? $_('quiz.edit_view.new_section_edit', {
|
||||||
: 'Neue Frage'}
|
values: { n: questions.findIndex((x) => x.id === editingId) + 1 },
|
||||||
|
})
|
||||||
|
: $_('quiz.edit_view.new_section_new')}
|
||||||
</h2>
|
</h2>
|
||||||
{#if editingId}
|
{#if editingId}
|
||||||
<button class="cancel-btn" onclick={cancelEdit}>
|
<button class="cancel-btn" onclick={cancelEdit}>
|
||||||
<X size={12} /> Abbrechen
|
<X size={12} />
|
||||||
|
{$_('quiz.edit_view.action_cancel')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Typ</span>
|
<span>{$_('quiz.edit_view.label_type')}</span>
|
||||||
<select bind:value={newType} onchange={onTypeChange}>
|
<select bind:value={newType} onchange={onTypeChange}>
|
||||||
<option value="single">Single Choice</option>
|
<option value="single">{$_('quiz.question_types.single')}</option>
|
||||||
<option value="multi">Multiple Choice</option>
|
<option value="multi">{$_('quiz.question_types.multi')}</option>
|
||||||
<option value="truefalse">Wahr / Falsch</option>
|
<option value="truefalse">{$_('quiz.question_types.truefalse')}</option>
|
||||||
<option value="text">Texteingabe</option>
|
<option value="text">{$_('quiz.question_types.text')}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Frage</span>
|
<span>{$_('quiz.edit_view.label_question')}</span>
|
||||||
<textarea bind:value={newText} rows="2" placeholder="Was möchtest du fragen?"></textarea>
|
<textarea
|
||||||
|
bind:value={newText}
|
||||||
|
rows="2"
|
||||||
|
placeholder={$_('quiz.edit_view.placeholder_question')}
|
||||||
|
></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{#if newType === 'text'}
|
{#if newType === 'text'}
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Korrekte Antwort</span>
|
<span>{$_('quiz.edit_view.label_correct_answer')}</span>
|
||||||
<input type="text" bind:value={newTextAnswer} placeholder="Erwartete Eingabe" />
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={newTextAnswer}
|
||||||
|
placeholder={$_('quiz.edit_view.placeholder_expected')}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="options-block">
|
<div class="options-block">
|
||||||
<span class="options-label">
|
<span class="options-label">
|
||||||
Antworten {newType === 'multi' ? '(mehrere richtig möglich)' : '(eine richtig)'}
|
{newType === 'multi'
|
||||||
|
? $_('quiz.edit_view.options_label_multi')
|
||||||
|
: $_('quiz.edit_view.options_label_single')}
|
||||||
</span>
|
</span>
|
||||||
{#each newOptions as opt, i (opt.id)}
|
{#each newOptions as opt, i (opt.id)}
|
||||||
<div class="option-row">
|
<div class="option-row">
|
||||||
<button
|
<button
|
||||||
class="correct-toggle"
|
class="correct-toggle"
|
||||||
class:on={opt.isCorrect}
|
class:on={opt.isCorrect}
|
||||||
title={opt.isCorrect ? 'Richtig' : 'Als richtig markieren'}
|
title={opt.isCorrect
|
||||||
aria-label="Richtig"
|
? $_('quiz.edit_view.correct_marked')
|
||||||
|
: $_('quiz.edit_view.correct_mark_action')}
|
||||||
|
aria-label={$_('quiz.edit_view.correct_marked')}
|
||||||
onclick={() => toggleNewCorrect(opt.id)}
|
onclick={() => toggleNewCorrect(opt.id)}
|
||||||
>
|
>
|
||||||
{#if opt.isCorrect}<Check size={14} weight="bold" />{/if}
|
{#if opt.isCorrect}<Check size={14} weight="bold" />{/if}
|
||||||
|
|
@ -331,13 +348,15 @@
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newOptions[i].text}
|
bind:value={newOptions[i].text}
|
||||||
placeholder={`Antwort ${i + 1}`}
|
placeholder={$_('quiz.edit_view.placeholder_option', {
|
||||||
|
values: { n: i + 1 },
|
||||||
|
})}
|
||||||
disabled={newType === 'truefalse'}
|
disabled={newType === 'truefalse'}
|
||||||
/>
|
/>
|
||||||
{#if newType !== 'truefalse' && newOptions.length > 2}
|
{#if newType !== 'truefalse' && newOptions.length > 2}
|
||||||
<button
|
<button
|
||||||
class="icon-btn"
|
class="icon-btn"
|
||||||
aria-label="Entfernen"
|
aria-label={$_('quiz.edit_view.action_remove')}
|
||||||
onclick={() => removeNewOption(opt.id)}
|
onclick={() => removeNewOption(opt.id)}
|
||||||
>
|
>
|
||||||
<Trash size={14} />
|
<Trash size={14} />
|
||||||
|
|
@ -347,26 +366,27 @@
|
||||||
{/each}
|
{/each}
|
||||||
{#if newType !== 'truefalse' && newOptions.length < 6}
|
{#if newType !== 'truefalse' && newOptions.length < 6}
|
||||||
<button class="add-option" onclick={addNewOption}>
|
<button class="add-option" onclick={addNewOption}>
|
||||||
<Plus size={12} /> Antwort hinzufügen
|
<Plus size={12} />
|
||||||
|
{$_('quiz.edit_view.action_add_option')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Erklärung (optional)</span>
|
<span>{$_('quiz.edit_view.label_explanation')}</span>
|
||||||
<textarea
|
<textarea
|
||||||
bind:value={newExplanation}
|
bind:value={newExplanation}
|
||||||
rows="2"
|
rows="2"
|
||||||
placeholder="Wird nach dem Antworten angezeigt"
|
placeholder={$_('quiz.edit_view.placeholder_explanation')}
|
||||||
></textarea>
|
></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button class="submit-btn" onclick={submitQuestion}>
|
<button class="submit-btn" onclick={submitQuestion}>
|
||||||
{#if editingId}
|
{#if editingId}
|
||||||
<Check size={14} /> Änderungen speichern
|
<Check size={14} /> {$_('quiz.edit_view.action_save_changes')}
|
||||||
{:else}
|
{:else}
|
||||||
<Plus size={14} /> Frage hinzufügen
|
<Plus size={14} /> {$_('quiz.edit_view.action_add_question')}
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,6 @@
|
||||||
"apps/mana/apps/web/src/lib/modules/profile/MeImagesView.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/profile/MeImagesView.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/questions/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/questions/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte": 6,
|
"apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte": 6,
|
||||||
"apps/mana/apps/web/src/lib/modules/quiz/EditView.svelte": 13,
|
|
||||||
"apps/mana/apps/web/src/lib/modules/quiz/ListView.svelte": 5,
|
"apps/mana/apps/web/src/lib/modules/quiz/ListView.svelte": 5,
|
||||||
"apps/mana/apps/web/src/lib/modules/quiz/PlayView.svelte": 6,
|
"apps/mana/apps/web/src/lib/modules/quiz/PlayView.svelte": 6,
|
||||||
"apps/mana/apps/web/src/lib/modules/quotes/views/DetailView.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/quotes/views/DetailView.svelte": 2,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte": 3,
|
"apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte": 3,
|
||||||
"apps/mana/apps/web/src/lib/modules/period/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/period/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/plants/ListView.svelte": 5,
|
"apps/mana/apps/web/src/lib/modules/plants/ListView.svelte": 5,
|
||||||
|
"apps/mana/apps/web/src/lib/modules/quiz/EditView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/quotes/components/QuoteCard.svelte": 4,
|
"apps/mana/apps/web/src/lib/modules/quotes/components/QuoteCard.svelte": 4,
|
||||||
"apps/mana/apps/web/src/lib/modules/recipes/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/recipes/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/times/components/EntryForm.svelte": 6,
|
"apps/mana/apps/web/src/lib/modules/times/components/EntryForm.svelte": 6,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue