feat(i18n): add new localization keys for lesson details and deepening sections
All checks were successful
Deploy to production / deploy (push) Successful in 2m46s

- Introduced new localization strings in German, English, and Spanish for lesson detail toggles and deepening sections, enhancing user experience across multiple languages.
- Updated VocabLessonView to incorporate these new localization keys, improving the clarity and accessibility of lesson information for users.
This commit is contained in:
Torsten Schulz (local)
2026-04-01 10:05:37 +02:00
parent 6d13965c76
commit 8bbfd46ada
4 changed files with 222 additions and 164 deletions

View File

@@ -7,8 +7,6 @@
<h2>{{ lesson.title }}</h2>
</div>
<p v-if="lesson.description" class="lesson-description">{{ lesson.description }}</p>
<!-- Tabs für Lernen und Übungen -->
<div class="lesson-tabs">
<button
@@ -43,10 +41,6 @@
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonTypeLabel') }}</span>
<strong>{{ getLessonTypeLabel(lesson.lessonType) }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.phaseLabel">
<span class="lesson-meta-label">Phase</span>
<strong>{{ getPhaseLabel(lessonPedagogy.phaseLabel) }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.didacticMode">
<span class="lesson-meta-label">Fokus</span>
<strong>{{ getDidacticModeLabel(lessonPedagogy.didacticMode) }}</strong>
@@ -59,15 +53,29 @@
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.exerciseLoad') }}</span>
<strong>{{ effectiveExercises?.length || 0 }} {{ $t('socialnetwork.vocab.courses.exercisesShort') }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.newUnitTarget">
<span class="lesson-meta-label">Neue Einheiten</span>
<strong>{{ lessonPedagogy.newUnitTarget }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.reviewWeight != null">
<span class="lesson-meta-label">Wiederholung</span>
<strong>{{ lessonPedagogy.reviewWeight }}%</strong>
</div>
</div>
<details
v-if="lessonPedagogy.phaseLabel || lessonPedagogy.newUnitTarget || lessonPedagogy.reviewWeight != null"
class="lesson-overview-more"
>
<summary class="lesson-overview-more__summary">
{{ $t('socialnetwork.vocab.courses.lessonDetailsToggle') }}
</summary>
<div class="lesson-overview-more__grid">
<div class="lesson-meta-item" v-if="lessonPedagogy.phaseLabel">
<span class="lesson-meta-label">Phase</span>
<strong>{{ getPhaseLabel(lessonPedagogy.phaseLabel) }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.newUnitTarget">
<span class="lesson-meta-label">Neue Einheiten</span>
<strong>{{ lessonPedagogy.newUnitTarget }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.reviewWeight != null">
<span class="lesson-meta-label">Wiederholung</span>
<strong>{{ lessonPedagogy.reviewWeight }}%</strong>
</div>
</div>
</details>
</div>
<div v-if="lessonPedagogy.isIntensiveReview" class="lesson-intensity-banner">
@@ -75,148 +83,6 @@
<p>Diese Lektion priorisiert Wiederholung und Vertiefung. Neuer Stoff wird bewusst reduziert, damit vorhandene Muster stabil werden.</p>
</div>
<div class="learn-grid">
<div
v-if="(lesson && lesson.description) || lessonDidactics.learningGoals.length > 0"
class="didactic-card lesson-intro-combined"
>
<div v-if="lesson && lesson.description" class="lesson-intro-block">
<h4>{{ $t('socialnetwork.vocab.courses.lessonDescription') }}</h4>
<p>{{ lesson.description }}</p>
</div>
<div v-if="lessonDidactics.learningGoals.length > 0" class="lesson-intro-block">
<h4>{{ $t('socialnetwork.vocab.courses.learningGoals') }}</h4>
<ul class="didactic-list">
<li v-for="(goal, index) in lessonDidactics.learningGoals" :key="'goal-' + index">{{ goal }}</li>
</ul>
</div>
</div>
<div v-if="normalizedCorePatterns.length > 0" class="didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.corePatterns') }}</h4>
<p v-if="corePatternsHaveGloss" class="core-patterns-hint">
{{ $t('socialnetwork.vocab.courses.corePatternsHint') }}
</p>
<div class="pattern-list">
<div v-for="(pattern, index) in normalizedCorePatterns" :key="'pattern-' + index" class="pattern-item">
<div class="pattern-target">{{ pattern.target }}</div>
<div v-if="pattern.gloss" class="pattern-gloss">{{ pattern.gloss }}</div>
</div>
</div>
</div>
<div v-if="lessonDidactics.grammarFocus.length > 0" class="grammar-explanations didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.grammarExplanations') }}</h4>
<div v-for="(explanation, index) in lessonDidactics.grammarFocus" :key="'grammar-' + index" class="grammar-explanation-item">
<strong>{{ explanation.title || $t('socialnetwork.vocab.courses.grammarImpulse') }}</strong>
<p>{{ explanation.text }}</p>
<p v-if="explanation.example" class="grammar-example">{{ explanation.example }}</p>
</div>
</div>
<div v-if="lessonDidactics.speakingPrompts.length > 0" class="didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.speakingTasks') }}</h4>
<div v-for="(prompt, index) in lessonDidactics.speakingPrompts" :key="'speaking-' + index" class="speaking-prompt-item">
<strong>{{ prompt.title || $t('socialnetwork.vocab.courses.speakingPrompt') }}</strong>
<p>{{ prompt.prompt }}</p>
<p v-if="prompt.cue" class="speaking-cue">{{ prompt.cue }}</p>
</div>
</div>
<div v-if="lessonDidactics.practicalTasks.length > 0" class="didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.practicalTasks') }}</h4>
<div v-for="(task, index) in lessonDidactics.practicalTasks" :key="'task-' + index" class="practical-task-item">
<strong>{{ task.title }}</strong>
<p>{{ task.text }}</p>
</div>
</div>
<div ref="assistantCard" class="didactic-card language-assistant-card" :class="{ 'language-assistant-card--focused': isAssistantFocused }">
<div class="language-assistant-card__header">
<div>
<h4>{{ $t('socialnetwork.vocab.courses.languageAssistantTitle') }}</h4>
<p class="language-assistant-card__intro">{{ $t('socialnetwork.vocab.courses.languageAssistantIntro') }}</p>
</div>
<button @click="openLanguageAssistantSettings" class="button-secondary language-assistant-card__settings">
{{ $t('socialnetwork.vocab.courses.languageAssistantSettings') }}
</button>
</div>
<div v-if="assistantLoading" class="language-assistant-card__state">
{{ $t('general.loading') }}
</div>
<div v-else-if="!assistantAvailable" class="language-assistant-card__state">
<p>{{ $t('socialnetwork.vocab.courses.languageAssistantSetupHint') }}</p>
</div>
<div v-else class="language-assistant-panel">
<div class="language-assistant-panel__modes">
<button
v-for="mode in assistantModes"
:key="mode.value"
type="button"
class="assistant-mode-button"
:class="{ active: assistantMode === mode.value }"
@click="assistantMode = mode.value"
>
{{ mode.label }}
</button>
</div>
<div class="language-assistant-panel__presets">
<button type="button" class="assistant-preset-button" @click="sendPresetPrompt('explain')">
{{ $t('socialnetwork.vocab.courses.languageAssistantPromptExplain') }}
</button>
<button type="button" class="assistant-preset-button" @click="sendPresetPrompt('practice')">
{{ $t('socialnetwork.vocab.courses.languageAssistantPromptPractice') }}
</button>
<button type="button" class="assistant-preset-button" @click="sendPresetPrompt('correct')">
{{ $t('socialnetwork.vocab.courses.languageAssistantPromptCorrect') }}
</button>
</div>
<div v-if="assistantMessages.length > 0" class="language-assistant-chat">
<article
v-for="(message, index) in assistantMessages"
:key="`${message.role}-${index}`"
class="assistant-message"
:class="`assistant-message--${message.role}`"
>
<strong>{{ message.role === 'assistant' ? $t('socialnetwork.vocab.courses.languageAssistantSpeakerAi') : $t('socialnetwork.vocab.courses.languageAssistantSpeakerYou') }}</strong>
<p>{{ message.content }}</p>
</article>
</div>
<label class="language-assistant-panel__input">
<span>{{ $t('socialnetwork.vocab.courses.languageAssistantInputLabel') }}</span>
<textarea
v-model="assistantInput"
:placeholder="$t('socialnetwork.vocab.courses.languageAssistantInputPlaceholder')"
rows="4"
/>
</label>
<p v-if="assistantError" class="form-error">{{ assistantError }}</p>
<div class="language-assistant-panel__actions">
<button
type="button"
@click="sendAssistantMessage()"
:disabled="assistantSubmitting || !assistantInput.trim()"
>
{{ assistantSubmitting ? $t('socialnetwork.vocab.courses.languageAssistantSending') : $t('socialnetwork.vocab.courses.languageAssistantSend') }}
</button>
</div>
</div>
</div>
<div v-if="lesson && lesson.culturalNotes" class="cultural-notes didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.culturalNotes') }}</h4>
<p>{{ lesson.culturalNotes }}</p>
</div>
</div>
<!-- Zwei Durchgänge: dieselben Kernmuster schrittweise vor dem Trainer -->
<div
v-if="prepItems.length > 0 && !vocabTrainerActive"
@@ -380,6 +246,158 @@
{{ $t('socialnetwork.vocab.courses.startExercises') }}
</button>
</div>
<details class="lesson-deepen-section surface-card">
<summary class="lesson-deepen-section__summary">
{{ $t('socialnetwork.vocab.courses.deepenSectionTitle') }}
</summary>
<div class="learn-grid">
<div
v-if="(lesson && lesson.description) || lessonDidactics.learningGoals.length > 0"
class="didactic-card lesson-intro-combined"
>
<div v-if="lesson && lesson.description" class="lesson-intro-block">
<h4>{{ $t('socialnetwork.vocab.courses.lessonDescription') }}</h4>
<p>{{ lesson.description }}</p>
</div>
<div v-if="lessonDidactics.learningGoals.length > 0" class="lesson-intro-block">
<h4>{{ $t('socialnetwork.vocab.courses.learningGoals') }}</h4>
<ul class="didactic-list">
<li v-for="(goal, index) in lessonDidactics.learningGoals" :key="'goal-' + index">{{ goal }}</li>
</ul>
</div>
</div>
<div v-if="normalizedCorePatterns.length > 0" class="didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.corePatterns') }}</h4>
<p v-if="corePatternsHaveGloss" class="core-patterns-hint">
{{ $t('socialnetwork.vocab.courses.corePatternsHint') }}
</p>
<div class="pattern-list">
<div v-for="(pattern, index) in normalizedCorePatterns" :key="'pattern-' + index" class="pattern-item">
<div class="pattern-target">{{ pattern.target }}</div>
<div v-if="pattern.gloss" class="pattern-gloss">{{ pattern.gloss }}</div>
</div>
</div>
</div>
<div v-if="lessonDidactics.grammarFocus.length > 0" class="grammar-explanations didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.grammarExplanations') }}</h4>
<div v-for="(explanation, index) in lessonDidactics.grammarFocus" :key="'grammar-' + index" class="grammar-explanation-item">
<strong>{{ explanation.title || $t('socialnetwork.vocab.courses.grammarImpulse') }}</strong>
<p>{{ explanation.text }}</p>
<p v-if="explanation.example" class="grammar-example">{{ explanation.example }}</p>
</div>
</div>
<div v-if="lessonDidactics.speakingPrompts.length > 0" class="didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.speakingTasks') }}</h4>
<div v-for="(prompt, index) in lessonDidactics.speakingPrompts" :key="'speaking-' + index" class="speaking-prompt-item">
<strong>{{ prompt.title || $t('socialnetwork.vocab.courses.speakingPrompt') }}</strong>
<p>{{ prompt.prompt }}</p>
<p v-if="prompt.cue" class="speaking-cue">{{ prompt.cue }}</p>
</div>
</div>
<div v-if="lessonDidactics.practicalTasks.length > 0" class="didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.practicalTasks') }}</h4>
<div v-for="(task, index) in lessonDidactics.practicalTasks" :key="'task-' + index" class="practical-task-item">
<strong>{{ task.title }}</strong>
<p>{{ task.text }}</p>
</div>
</div>
<div v-if="lesson && lesson.culturalNotes" class="cultural-notes didactic-card">
<h4>{{ $t('socialnetwork.vocab.courses.culturalNotes') }}</h4>
<p>{{ lesson.culturalNotes }}</p>
</div>
</div>
</details>
<details class="lesson-assistant-section surface-card">
<summary class="lesson-deepen-section__summary">
{{ $t('socialnetwork.vocab.courses.assistantSectionTitle') }}
</summary>
<div ref="assistantCard" class="didactic-card language-assistant-card" :class="{ 'language-assistant-card--focused': isAssistantFocused }">
<div class="language-assistant-card__header">
<div>
<h4>{{ $t('socialnetwork.vocab.courses.languageAssistantTitle') }}</h4>
<p class="language-assistant-card__intro">{{ $t('socialnetwork.vocab.courses.languageAssistantIntro') }}</p>
</div>
<button @click="openLanguageAssistantSettings" class="button-secondary language-assistant-card__settings">
{{ $t('socialnetwork.vocab.courses.languageAssistantSettings') }}
</button>
</div>
<div v-if="assistantLoading" class="language-assistant-card__state">
{{ $t('general.loading') }}
</div>
<div v-else-if="!assistantAvailable" class="language-assistant-card__state">
<p>{{ $t('socialnetwork.vocab.courses.languageAssistantSetupHint') }}</p>
</div>
<div v-else class="language-assistant-panel">
<div class="language-assistant-panel__modes">
<button
v-for="mode in assistantModes"
:key="mode.value"
type="button"
class="assistant-mode-button"
:class="{ active: assistantMode === mode.value }"
@click="assistantMode = mode.value"
>
{{ mode.label }}
</button>
</div>
<div class="language-assistant-panel__presets">
<button type="button" class="assistant-preset-button" @click="sendPresetPrompt('explain')">
{{ $t('socialnetwork.vocab.courses.languageAssistantPromptExplain') }}
</button>
<button type="button" class="assistant-preset-button" @click="sendPresetPrompt('practice')">
{{ $t('socialnetwork.vocab.courses.languageAssistantPromptPractice') }}
</button>
<button type="button" class="assistant-preset-button" @click="sendPresetPrompt('correct')">
{{ $t('socialnetwork.vocab.courses.languageAssistantPromptCorrect') }}
</button>
</div>
<div v-if="assistantMessages.length > 0" class="language-assistant-chat">
<article
v-for="(message, index) in assistantMessages"
:key="`${message.role}-${index}`"
class="assistant-message"
:class="`assistant-message--${message.role}`"
>
<strong>{{ message.role === 'assistant' ? $t('socialnetwork.vocab.courses.languageAssistantSpeakerAi') : $t('socialnetwork.vocab.courses.languageAssistantSpeakerYou') }}</strong>
<p>{{ message.content }}</p>
</article>
</div>
<label class="language-assistant-panel__input">
<span>{{ $t('socialnetwork.vocab.courses.languageAssistantInputLabel') }}</span>
<textarea
v-model="assistantInput"
:placeholder="$t('socialnetwork.vocab.courses.languageAssistantInputPlaceholder')"
rows="4"
/>
</label>
<p v-if="assistantError" class="form-error">{{ assistantError }}</p>
<div class="language-assistant-panel__actions">
<button
type="button"
@click="sendAssistantMessage()"
:disabled="assistantSubmitting || !assistantInput.trim()"
>
{{ assistantSubmitting ? $t('socialnetwork.vocab.courses.languageAssistantSending') : $t('socialnetwork.vocab.courses.languageAssistantSend') }}
</button>
</div>
</div>
</div>
</details>
</div>
<!-- Übungen-Tab (Kapitel-Prüfung) -->
@@ -2388,7 +2406,7 @@ export default {
.lesson-overview-card {
display: flex;
justify-content: space-between;
flex-direction: column;
gap: 20px;
padding: 20px;
margin-bottom: 20px;
@@ -2404,9 +2422,8 @@ export default {
.lesson-meta-grid {
display: grid;
grid-template-columns: repeat(3, minmax(130px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
min-width: 360px;
}
.lesson-meta-item {
@@ -2440,6 +2457,25 @@ export default {
margin-top: 6px;
}
.lesson-overview-more {
border-top: 1px solid rgba(160, 120, 40, 0.18);
padding-top: 14px;
}
.lesson-overview-more__summary,
.lesson-deepen-section__summary {
cursor: pointer;
font-weight: 600;
color: #5f4313;
}
.lesson-overview-more__grid {
margin-top: 12px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
}
.learn-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
@@ -2553,6 +2589,17 @@ export default {
color: #8a5a00;
}
.lesson-deepen-section,
.lesson-assistant-section {
margin-top: 20px;
padding: 16px 18px;
}
.lesson-deepen-section .learn-grid,
.lesson-assistant-section .language-assistant-card {
margin-top: 16px;
}
.grammar-example,
.speaking-cue,
.pattern-drill-hint {
@@ -2610,11 +2657,6 @@ export default {
box-shadow: none;
}
.lesson-description {
color: #666;
margin-bottom: 20px;
}
.grammar-exercises {
margin-top: 30px;
}
@@ -2835,6 +2877,13 @@ export default {
margin-bottom: 14px;
}
.vocab-list--overview {
padding: 14px 16px;
background: rgba(255, 255, 255, 0.86);
border: 1px solid #eadfcf;
border-radius: 12px;
}
.vocab-list h4 {
margin-bottom: 15px;
color: #333;