diff --git a/backend/scripts/apply-bisaya-course-refresh.js b/backend/scripts/apply-bisaya-course-refresh.js index 0f9767d..b09adaa 100644 --- a/backend/scripts/apply-bisaya-course-refresh.js +++ b/backend/scripts/apply-bisaya-course-refresh.js @@ -22,7 +22,26 @@ const LESSON_DIDACTICS = { 'Höfliche Reaktionen wie Danke und Bitte passend einsetzen.', 'Ein kurzes Begrüßungs-Mini-Gespräch laut üben.' ], - corePatterns: ['Kumusta ka?', 'Maayong buntag.', 'Maayong adlaw.', 'Maayong gabii.', 'Maayong gabii, matulog na ta.', 'Katulog og maayo.', 'Kapoy na ka?', 'Matulog na ta.', 'Inom sa og tubig.', 'Patya ang suga.', 'Tabuni ang imong kaugalingon.', 'Ugma nasad.', 'Damgo og nindot.', 'Amping.', 'Babay.', 'Maayo ko.', 'Salamat.', 'Palihug.'], + corePatterns: [ + { target: 'Kumusta ka?', gloss: 'Wie geht es dir?' }, + { target: 'Maayong buntag.', gloss: 'Guten Morgen.' }, + { target: 'Maayong adlaw.', gloss: 'Guten Tag.' }, + { target: 'Maayong gabii.', gloss: 'Guten Abend.' }, + { target: 'Maayong gabii, matulog na ta.', gloss: 'Guten Abend, wir legen uns schlafen.' }, + { target: 'Katulog og maayo.', gloss: 'Schlaf gut.' }, + { target: 'Kapoy na ka?', gloss: 'Bist du müde?' }, + { target: 'Matulog na ta.', gloss: 'Lass uns schlafen gehen.' }, + { target: 'Inom sa og tubig.', gloss: 'Trink Wasser.' }, + { target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' }, + { target: 'Tabuni ang imong kaugalingon.', gloss: 'Deck dich zu.' }, + { target: 'Ugma nasad.', gloss: 'Bis morgen wieder.' }, + { target: 'Damgo og nindot.', gloss: 'Träum schön.' }, + { target: 'Amping.', gloss: 'Pass auf dich auf.' }, + { target: 'Babay.', gloss: 'Tschüss.' }, + { target: 'Maayo ko.', gloss: 'Mir geht es gut.' }, + { target: 'Salamat.', gloss: 'Danke.' }, + { target: 'Palihug.', gloss: 'Bitte.' } + ], grammarFocus: [ { title: 'Kurzantworten mit ko', text: 'Mit "ko" sprichst du über dich selbst: "Maayo ko."', example: 'Maayo ko. = Mir geht es gut.' }, { title: 'Maayong + Tageszeit', text: 'Mit "Maayong" kannst du Grüße für verschiedene Tageszeiten bilden.', example: 'Maayong buntag. / Maayong gabii.' }, diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index f8ba483..f79d7ba 100644 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -30,19 +30,46 @@ const GENERATED_BISAYA_DIDACTICS = { ...BISAYA_PHASE5_DIDACTICS }; -const GENERIC_DISTRACTOR_PATTERNS = Array.from(new Set( - Object.values(GENERATED_BISAYA_DIDACTICS) - .flatMap((entry) => Array.isArray(entry?.corePatterns) ? entry.corePatterns : []) - .map((pattern) => String(pattern || '').trim()) - .filter(Boolean) -)).slice(0, 200); - function normalizeText(value) { return String(value || '') .trim() .replace(/\s+/g, ' '); } +function normalizeCorePatternEntry(entry) { + if (entry === null || entry === undefined || entry === '') { + return null; + } + if (typeof entry === 'object' && !Array.isArray(entry)) { + const target = normalizeText(entry.target ?? entry.ceb ?? entry.phrase ?? ''); + const gloss = normalizeText(entry.gloss ?? entry.de ?? entry.translation ?? ''); + if (!target) return null; + return { target, gloss }; + } + const s = normalizeText(entry); + if (!s) return null; + const pipe = s.indexOf('|'); + if (pipe !== -1) { + const target = normalizeText(s.slice(0, pipe)); + const gloss = normalizeText(s.slice(pipe + 1)); + if (!target) return null; + return { target, gloss }; + } + return { target: s, gloss: '' }; +} + +function corePatternTarget(entry) { + const n = normalizeCorePatternEntry(entry); + return n ? n.target : ''; +} + +const GENERIC_DISTRACTOR_PATTERNS = Array.from(new Set( + Object.values(GENERATED_BISAYA_DIDACTICS) + .flatMap((entry) => Array.isArray(entry?.corePatterns) ? entry.corePatterns : []) + .map((pattern) => corePatternTarget(pattern)) + .filter(Boolean) +)).slice(0, 200); + function simpleHash(value) { return Array.from(String(value || '')).reduce((sum, char) => sum + char.charCodeAt(0), 0); } @@ -63,7 +90,9 @@ function getLessonDidactics(lesson) { return { learningGoals, - corePatterns: corePatterns.map((entry) => normalizeText(entry)).filter(Boolean), + corePatterns: corePatterns + .map((entry) => normalizeCorePatternEntry(entry)) + .filter(Boolean), grammarFocus, speakingPrompts, practicalTasks @@ -323,10 +352,10 @@ function generateExercisesFromDidactics(lesson) { return []; } - const patternA = corePatterns[0]; - const patternB = corePatterns[1] || corePatterns[0]; + const patternA = corePatternTarget(corePatterns[0]); + const patternB = corePatternTarget(corePatterns[1] || corePatterns[0]); const lessonPool = Array.from(new Set([ - ...corePatterns, + ...corePatterns.map((p) => corePatternTarget(p)), ...GENERIC_DISTRACTOR_PATTERNS ])); let generated = []; diff --git a/backend/scripts/create-bisaya-course.js b/backend/scripts/create-bisaya-course.js index ea78407..843e65d 100755 --- a/backend/scripts/create-bisaya-course.js +++ b/backend/scripts/create-bisaya-course.js @@ -26,24 +26,24 @@ const LESSON_DIDACTICS = { 'Eine kurze Abend- und Schlafensroutine im Familienalltag sprechen.' ], corePatterns: [ - 'Kumusta ka?', - 'Maayong buntag.', - 'Maayong adlaw.', - 'Maayong gabii.', - 'Maayong gabii, matulog na ta.', - 'Katulog og maayo.', - 'Kapoy na ka?', - 'Matulog na ta.', - 'Inom sa og tubig.', - 'Patya ang suga.', - 'Tabuni ang imong kaugalingon.', - 'Ugma nasad.', - 'Damgo og nindot.', - 'Amping.', - 'Babay.', - 'Maayo ko.', - 'Salamat.', - 'Palihug.' + { target: 'Kumusta ka?', gloss: 'Wie geht es dir?' }, + { target: 'Maayong buntag.', gloss: 'Guten Morgen.' }, + { target: 'Maayong adlaw.', gloss: 'Guten Tag.' }, + { target: 'Maayong gabii.', gloss: 'Guten Abend.' }, + { target: 'Maayong gabii, matulog na ta.', gloss: 'Guten Abend, wir legen uns schlafen.' }, + { target: 'Katulog og maayo.', gloss: 'Schlaf gut.' }, + { target: 'Kapoy na ka?', gloss: 'Bist du müde?' }, + { target: 'Matulog na ta.', gloss: 'Lass uns schlafen gehen.' }, + { target: 'Inom sa og tubig.', gloss: 'Trink Wasser.' }, + { target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' }, + { target: 'Tabuni ang imong kaugalingon.', gloss: 'Deck dich zu.' }, + { target: 'Ugma nasad.', gloss: 'Bis morgen wieder.' }, + { target: 'Damgo og nindot.', gloss: 'Träum schön.' }, + { target: 'Amping.', gloss: 'Pass auf dich auf.' }, + { target: 'Babay.', gloss: 'Tschüss.' }, + { target: 'Maayo ko.', gloss: 'Mir geht es gut.' }, + { target: 'Salamat.', gloss: 'Danke.' }, + { target: 'Palihug.', gloss: 'Bitte.' } ], grammarFocus: [ { diff --git a/backend/scripts/update-bisaya-didactics.js b/backend/scripts/update-bisaya-didactics.js index b3cdb78..f0b742b 100644 --- a/backend/scripts/update-bisaya-didactics.js +++ b/backend/scripts/update-bisaya-didactics.js @@ -18,7 +18,26 @@ const LESSON_DIDACTICS = { 'Höfliche Reaktionen wie Danke und Bitte passend einsetzen.', 'Ein kurzes Begrüßungs-Mini-Gespräch laut üben.' ], - corePatterns: ['Kumusta ka?', 'Maayong buntag.', 'Maayong adlaw.', 'Maayong gabii.', 'Maayong gabii, matulog na ta.', 'Katulog og maayo.', 'Kapoy na ka?', 'Matulog na ta.', 'Inom sa og tubig.', 'Patya ang suga.', 'Tabuni ang imong kaugalingon.', 'Ugma nasad.', 'Damgo og nindot.', 'Amping.', 'Babay.', 'Maayo ko.', 'Salamat.', 'Palihug.'], + corePatterns: [ + { target: 'Kumusta ka?', gloss: 'Wie geht es dir?' }, + { target: 'Maayong buntag.', gloss: 'Guten Morgen.' }, + { target: 'Maayong adlaw.', gloss: 'Guten Tag.' }, + { target: 'Maayong gabii.', gloss: 'Guten Abend.' }, + { target: 'Maayong gabii, matulog na ta.', gloss: 'Guten Abend, wir legen uns schlafen.' }, + { target: 'Katulog og maayo.', gloss: 'Schlaf gut.' }, + { target: 'Kapoy na ka?', gloss: 'Bist du müde?' }, + { target: 'Matulog na ta.', gloss: 'Lass uns schlafen gehen.' }, + { target: 'Inom sa og tubig.', gloss: 'Trink Wasser.' }, + { target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' }, + { target: 'Tabuni ang imong kaugalingon.', gloss: 'Deck dich zu.' }, + { target: 'Ugma nasad.', gloss: 'Bis morgen wieder.' }, + { target: 'Damgo og nindot.', gloss: 'Träum schön.' }, + { target: 'Amping.', gloss: 'Pass auf dich auf.' }, + { target: 'Babay.', gloss: 'Tschüss.' }, + { target: 'Maayo ko.', gloss: 'Mir geht es gut.' }, + { target: 'Salamat.', gloss: 'Danke.' }, + { target: 'Palihug.', gloss: 'Bitte.' } + ], grammarFocus: [ { title: 'Kurzantworten mit ko', text: 'Mit "ko" sprichst du über dich selbst: "Maayo ko."', example: 'Maayo ko. = Mir geht es gut.' }, { title: 'Maayong + Tageszeit', text: 'Mit "Maayong" kannst du Grüße für verschiedene Tageszeiten bilden.', example: 'Maayong buntag. / Maayong gabii.' }, diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index c4573e5..aeaa90d 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -106,7 +106,13 @@ export default class VocabService { `Lektion: ${lesson?.title || 'Unbekannte Lektion'}`, lesson?.description ? `Beschreibung: ${lesson.description}` : '', learningGoals.length ? `Lernziele: ${learningGoals.join(' | ')}` : '', - corePatterns.length ? `Kernmuster: ${corePatterns.join(' | ')}` : '', + corePatterns.length + ? `Kernmuster: ${corePatterns.map((p) => { + const n = this._normalizeCorePatternEntry(p); + if (!n) return ''; + return n.gloss ? `${n.target} (${n.gloss})` : n.target; + }).filter(Boolean).join(' | ')}` + : '', speakingPrompts.length ? `Sprechaufträge: ${speakingPrompts.map((item) => item.prompt || item.title || '').filter(Boolean).join(' | ')}` : '', @@ -239,14 +245,16 @@ export default class VocabService { speakingPrompts.forEach((prompt, index) => { const learning = String(prompt?.prompt || prompt?.title || '').trim(); - const reference = String(prompt?.cue || corePatterns[index] || corePatterns[0] || '').trim(); + const refEntry = corePatterns[index] ?? corePatterns[0]; + const reference = String(prompt?.cue || this._corePatternTarget(refEntry) || '').trim(); if (!learning || !reference || learning === reference) return; vocabMap.set(`${learning}-${reference}`, { learning, reference }); }); practicalTasks.forEach((task, index) => { const learning = String(task?.text || task?.title || '').trim(); - const reference = String(corePatterns[index] || corePatterns[0] || '').trim(); + const refEntry = corePatterns[index] ?? corePatterns[0]; + const reference = String(this._corePatternTarget(refEntry) || '').trim(); if (!learning || !reference || learning === reference) return; vocabMap.set(`${learning}-${reference}`, { learning, reference }); }); @@ -270,6 +278,49 @@ export default class VocabService { return []; } + /** + * Kernmuster: Zielsprachen-Phrase + optionale Glossierung (z. B. Deutsch). + * Unterstützt Legacy-Strings, "Phrase|Gloss" und Objekte { target, gloss } / { ceb, de }. + */ + _normalizeCorePatternEntry(entry) { + if (entry === null || entry === undefined || entry === '') { + return null; + } + if (typeof entry === 'object' && !Array.isArray(entry)) { + const target = String(entry.target ?? entry.ceb ?? entry.phrase ?? '').trim(); + const gloss = String(entry.gloss ?? entry.de ?? entry.translation ?? '').trim(); + if (!target) return null; + return { target, gloss }; + } + const s = String(entry).trim(); + if (!s) return null; + const pipe = s.indexOf('|'); + if (pipe !== -1) { + const target = s.slice(0, pipe).trim(); + const gloss = s.slice(pipe + 1).trim(); + if (!target) return null; + return { target, gloss }; + } + return { target: s, gloss: '' }; + } + + _normalizeCorePatternList(value) { + if (!value) return []; + const raw = Array.isArray(value) + ? value + : (typeof value === 'string' + ? value.split(/\r?\n|;/).map((entry) => entry.trim()).filter(Boolean) + : []); + return raw + .map((entry) => this._normalizeCorePatternEntry(entry)) + .filter(Boolean); + } + + _corePatternTarget(entry) { + const n = this._normalizeCorePatternEntry(entry); + return n ? n.target : ''; + } + _normalizeStructuredList(value, keys = ['title', 'text']) { if (!value) return []; if (Array.isArray(value)) { @@ -482,7 +533,7 @@ export default class VocabService { const uniquePatterns = [...new Set(patterns.map((item) => String(item || '').trim()).filter(Boolean))]; const learningGoals = this._normalizeStringList(plainLesson.learningGoals); - const corePatterns = this._normalizeStringList(plainLesson.corePatterns); + const corePatterns = this._normalizeCorePatternList(plainLesson.corePatterns); const grammarFocus = this._normalizeStructuredList(plainLesson.grammarFocus, ['title', 'text', 'example']); const explicitSpeakingPrompts = this._normalizeStructuredList(plainLesson.speakingPrompts, ['title', 'prompt', 'cue']); const practicalTasks = this._normalizeStructuredList(plainLesson.practicalTasks, ['title', 'text']); @@ -495,7 +546,9 @@ export default class VocabService { 'Ein bis zwei Satzmuster aktiv anwenden.', 'Kurze Sätze oder Mini-Dialoge zum Thema selbst bilden.' ], - corePatterns: corePatterns.length > 0 ? corePatterns : uniquePatterns.slice(0, 5), + corePatterns: corePatterns.length > 0 + ? corePatterns + : uniquePatterns.slice(0, 5).map((s) => ({ target: String(s || '').trim(), gloss: '' })).filter((p) => p.target), grammarFocus: grammarFocus.length > 0 ? grammarFocus : uniqueGrammarExplanations.slice(0, 4), speakingPrompts: explicitSpeakingPrompts.length > 0 ? explicitSpeakingPrompts : speakingPrompts.slice(0, 4), practicalTasks: practicalTasks.length > 0 @@ -1775,7 +1828,7 @@ export default class VocabService { audioUrl: audioUrl || null, culturalNotes: culturalNotes || null, learningGoals: this._normalizeStringList(learningGoals), - corePatterns: this._normalizeStringList(corePatterns), + corePatterns: this._normalizeCorePatternList(corePatterns), grammarFocus: this._normalizeStructuredList(grammarFocus, ['title', 'text', 'example']), speakingPrompts: this._normalizeStructuredList(speakingPrompts, ['title', 'prompt', 'cue']), practicalTasks: this._normalizeStructuredList(practicalTasks, ['title', 'text']), @@ -1822,7 +1875,7 @@ export default class VocabService { if (audioUrl !== undefined) updates.audioUrl = audioUrl; if (culturalNotes !== undefined) updates.culturalNotes = culturalNotes; if (learningGoals !== undefined) updates.learningGoals = this._normalizeStringList(learningGoals); - if (corePatterns !== undefined) updates.corePatterns = this._normalizeStringList(corePatterns); + if (corePatterns !== undefined) updates.corePatterns = this._normalizeCorePatternList(corePatterns); if (grammarFocus !== undefined) updates.grammarFocus = this._normalizeStructuredList(grammarFocus, ['title', 'text', 'example']); if (speakingPrompts !== undefined) updates.speakingPrompts = this._normalizeStructuredList(speakingPrompts, ['title', 'prompt', 'cue']); if (practicalTasks !== undefined) updates.practicalTasks = this._normalizeStructuredList(practicalTasks, ['title', 'text']); diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json index eeccab9..071e782 100644 --- a/frontend/src/i18n/locales/de/socialnetwork.json +++ b/frontend/src/i18n/locales/de/socialnetwork.json @@ -447,6 +447,14 @@ "grammarImpulse": "Grammatik-Impuls", "learningGoals": "Lernziele", "corePatterns": "Kernmuster", + "corePatternsHint": "Zuerst die Zielsprache lesen, darunter die deutsche Bedeutung — so lernst du jedes Muster bewusst in beiden Richtungen.", + "vocabPrepTitle": "Vorbereitung vor dem Vokabeltrainer", + "vocabPrepStep1": "Lies Kernmuster und Wortliste (Deutsch ↔ Zielsprache) einmal in Ruhe durch.", + "vocabPrepConfirm1": "Erste Durchsicht erledigt", + "vocabPrepStep2": "Gehe die gleichen Begriffe noch einmal durch (aktive Wiederholung, ohne zu üben).", + "vocabPrepConfirm2": "Zweite Durchsicht erledigt", + "vocabPrepReady": "Du kannst jetzt mit dem Vokabeltrainer starten.", + "vocabTrainerLockedHint": "Bitte bestätige zuerst zwei Lern-Durchgänge bei „Vorbereitung vor dem Vokabeltrainer“.", "speakingTasks": "Sprechaufträge", "speakingPrompt": "Sprechauftrag", "practicalTasks": "Praxisaufgaben", diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json index 30a386f..677786a 100644 --- a/frontend/src/i18n/locales/en/socialnetwork.json +++ b/frontend/src/i18n/locales/en/socialnetwork.json @@ -447,6 +447,14 @@ "grammarImpulse": "Grammar Focus", "learningGoals": "Learning Goals", "corePatterns": "Core Patterns", + "corePatternsHint": "Read the target language first, then the meaning below — you learn each pattern both ways.", + "vocabPrepTitle": "Preparation before the vocabulary trainer", + "vocabPrepStep1": "Read through core patterns and the word list (native language ↔ target language) once.", + "vocabPrepConfirm1": "First pass done", + "vocabPrepStep2": "Go through the same items again (active review, not testing yet).", + "vocabPrepConfirm2": "Second pass done", + "vocabPrepReady": "You can start the vocabulary trainer now.", + "vocabTrainerLockedHint": "Please confirm two preparation steps under “Preparation before the vocabulary trainer” first.", "speakingTasks": "Speaking Tasks", "speakingPrompt": "Speaking Prompt", "practicalTasks": "Practical Tasks", diff --git a/frontend/src/i18n/locales/es/socialnetwork.json b/frontend/src/i18n/locales/es/socialnetwork.json index 138f3ac..bfee817 100644 --- a/frontend/src/i18n/locales/es/socialnetwork.json +++ b/frontend/src/i18n/locales/es/socialnetwork.json @@ -445,6 +445,14 @@ "grammarImpulse": "Impulso gramatical", "learningGoals": "Objetivos", "corePatterns": "Patrones básicos", + "corePatternsHint": "Primero la lengua meta; debajo, el significado en tu idioma.", + "vocabPrepTitle": "Preparación antes del entrenador de vocabulario", + "vocabPrepStep1": "Lee una vez los patrones clave y la lista de palabras (idioma nativo ↔ lengua meta).", + "vocabPrepConfirm1": "Primera lectura hecha", + "vocabPrepStep2": "Repasa los mismos elementos otra vez (repaso activo, aún sin practicar).", + "vocabPrepConfirm2": "Segunda lectura hecha", + "vocabPrepReady": "Ya puedes iniciar el entrenador de vocabulario.", + "vocabTrainerLockedHint": "Confirma primero los dos pasos de preparación arriba.", "speakingTasks": "Tareas orales", "speakingPrompt": "Tarea oral", "practicalTasks": "Tareas prácticas", diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 0cb8faf..2d00d4b 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -92,11 +92,15 @@ -
+ {{ $t('socialnetwork.vocab.courses.corePatternsHint') }} +
{{ $t('socialnetwork.vocab.courses.vocabInfoText') }}
+{{ $t('socialnetwork.vocab.courses.vocabPrepStep1') }}
+ + + +{{ $t('socialnetwork.vocab.courses.vocabPrepStep2') }}
+ + +{{ $t('socialnetwork.vocab.courses.vocabPrepReady') }}
+{{ exerciseUnlockHint }}
{{ hasPreviousVocab ? 'Starte mit den neuen Vokabeln dieser Lektion. Mit fortschreitendem Üben mischt der Trainer automatisch passende Wiederholungen ein.' : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}
- + +{{ hasPreviousVocab ? 'Starte mit den neuen Vokabeln dieser Lektion. Mit fortschreitendem Üben mischt der Trainer automatisch passende Wiederholungen ein.' : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}
+ + +{{ $t('socialnetwork.vocab.courses.vocabTrainerLockedHint') }}
{{ $t('socialnetwork.vocab.courses.vocabInfoText') }}
-{{ $t('socialnetwork.vocab.courses.noVocabInfo') }}
@@ -734,6 +762,8 @@ export default { vocabTrainerCurrentAttempts: 0, vocabTrainerReviewAttempts: 0, exercisePreparationCompleted: false, + /** 0 = noch kein Durchgang, 1 = erste Durchsicht, 2 = zweite Durchsicht — dann Vokabeltrainer */ + lessonPrepStage: 0, currentVocabQuestion: null, vocabTrainerAnswer: '', vocabTrainerSelectedChoice: null, @@ -897,6 +927,21 @@ export default { practicalTasks: [] }; }, + normalizedCorePatterns() { + const raw = this.lessonDidactics.corePatterns || []; + return raw + .map((p) => this.normalizeCorePatternEntry(p)) + .filter(Boolean); + }, + corePatternsHaveGloss() { + return this.normalizedCorePatterns.some((p) => p.gloss); + }, + canStartVocabTrainerPrep() { + if (!this.importantVocab || this.lessonPrepStage < 2) { + return false; + } + return true; + }, lessonPedagogy() { return this.lesson?.pedagogy || { didacticMode: null, @@ -943,6 +988,29 @@ export default { } }, methods: { + normalizeCorePatternEntry(p) { + if (p && typeof p === 'object' && p.target) { + return { + target: String(p.target).trim(), + gloss: String(p.gloss || '').trim() + }; + } + const s = String(p || '').trim(); + if (!s) return null; + const i = s.indexOf('|'); + if (i !== -1) { + return { + target: s.slice(0, i).trim(), + gloss: s.slice(i + 1).trim() + }; + } + return { target: s, gloss: '' }; + }, + corePatternToDisplayString(p) { + const n = this.normalizeCorePatternEntry(p); + if (!n) return ''; + return n.gloss ? `${n.target} (${n.gloss})` : n.target; + }, openExercisesTab() { if (!this.canAccessExercises) { this.activeTab = 'learn'; @@ -1133,6 +1201,7 @@ export default { this.assistantInput = ''; this.assistantError = ''; this.exercisePreparationCompleted = false; + this.lessonPrepStage = 0; this.vocabTrainerActive = false; this.vocabTrainerPool = []; this.vocabTrainerMixedPool = []; @@ -1222,10 +1291,11 @@ export default { buildAssistantPrompt(preset) { const lessonTitle = this.lesson?.title || this.$t('socialnetwork.vocab.courses.thisLesson'); const firstPattern = this.lessonDidactics.corePatterns?.[0]; + const firstPatternStr = firstPattern ? this.corePatternToDisplayString(firstPattern) : ''; const firstGrammar = this.lessonDidactics.grammarFocus?.[0]?.text; if (preset === 'explain') { - return `${this.$t('socialnetwork.vocab.courses.languageAssistantPresetExplainStart')} "${lessonTitle}". ${firstPattern ? `${this.$t('socialnetwork.vocab.courses.languageAssistantPatternHint')} ${firstPattern}.` : ''} ${firstGrammar || ''}`.trim(); + return `${this.$t('socialnetwork.vocab.courses.languageAssistantPresetExplainStart')} "${lessonTitle}". ${firstPatternStr ? `${this.$t('socialnetwork.vocab.courses.languageAssistantPatternHint')} ${firstPatternStr}.` : ''} ${firstGrammar || ''}`.trim(); } if (preset === 'correct') { return this.$t('socialnetwork.vocab.courses.languageAssistantPresetCorrectStart', { lesson: lessonTitle }); @@ -1669,6 +1739,9 @@ export default { debugLog('[VocabLessonView] Keine Vokabeln vorhanden'); return; } + if (!this.canStartVocabTrainerPrep) { + return; + } debugLog('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length); debugLog('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0); this.vocabTrainerActive = true; @@ -2329,6 +2402,42 @@ export default { border-radius: 8px; } +.pattern-target { + font-weight: 600; + color: #1a1a1a; +} + +.pattern-gloss { + margin-top: 6px; + font-size: 0.92rem; + color: #555; +} + +.core-patterns-hint { + margin: 0 0 12px; + font-size: 0.9rem; + color: #666; +} + +.vocab-prep-pass { + margin-bottom: 18px; +} + +.vocab-prep-pass .btn-prep-pass { + margin-top: 8px; +} + +.vocab-prep-pass__ready { + margin: 0; + color: #2d6a3e; + font-weight: 600; +} + +.vocab-trainer-locked-hint { + margin: 0; + color: #8a5a00; +} + .grammar-example, .speaking-cue, .pattern-drill-hint {