feat(VocabLessonView, localization): add vocabulary review feature and update translations
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s

- Implemented a new vocabulary review section in VocabLessonView to display the last incorrect answer, including the asked vocabulary, user answer, and correct answer.
- Added localization strings for the new review feature in Cebuano, German, English, Spanish, and French, enhancing user experience across multiple languages.
- Updated the UI to visually differentiate the review section, improving clarity and usability during vocabulary training sessions.
This commit is contained in:
Torsten Schulz (local)
2026-04-20 09:39:48 +02:00
parent dee4991be7
commit 5f13583e41
6 changed files with 75 additions and 0 deletions

View File

@@ -594,6 +594,9 @@
"checkAnswer": "Susihi Answer", "checkAnswer": "Susihi Answer",
"correct": "Tama!", "correct": "Tama!",
"wrong": "Sayop", "wrong": "Sayop",
"trainerWrongReviewTitle": "Tan-awa pag-usab",
"trainerAskedVocab": "Gipangutana",
"trainerYourAnswer": "Imong answer",
"explanation": "Pasabot", "explanation": "Pasabot",
"learn": "Learn", "learn": "Learn",
"exercises": "Tsek sa kapitulo", "exercises": "Tsek sa kapitulo",

View File

@@ -576,6 +576,9 @@
"checkAnswer": "Antwort prüfen", "checkAnswer": "Antwort prüfen",
"correct": "Richtig!", "correct": "Richtig!",
"wrong": "Falsch", "wrong": "Falsch",
"trainerWrongReviewTitle": "Noch einmal ansehen",
"trainerAskedVocab": "Abgefragt",
"trainerYourAnswer": "Deine Antwort",
"explanation": "Erklärung", "explanation": "Erklärung",
"learn": "Lernen", "learn": "Lernen",
"exercises": "Kapitel-Prüfung", "exercises": "Kapitel-Prüfung",

View File

@@ -576,6 +576,9 @@
"checkAnswer": "Check Answer", "checkAnswer": "Check Answer",
"correct": "Correct!", "correct": "Correct!",
"wrong": "Wrong", "wrong": "Wrong",
"trainerWrongReviewTitle": "Review this again",
"trainerAskedVocab": "Asked",
"trainerYourAnswer": "Your answer",
"explanation": "Explanation", "explanation": "Explanation",
"learn": "Learn", "learn": "Learn",
"exercises": "Chapter Test", "exercises": "Chapter Test",

View File

@@ -560,6 +560,9 @@
"checkAnswer": "Comprobar respuesta", "checkAnswer": "Comprobar respuesta",
"correct": "¡Correcto!", "correct": "¡Correcto!",
"wrong": "Incorrecto", "wrong": "Incorrecto",
"trainerWrongReviewTitle": "Repasar de nuevo",
"trainerAskedVocab": "Preguntado",
"trainerYourAnswer": "Tu respuesta",
"explanation": "Explicación", "explanation": "Explicación",
"learn": "Aprender", "learn": "Aprender",
"exercises": "Prueba del capítulo", "exercises": "Prueba del capítulo",

View File

@@ -560,6 +560,9 @@
"checkAnswer": "Vérifier la réponse", "checkAnswer": "Vérifier la réponse",
"correct": "Correct!", "correct": "Correct!",
"wrong": "Incorrect", "wrong": "Incorrect",
"trainerWrongReviewTitle": "À revoir",
"trainerAskedVocab": "Demandé",
"trainerYourAnswer": "Votre réponse",
"explanation": "Explication", "explanation": "Explication",
"learn": "Apprendre", "learn": "Apprendre",
"exercises": "Examen de chapitre", "exercises": "Examen de chapitre",

View File

