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 || '—' }}
+