From 02b3636e10ae5299756032ec80c1f9ae475182a1 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 1 Apr 2026 08:58:36 +0200 Subject: [PATCH] feat(vocab-prep): implement enhanced vocabulary preparation steps in VocabLessonView - Introduced a structured vocabulary preparation process with two review stages before engaging with the vocabulary trainer. - Added localization support for new vocabulary preparation messages in German, English, and Spanish, improving accessibility for users. - Updated the VocabLessonView component to display current progress and next steps during vocabulary preparation, enhancing user guidance. - Refactored related logic to manage preparation stages and item navigation effectively. --- .../src/i18n/locales/de/socialnetwork.json | 2 + .../src/i18n/locales/en/socialnetwork.json | 2 + .../src/i18n/locales/es/socialnetwork.json | 2 + frontend/src/views/social/VocabLessonView.vue | 123 ++++++++++++++---- 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json index 071e782..4faeaf6 100644 --- a/frontend/src/i18n/locales/de/socialnetwork.json +++ b/frontend/src/i18n/locales/de/socialnetwork.json @@ -450,6 +450,8 @@ "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.", + "vocabPrepProgress": "Durchgang {pass}: Begriff {current} von {total}", + "vocabPrepNextItem": "Nächster Begriff", "vocabPrepConfirm1": "Erste Durchsicht erledigt", "vocabPrepStep2": "Gehe die gleichen Begriffe noch einmal durch (aktive Wiederholung, ohne zu üben).", "vocabPrepConfirm2": "Zweite Durchsicht erledigt", diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json index 677786a..ee7559d 100644 --- a/frontend/src/i18n/locales/en/socialnetwork.json +++ b/frontend/src/i18n/locales/en/socialnetwork.json @@ -450,6 +450,8 @@ "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.", + "vocabPrepProgress": "Pass {pass}: item {current} of {total}", + "vocabPrepNextItem": "Next item", "vocabPrepConfirm1": "First pass done", "vocabPrepStep2": "Go through the same items again (active review, not testing yet).", "vocabPrepConfirm2": "Second pass done", diff --git a/frontend/src/i18n/locales/es/socialnetwork.json b/frontend/src/i18n/locales/es/socialnetwork.json index bfee817..065fa1b 100644 --- a/frontend/src/i18n/locales/es/socialnetwork.json +++ b/frontend/src/i18n/locales/es/socialnetwork.json @@ -448,6 +448,8 @@ "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).", + "vocabPrepProgress": "Pasada {pass}: término {current} de {total}", + "vocabPrepNextItem": "Siguiente término", "vocabPrepConfirm1": "Primera lectura hecha", "vocabPrepStep2": "Repasa los mismos elementos otra vez (repaso activo, aún sin practicar).", "vocabPrepConfirm2": "Segunda lectura hecha", diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 2d00d4b..f96084f 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -217,8 +217,41 @@ - -
+ +
+

{{ $t('socialnetwork.vocab.courses.vocabPrepTitle') }}

+ +

{{ $t('socialnetwork.vocab.courses.vocabPrepReady') }}

+
+ + +

{{ $t('socialnetwork.vocab.courses.importantVocab') }}

{{ $t('socialnetwork.vocab.courses.vocabInfoText') }}

@@ -230,27 +263,6 @@
- -
-

{{ $t('socialnetwork.vocab.courses.vocabPrepTitle') }}

- - -

{{ $t('socialnetwork.vocab.courses.vocabPrepReady') }}

-
-

{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}

@@ -764,6 +776,7 @@ export default { exercisePreparationCompleted: false, /** 0 = noch kein Durchgang, 1 = erste Durchsicht, 2 = zweite Durchsicht — dann Vokabeltrainer */ lessonPrepStage: 0, + lessonPrepIndex: 0, currentVocabQuestion: null, vocabTrainerAnswer: '', vocabTrainerSelectedChoice: null, @@ -933,11 +946,34 @@ export default { .map((p) => this.normalizeCorePatternEntry(p)) .filter(Boolean); }, + prepItems() { + if (this.normalizedCorePatterns.length > 0) { + return this.normalizedCorePatterns; + } + return (this.importantVocab || []) + .map((item) => ({ + target: String(item?.reference || '').trim(), + gloss: String(item?.learning || '').trim() + })) + .filter((item) => item.target); + }, + currentPrepItem() { + return this.prepItems[this.lessonPrepIndex] || null; + }, + isLastPrepItemInPass() { + if (this.prepItems.length === 0) { + return true; + } + return this.lessonPrepIndex >= this.prepItems.length - 1; + }, corePatternsHaveGloss() { return this.normalizedCorePatterns.some((p) => p.gloss); }, canStartVocabTrainerPrep() { - if (!this.importantVocab || this.lessonPrepStage < 2) { + if (!this.importantVocab || this.importantVocab.length === 0) { + return false; + } + if (this.prepItems.length > 0 && this.lessonPrepStage < 2) { return false; } return true; @@ -1011,6 +1047,17 @@ export default { if (!n) return ''; return n.gloss ? `${n.target} (${n.gloss})` : n.target; }, + advancePrepPass() { + if (this.lessonPrepStage >= 2 || this.prepItems.length === 0) { + return; + } + if (!this.isLastPrepItemInPass) { + this.lessonPrepIndex += 1; + return; + } + this.lessonPrepStage += 1; + this.lessonPrepIndex = 0; + }, openExercisesTab() { if (!this.canAccessExercises) { this.activeTab = 'learn'; @@ -1202,6 +1249,7 @@ export default { this.assistantError = ''; this.exercisePreparationCompleted = false; this.lessonPrepStage = 0; + this.lessonPrepIndex = 0; this.vocabTrainerActive = false; this.vocabTrainerPool = []; this.vocabTrainerMixedPool = []; @@ -2423,6 +2471,33 @@ export default { margin-bottom: 18px; } +.vocab-prep-pass__step { + margin: 0 0 10px; + font-size: 0.9rem; + color: #7a6848; + font-weight: 600; +} + +.vocab-prep-card { + margin-top: 12px; + padding: 16px 18px; + background: #fff; + border: 1px solid #e5dccf; + border-radius: 10px; +} + +.vocab-prep-card__target { + font-size: 1.2rem; + font-weight: 700; + color: #1f1a16; +} + +.vocab-prep-card__gloss { + margin-top: 8px; + font-size: 1rem; + color: #6a5a44; +} + .vocab-prep-pass .btn-prep-pass { margin-top: 8px; }