From e17f0cdce041371548bbce5c9fd5296e104fdda7 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Tue, 7 Apr 2026 09:09:43 +0200 Subject: [PATCH] feat(vocab): enhance vocabulary exercises and localization support - Updated core patterns in BISAYA_PHASE5_DIDACTICS to include gloss translations for better understanding. - Refactored vocabulary exercise generation in update-food-care-exercises.js to improve randomization and user engagement. - Added new exercise types and improved question structures for vocabulary lessons, enhancing the learning experience. - Enhanced localization files for German, English, and Spanish to support new exercise features and improve user guidance. - Updated VocabLessonView to incorporate sequential navigation for exercises, providing a more structured learning flow. --- .../scripts/bisaya-course-phase5-extension.js | 7 +- backend/scripts/update-food-care-exercises.js | 72 ++++- .../src/i18n/locales/de/socialnetwork.json | 8 + .../src/i18n/locales/en/socialnetwork.json | 8 + .../src/i18n/locales/es/socialnetwork.json | 8 + frontend/src/views/social/VocabLessonView.vue | 283 ++++++++++++++++-- 6 files changed, 348 insertions(+), 38 deletions(-) diff --git a/backend/scripts/bisaya-course-phase5-extension.js b/backend/scripts/bisaya-course-phase5-extension.js index 4b19099..7183f81 100644 --- a/backend/scripts/bisaya-course-phase5-extension.js +++ b/backend/scripts/bisaya-course-phase5-extension.js @@ -52,7 +52,12 @@ export const BISAYA_PHASE5_DIDACTICS = { 'Nahe Bedeutungen in stabilere Antworten überführen.', 'Häufige Stolperstellen transparent machen.' ], - corePatterns: ['Palangga taka.', 'Mingaw ko nimo.', 'Magpahuway sa.', 'Andam na ka?'] + corePatterns: [ + { target: 'Palangga taka.', gloss: 'Ich hab dich lieb.' }, + { target: 'Mingaw ko nimo.', gloss: 'Ich vermisse dich.' }, + { target: 'Magpahuway sa.', gloss: 'Ruh dich aus.' }, + { target: 'Andam na ka?', gloss: 'Bist du fertig?' } + ] }, 'Freies Erzählen - Mein Alltag': { learningGoals: [ diff --git a/backend/scripts/update-food-care-exercises.js b/backend/scripts/update-food-care-exercises.js index a97d7d1..3355c6a 100755 --- a/backend/scripts/update-food-care-exercises.js +++ b/backend/scripts/update-food-care-exercises.js @@ -652,23 +652,43 @@ async function updateFoodCareExercises() { totalExercisesCreated++; } } else if (lesson.title === 'Essen & Trinken') { - // Vokabular-Übungen für "Essen & Trinken" - for (const vocab of conversations) { - // Multiple Choice: Muttersprache -> Bisaya + // Vokabular: keine strikte Paarung Deutsch→Bisaya direkt gefolgt von Rückrichtung (vermeidet Verräter-Effekt). + const n = conversations.length; + const offset = Math.max(1, Math.floor(n / 2)); + const revIdx = (i) => (i + offset) % Math.max(n, 1); + + const pickOtherBisaya = (correct, start) => { + for (let t = 1; t <= n; t++) { + const o = conversations[(start + t) % n]?.bisaya; + if (o && o !== correct) return o; + } + return 'Salamat'; + }; + const pickOtherNative = (correct, start) => { + for (let t = 1; t <= n; t++) { + const o = conversations[(start + t) % n]?.native; + if (o && o !== correct) return o; + } + return 'Danke'; + }; + + for (let i = 0; i < n; i++) { + const vocab = conversations[i]; await VocabGrammarExercise.create({ lessonId: lesson.id, - exerciseTypeId: 2, // multiple_choice + exerciseTypeId: 2, exerciseNumber: exerciseNumber++, title: `Wie sagt man "${vocab.native}"?`, instruction: 'Wähle die richtige Übersetzung.', questionData: JSON.stringify({ type: 'multiple_choice', + answerLanguage: 'target', question: `Wie sagt man "${vocab.native}" auf Bisaya?`, options: [ vocab.bisaya, - conversations[(exerciseNumber - 2 + 1) % conversations.length]?.bisaya || 'Salamat', - conversations[(exerciseNumber - 2 + 2) % conversations.length]?.bisaya || 'Maayo', - conversations[(exerciseNumber - 2 + 3) % conversations.length]?.bisaya || 'Palihug' + pickOtherBisaya(vocab.bisaya, i), + pickOtherBisaya(vocab.bisaya, i + 3), + pickOtherBisaya(vocab.bisaya, i + 6) ] }), answerData: JSON.stringify({ @@ -679,22 +699,25 @@ async function updateFoodCareExercises() { createdByUserId: course.owner_user_id || systemUser.id }); totalExercisesCreated++; + } - // Multiple Choice: Bisaya -> Muttersprache + for (let i = 0; i < n; i++) { + const vocab = conversations[revIdx(i)]; await VocabGrammarExercise.create({ lessonId: lesson.id, - exerciseTypeId: 2, // multiple_choice + exerciseTypeId: 2, exerciseNumber: exerciseNumber++, title: `Was bedeutet "${vocab.bisaya}"?`, instruction: 'Wähle die richtige Übersetzung.', questionData: JSON.stringify({ type: 'multiple_choice', + answerLanguage: 'native', question: `Was bedeutet "${vocab.bisaya}"?`, options: [ vocab.native, - conversations[(exerciseNumber - 3 + 1) % conversations.length]?.native || 'Danke', - conversations[(exerciseNumber - 3 + 2) % conversations.length]?.native || 'Bitte', - conversations[(exerciseNumber - 3 + 3) % conversations.length]?.native || 'Gut' + pickOtherNative(vocab.native, i), + pickOtherNative(vocab.native, i + 4), + pickOtherNative(vocab.native, i + 8) ] }), answerData: JSON.stringify({ @@ -706,6 +729,31 @@ async function updateFoodCareExercises() { }); totalExercisesCreated++; } + + for (let i = 0; i < n; i++) { + const vocab = conversations[i]; + await VocabGrammarExercise.create({ + lessonId: lesson.id, + exerciseTypeId: 4, + exerciseNumber: exerciseNumber++, + title: `Tippe auf Bisaya: "${vocab.native}"`, + instruction: 'Übersetze das Wort (Schreibtest).', + questionData: JSON.stringify({ + type: 'transformation', + text: vocab.native, + sourceLanguage: nativeLangName, + targetLanguage: 'Bisaya' + }), + answerData: JSON.stringify({ + type: 'transformation', + correct: vocab.bisaya, + alternatives: [] + }), + explanation: vocab.explanation, + createdByUserId: course.owner_user_id || systemUser.id + }); + totalExercisesCreated++; + } } } diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json index 130bf6b..d6548b0 100644 --- a/frontend/src/i18n/locales/de/socialnetwork.json +++ b/frontend/src/i18n/locales/de/socialnetwork.json @@ -517,6 +517,12 @@ "exerciseProgressLabel": "Fortschritt", "exerciseTargetLabel": "Benötigt", "exerciseCardLabel": "Aufgabe {number}", + "exerciseSequentialProgress": "Frage {current} von {total}", + "exerciseSequentialBack": "Zurück", + "exerciseSequentialNext": "Weiter", + "exerciseWrongTitle": "Noch nicht richtig", + "exerciseReinforcementGoPractice": "Zum Üben wechseln", + "exerciseReinforcementStay": "Bei der Prüfung bleiben", "exerciseStatusOpen": "Offen", "exerciseStatusCorrect": "Erledigt", "exerciseStatusRetry": "Nochmal prüfen", @@ -548,6 +554,8 @@ "vocabPrepTitle": "Vorbereitung vor dem Vokabeltrainer", "vocabPrepStep1": "Lies Kernmuster und Wortliste (Deutsch ↔ Zielsprache) einmal in Ruhe durch.", "vocabPrepProgress": "Durchgang {pass}: Begriff {current} von {total}", + "vocabPrepTargetLabel": "Zielsprache", + "vocabPrepGlossLabel": "Deutsch", "vocabPrepNextItem": "Nächster Begriff", "vocabPrepConfirm1": "Erste Durchsicht erledigt", "vocabPrepStep2": "Gehe die gleichen Begriffe noch einmal durch (aktive Wiederholung, ohne zu üben).", diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json index aafad72..b4cf391 100644 --- a/frontend/src/i18n/locales/en/socialnetwork.json +++ b/frontend/src/i18n/locales/en/socialnetwork.json @@ -517,6 +517,12 @@ "exerciseProgressLabel": "Progress", "exerciseTargetLabel": "Required", "exerciseCardLabel": "Task {number}", + "exerciseSequentialProgress": "Question {current} of {total}", + "exerciseSequentialBack": "Back", + "exerciseSequentialNext": "Next", + "exerciseWrongTitle": "Not quite right", + "exerciseReinforcementGoPractice": "Go to practice", + "exerciseReinforcementStay": "Stay on the test", "exerciseStatusOpen": "Open", "exerciseStatusCorrect": "Done", "exerciseStatusRetry": "Try again", @@ -548,6 +554,8 @@ "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}", + "vocabPrepTargetLabel": "Target language", + "vocabPrepGlossLabel": "Meaning", "vocabPrepNextItem": "Next item", "vocabPrepConfirm1": "First pass done", "vocabPrepStep2": "Go through the same items again (active review, not testing yet).", diff --git a/frontend/src/i18n/locales/es/socialnetwork.json b/frontend/src/i18n/locales/es/socialnetwork.json index 0a387de..90628e1 100644 --- a/frontend/src/i18n/locales/es/socialnetwork.json +++ b/frontend/src/i18n/locales/es/socialnetwork.json @@ -515,6 +515,12 @@ "exerciseProgressLabel": "Progreso", "exerciseTargetLabel": "Necesario", "exerciseCardLabel": "Tarea {number}", + "exerciseSequentialProgress": "Pregunta {current} de {total}", + "exerciseSequentialBack": "Atrás", + "exerciseSequentialNext": "Siguiente", + "exerciseWrongTitle": "Aún no es correcto", + "exerciseReinforcementGoPractice": "Ir a practicar", + "exerciseReinforcementStay": "Seguir en la prueba", "exerciseStatusOpen": "Pendiente", "exerciseStatusCorrect": "Hecha", "exerciseStatusRetry": "Revisar otra vez", @@ -546,6 +552,8 @@ "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}", + "vocabPrepTargetLabel": "Lengua meta", + "vocabPrepGlossLabel": "Significado", "vocabPrepNextItem": "Siguiente término", "vocabPrepConfirm1": "Primera lectura hecha", "vocabPrepStep2": "Repasa los mismos elementos otra vez (repaso activo, aún sin practicar).", diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 2a7c781..6cd71f2 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -125,8 +125,14 @@ : $t('socialnetwork.vocab.courses.vocabPrepStep2') }}

-
{{ currentPrepItem.target }}
-
{{ currentPrepItem.gloss }}
+
+ {{ $t('socialnetwork.vocab.courses.vocabPrepTargetLabel') }} +
{{ currentPrepItem.target }}
+
+
+ {{ $t('socialnetwork.vocab.courses.vocabPrepGlossLabel') }} +
{{ currentPrepItem.gloss || '—' }}
+
+ + +
- {{ $t('socialnetwork.vocab.courses.exerciseCardLabel', { number: index + 1 }) }} + {{ $t('socialnetwork.vocab.courses.exerciseCardLabel', { number: exercisePanelDisplayNumber(index) }) }}

{{ exercise.title }}

+ + +
+
+
+ {{ $t('socialnetwork.vocab.courses.exerciseWrongTitle') }} + +
+
+

+ {{ $t('socialnetwork.vocab.courses.correctAnswer') }}: + {{ exerciseReinforcementCorrectAnswer }} +

+

{{ exerciseReinforcementMessage }}

+
+ +
+