@@ -320,6 +320,23 @@
{{ $t('socialnetwork.vocab.courses.checkAnswer') }} {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
</button> </button>
</div> </div>
<div v-if="vocabTrainerLastWrongReview" class="vocab-wrong-review">
<strong>{{ $t('socialnetwork.vocab.courses.trainerWrongReviewTitle') }}</strong>
<dl>
<div>
<dt>{{ $t('socialnetwork.vocab.courses.trainerAskedVocab') }}</dt>
<dd>{{ vocabTrainerLastWrongReview.prompt }}</dd>
</div>
<div>
<dt>{{ $t('socialnetwork.vocab.courses.trainerYourAnswer') }}</dt>
<dd>{{ vocabTrainerLastWrongReview.userAnswer }}</dd>
</div>
<div>
<dt>{{ $t('socialnetwork.vocab.courses.correctAnswer') }}</dt>
<dd>{{ vocabTrainerLastWrongReview.answer }}</dd>
</div>
</dl>
</div>
<!-- "Weiter"-Button nur bei falscher Antwort (bei richtiger Antwort wird automatisch weiter gemacht) --> <!-- "Weiter"-Button nur bei falscher Antwort (bei richtiger Antwort wird automatisch weiter gemacht) -->
<div v-if="vocabTrainerMode === 'multiple_choice' && vocabTrainerAnswered && !vocabTrainerLastCorrect" class="vocab-next"> <div v-if="vocabTrainerMode === 'multiple_choice' && vocabTrainerAnswered && !vocabTrainerLastCorrect" class="vocab-next">
<button @click="continueAfterVocabAnswer">{{ $t('socialnetwork.vocab.courses.next') }}</button> <button @click="continueAfterVocabAnswer">{{ $t('socialnetwork.vocab.courses.next') }}</button>
@@ -1085,6 +1102,7 @@ export default {
vocabTrainerAnswered: false, vocabTrainerAnswered: false,
vocabTrainerLastCorrect: false, vocabTrainerLastCorrect: false,
vocabTrainerContinueTimer: null, vocabTrainerContinueTimer: null,
vocabTrainerLastWrongReview: null,
vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning
isCheckingLessonCompletion: false, // Flag um Endlosschleife zu verhindern isCheckingLessonCompletion: false, // Flag um Endlosschleife zu verhindern
isNavigatingToNext: false, // Flag um mehrfache Navigation zu verhindern isNavigatingToNext: false, // Flag um mehrfache Navigation zu verhindern
@@ -3351,6 +3369,7 @@ export default {
this.vocabTrainerReviewAttempts = 0; this.vocabTrainerReviewAttempts = 0;
this.vocabTrainerStats = {}; this.vocabTrainerStats = {};
this.vocabTrainerRepeatQueue = []; this.vocabTrainerRepeatQueue = [];
this.vocabTrainerLastWrongReview = null;
// Bereite Mixed-Pool aus alten Vokabeln vor (ohne Duplikate aus aktueller Lektion) // Bereite Mixed-Pool aus alten Vokabeln vor (ohne Duplikate aus aktueller Lektion)
this.vocabTrainerMixedPool = this._buildMixedPool(); this.vocabTrainerMixedPool = this._buildMixedPool();
this.vocabTrainerPhase = 'current'; this.vocabTrainerPhase = 'current';
@@ -3377,6 +3396,7 @@ export default {
this.vocabTrainerAnswer = ''; this.vocabTrainerAnswer = '';
this.vocabTrainerSelectedChoice = null; this.vocabTrainerSelectedChoice = null;
this.vocabTrainerAnswered = false; this.vocabTrainerAnswered = false;
this.vocabTrainerLastWrongReview = null;
}, },
/** Erstellt den Mixed-Pool aus vorherigen Lektions-Vokabeln (ohne Duplikate der aktuellen Lektion) */ /** Erstellt den Mixed-Pool aus vorherigen Lektions-Vokabeln (ohne Duplikate der aktuellen Lektion) */
_buildMixedPool() { _buildMixedPool() {
@@ -3945,10 +3965,16 @@ export default {
} }
if (this.vocabTrainerLastCorrect) { if (this.vocabTrainerLastCorrect) {
this.vocabTrainerLastWrongReview = null;
this.vocabTrainerCorrect++; this.vocabTrainerCorrect++;
stats.correct++; stats.correct++;
this.resolveRepeatedVocab(this.currentVocabQuestion.vocab); this.resolveRepeatedVocab(this.currentVocabQuestion.vocab);
} else { } else {
this.vocabTrainerLastWrongReview = {
prompt: this.currentVocabQuestion.prompt,
userAnswer,
answer: this.currentVocabQuestion.answer
};
this.vocabTrainerWrong++; this.vocabTrainerWrong++;
stats.wrong++; stats.wrong++;
this.queueFailedVocab(this.currentVocabQuestion.vocab); this.queueFailedVocab(this.currentVocabQuestion.vocab);
@@ -5537,6 +5563,40 @@ export default {
border: 1px solid #f5c6cb; border: 1px solid #f5c6cb;
} }
.vocab-wrong-review {
margin: 12px 0 15px;
padding: 14px 16px;
border: 1px solid #ef9a9a;
border-radius: 6px;
background: #fff1f1;
color: #4a1f1f;
}
.vocab-wrong-review strong {
display: block;
margin-bottom: 10px;
}
.vocab-wrong-review dl {
display: grid;
gap: 8px;
margin: 0;
}
.vocab-wrong-review dl > div {
display: grid;
grid-template-columns: minmax(120px, 0.3fr) 1fr;
gap: 10px;
}
.vocab-wrong-review dt {
font-weight: 700;
}
.vocab-wrong-review dd {
margin: 0;
}
.vocab-next { .vocab-next {
margin-top: 15px; margin-top: 15px;
} }