diff --git a/backend/controllers/adminController.js b/backend/controllers/adminController.js
index b7d821b..78c3818 100644
--- a/backend/controllers/adminController.js
+++ b/backend/controllers/adminController.js
@@ -33,6 +33,7 @@ class AdminController {
this.getUser = this.getUser.bind(this);
this.getUsers = this.getUsers.bind(this);
this.updateUser = this.updateUser.bind(this);
+ this.resetUserVocabLessonProgress = this.resetUserVocabLessonProgress.bind(this);
this.getAdultVerificationRequests = this.getAdultVerificationRequests.bind(this);
this.setAdultVerificationStatus = this.setAdultVerificationStatus.bind(this);
this.getAdultVerificationDocument = this.getAdultVerificationDocument.bind(this);
@@ -129,6 +130,25 @@ class AdminController {
}
}
+ async resetUserVocabLessonProgress(req, res) {
+ const schema = Joi.object({
+ lessonId: Joi.number().integer().positive().required()
+ });
+ const { error, value } = schema.validate(req.body || {});
+ if (error) {
+ return res.status(400).json({ error: error.details[0].message });
+ }
+ try {
+ const { userid: requester } = req.headers;
+ const { id } = req.params;
+ const result = await AdminService.adminResetUserVocabLessonProgress(requester, id, value.lessonId);
+ res.status(200).json(result);
+ } catch (err) {
+ const status = err.message === 'noaccess' ? 403 : (err.message === 'lessonnotfound' ? 404 : 500);
+ res.status(status).json({ error: err.message });
+ }
+ }
+
async getAdultVerificationRequests(req, res) {
try {
const { userid: requester } = req.headers;
diff --git a/backend/controllers/vocabController.js b/backend/controllers/vocabController.js
index 2b8f800..44322dd 100644
--- a/backend/controllers/vocabController.js
+++ b/backend/controllers/vocabController.js
@@ -52,6 +52,7 @@ class VocabController {
// Progress
this.getCourseProgress = this._wrapWithUser((userId, req) => this.service.getCourseProgress(userId, req.params.courseId));
this.updateLessonProgress = this._wrapWithUser((userId, req) => this.service.updateLessonProgress(userId, req.params.lessonId, req.body));
+ this.resetLessonProgress = this._wrapWithUser((userId, req) => this.service.resetMyLessonProgress(userId, req.params.lessonId));
// Grammar Exercises
this.getExerciseTypes = this._wrapWithUser((userId) => this.service.getExerciseTypes());
diff --git a/backend/routers/adminRouter.js b/backend/routers/adminRouter.js
index 5a7a25b..cba3a54 100644
--- a/backend/routers/adminRouter.js
+++ b/backend/routers/adminRouter.js
@@ -25,6 +25,7 @@ router.put('/users/:id/adult-verification', authenticate, adminController.setAdu
router.get('/users/erotic-moderation', authenticate, adminController.getEroticModerationReports);
router.get('/users/erotic-moderation/preview/:type/:targetId', authenticate, adminController.getEroticModerationPreview);
router.put('/users/erotic-moderation/:id', authenticate, adminController.applyEroticModerationAction);
+router.post('/users/:id/vocab-lesson-progress/reset', authenticate, adminController.resetUserVocabLessonProgress);
router.get('/users/:id', authenticate, adminController.getUser);
router.put('/users/:id', authenticate, adminController.updateUser);
diff --git a/backend/routers/vocabRouter.js b/backend/routers/vocabRouter.js
index ab5da22..35b3a67 100644
--- a/backend/routers/vocabRouter.js
+++ b/backend/routers/vocabRouter.js
@@ -48,6 +48,7 @@ router.delete('/courses/:courseId/enroll', vocabController.unenrollFromCourse);
router.get('/courses/:courseId/progress', vocabController.getCourseProgress);
router.get('/lessons/:lessonId', vocabController.getLesson);
router.put('/lessons/:lessonId/progress', vocabController.updateLessonProgress);
+router.delete('/lessons/:lessonId/progress', vocabController.resetLessonProgress);
// Grammar Exercises
router.get('/grammar/exercise-types', vocabController.getExerciseTypes);
diff --git a/backend/scripts/apply-bisaya-course-refresh.js b/backend/scripts/apply-bisaya-course-refresh.js
index f3676ad..5e38589 100644
--- a/backend/scripts/apply-bisaya-course-refresh.js
+++ b/backend/scripts/apply-bisaya-course-refresh.js
@@ -69,17 +69,19 @@ const LESSON_DIDACTICS = {
{ target: 'Si Tatay.', gloss: 'Das ist Papa.' },
{ target: 'Si Kuya nako.', gloss: 'Das ist mein älterer Bruder.' },
{ target: 'Si Ate nako.', gloss: 'Das ist meine ältere Schwester.' },
+ { target: 'Si Dodong nako.', gloss: 'Das ist mein jüngerer Bruder.' },
+ { target: 'Si Inday nako.', gloss: 'Das ist meine jüngere Schwester.' },
{ target: 'Si Lola nako.', gloss: 'Das ist meine Großmutter.' },
{ target: 'Si Lolo nako.', gloss: 'Das ist mein Großvater.' }
],
grammarFocus: [
- { title: 'Respekt in Familienanreden', text: 'Kuya und Ate werden nicht nur in der Familie, sondern auch respektvoll für ältere Personen benutzt.', example: 'Kuya, palihug.' },
+ { title: 'Respekt in Familienanreden', text: 'Kuya und Ate richtest du an ältere Geschwister (oder respektvoll an andere). Dodong und Inday nutzt du für jüngere Brüder bzw. Schwestern; „Ading“ ist eine weiche Anrede an jüngere Geschwister.', example: 'Kuya, palihug. / Si Dodong nako.' },
{ title: 'si als Personenmarker', text: 'Mit "si" markierst du im einfachen Satz eine konkrete Person.', example: 'Si Nanay. Si Tatay.' }
],
speakingPrompts: [
- { title: 'Meine Familie', prompt: 'Stelle vier Familienmitglieder mit kurzen Sätzen vor.', cue: 'Si Nanay. Si Tatay. Si Lola nako. Si Kuya nako.' }
+ { title: 'Meine Familie', prompt: 'Stelle vier Familienmitglieder mit kurzen Sätzen vor.', cue: 'Si Nanay. Si Tatay. Si Kuya nako. Si Dodong nako.' }
],
- practicalTasks: [{ title: 'Familienpraxis', text: 'Nenne laut sechs Familienwörter und bilde danach drei Mini-Sätze über deine Familie.' }]
+ practicalTasks: [{ title: 'Familienpraxis', text: 'Nenne laut die acht Kern-Familienwörter und bilde danach drei Mini-Sätze über deine Familie.' }]
},
'Überlebenssätze - Teil 1': {
learningGoals: [
@@ -116,6 +118,8 @@ const LESSON_DIDACTICS = {
{ target: 'Asa si Tatay?', gloss: 'Wo ist Papa?' },
{ target: 'Naa siya sa balay.', gloss: 'Er ist zu Hause.' },
{ target: 'Kumusta na ang Kuya?', gloss: 'Wie geht es dem älteren Bruder?' },
+ { target: 'Kumusta na ang Dodong?', gloss: 'Wie geht es dem jüngeren Bruder?' },
+ { target: 'Kumusta na ang Inday?', gloss: 'Wie geht es der jüngeren Schwester?' },
{ target: 'Gutom na ko, Nanay.', gloss: 'Ich habe Hunger, Mama.' },
{ target: 'Hapit na ang pagkaon.', gloss: 'Das Essen ist fast fertig.' }
],
diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js
index f79d7ba..4fe654e 100644
--- a/backend/scripts/create-bisaya-course-content.js
+++ b/backend/scripts/create-bisaya-course-content.js
@@ -770,6 +770,36 @@ const BISAYA_EXERCISES = {
},
explanation: '"Ate" bedeutet "ältere Schwester" auf Bisaya. Wird auch für respektvolle Anrede von älteren Frauen verwendet.'
},
+ {
+ exerciseTypeId: 2, // multiple_choice
+ title: 'Wie sagt man "jüngerer Bruder" auf Bisaya?',
+ instruction: 'Wähle die richtige Übersetzung.',
+ questionData: {
+ type: 'multiple_choice',
+ question: 'Wie sagt man "jüngerer Bruder" auf Bisaya?',
+ options: ['Dodong', 'Inday', 'Kuya', 'Ate']
+ },
+ answerData: {
+ type: 'multiple_choice',
+ correctAnswer: 0
+ },
+ explanation: '"Dodong" ist die gängige Bisaya-Bezeichnung für einen jüngeren Bruder (von älteren Geschwistern aus gesehen).'
+ },
+ {
+ exerciseTypeId: 2, // multiple_choice
+ title: 'Wie sagt man "jüngere Schwester" auf Bisaya?',
+ instruction: 'Wähle die richtige Übersetzung.',
+ questionData: {
+ type: 'multiple_choice',
+ question: 'Wie sagt man "jüngere Schwester" auf Bisaya?',
+ options: ['Inday', 'Dodong', 'Ate', 'Kuya']
+ },
+ answerData: {
+ type: 'multiple_choice',
+ correctAnswer: 0
+ },
+ explanation: '"Inday" ist die gängige Bisaya-Bezeichnung für eine jüngere Schwester (von älteren Geschwistern aus gesehen).'
+ },
{
exerciseTypeId: 2, // multiple_choice
title: 'Wie sagt man "Großmutter" auf Bisaya?',
@@ -806,14 +836,14 @@ const BISAYA_EXERCISES = {
instruction: 'Fülle die Lücken mit den richtigen Bisaya-Familienwörtern.',
questionData: {
type: 'gap_fill',
- text: '{gap} (Mutter) | {gap} (Vater) | {gap} (älterer Bruder) | {gap} (ältere Schwester) | {gap} (Großmutter) | {gap} (Großvater)',
- gaps: 6
+ text: '{gap} (Mutter) | {gap} (Vater) | {gap} (älterer Bruder) | {gap} (ältere Schwester) | {gap} (jüngerer Bruder) | {gap} (jüngere Schwester) | {gap} (Großmutter) | {gap} (Großvater)',
+ gaps: 8
},
answerData: {
type: 'gap_fill',
- answers: ['Nanay', 'Tatay', 'Kuya', 'Ate', 'Lola', 'Lolo']
+ answers: ['Nanay', 'Tatay', 'Kuya', 'Ate', 'Dodong', 'Inday', 'Lola', 'Lolo']
},
- explanation: 'Nanay = Mutter, Tatay = Vater, Kuya = älterer Bruder, Ate = ältere Schwester, Lola = Großmutter, Lolo = Großvater.'
+ explanation: 'Nanay = Mutter, Tatay = Vater, Kuya = älterer Bruder, Ate = ältere Schwester, Dodong = jüngerer Bruder, Inday = jüngere Schwester, Lola = Großmutter, Lolo = Großvater.'
},
{
exerciseTypeId: 4, // transformation
diff --git a/backend/scripts/create-bisaya-course.js b/backend/scripts/create-bisaya-course.js
index ad143e9..f3aa7c6 100755
--- a/backend/scripts/create-bisaya-course.js
+++ b/backend/scripts/create-bisaya-course.js
@@ -117,14 +117,16 @@ const LESSON_DIDACTICS = {
{ target: 'Si Tatay.', gloss: 'Das ist Papa.' },
{ target: 'Si Kuya nako.', gloss: 'Das ist mein älterer Bruder.' },
{ target: 'Si Ate nako.', gloss: 'Das ist meine ältere Schwester.' },
+ { target: 'Si Dodong nako.', gloss: 'Das ist mein jüngerer Bruder.' },
+ { target: 'Si Inday nako.', gloss: 'Das ist meine jüngere Schwester.' },
{ target: 'Si Lola nako.', gloss: 'Das ist meine Großmutter.' },
{ target: 'Si Lolo nako.', gloss: 'Das ist mein Großvater.' }
],
grammarFocus: [
{
title: 'Respekt in Familienanreden',
- text: 'Kuya und Ate werden nicht nur in der Familie, sondern auch respektvoll für ältere Personen benutzt.',
- example: 'Kuya, palihug.'
+ text: 'Kuya und Ate richtest du an ältere Geschwister (oder respektvoll an andere). Dodong und Inday nutzt du für jüngere Brüder bzw. Schwestern; „Ading“ ist eine weiche Anrede an jüngere Geschwister.',
+ example: 'Kuya, palihug. / Si Dodong nako.'
},
{
title: 'si als Personenmarker',
@@ -136,13 +138,13 @@ const LESSON_DIDACTICS = {
{
title: 'Meine Familie',
prompt: 'Stelle vier Familienmitglieder mit kurzen Sätzen vor.',
- cue: 'Si Nanay. Si Tatay. Si Lola nako. Si Kuya nako.'
+ cue: 'Si Nanay. Si Tatay. Si Kuya nako. Si Dodong nako.'
}
],
practicalTasks: [
{
title: 'Familienpraxis',
- text: 'Nenne laut sechs Familienwörter und bilde danach drei Mini-Sätze über deine Familie.'
+ text: 'Nenne laut die acht Kern-Familienwörter und bilde danach drei Mini-Sätze über deine Familie.'
}
]
},
@@ -202,6 +204,8 @@ const LESSON_DIDACTICS = {
{ target: 'Asa si Tatay?', gloss: 'Wo ist Papa?' },
{ target: 'Naa siya sa balay.', gloss: 'Er ist zu Hause.' },
{ target: 'Kumusta na ang Kuya?', gloss: 'Wie geht es dem älteren Bruder?' },
+ { target: 'Kumusta na ang Dodong?', gloss: 'Wie geht es dem jüngeren Bruder?' },
+ { target: 'Kumusta na ang Inday?', gloss: 'Wie geht es der jüngeren Schwester?' },
{ target: 'Gutom na ko, Nanay.', gloss: 'Ich habe Hunger, Mama.' },
{ target: 'Hapit na ang pagkaon.', gloss: 'Das Essen ist fast fertig.' }
],
@@ -463,9 +467,9 @@ const LESSONS = [
cultural: 'Diese Sätze helfen dir sofort im Alltag weiter.' },
{ week: 1, day: 2, num: 3, type: 'vocab', title: 'Familienwörter',
- desc: 'Mama, Papa, Kuya, Ate, Lola, Lolo und mehr',
+ desc: 'Mama, Papa, Kuya, Ate, Dodong, Inday, Lola, Lolo',
targetMin: 20, targetScore: 85, review: true,
- cultural: 'Kuya und Ate werden auch für Nicht-Verwandte verwendet – sehr respektvoll!' },
+ cultural: 'Kuya und Ate für Ältere; Dodong und Inday für jüngere Geschwister. Kuya/Ate werden auch respektvoll außerhalb der Familie genutzt.' },
{ week: 1, day: 2, num: 4, type: 'conversation', title: 'Familien-Gespräche',
desc: 'Einfache Gespräche mit Familienmitgliedern',
diff --git a/backend/scripts/update-bisaya-didactics.js b/backend/scripts/update-bisaya-didactics.js
index 3948ab4..f0d667f 100644
--- a/backend/scripts/update-bisaya-didactics.js
+++ b/backend/scripts/update-bisaya-didactics.js
@@ -67,18 +67,20 @@ const LESSON_DIDACTICS = {
{ target: 'Si Tatay.', gloss: 'Das ist Papa.' },
{ target: 'Si Kuya nako.', gloss: 'Das ist mein älterer Bruder.' },
{ target: 'Si Ate nako.', gloss: 'Das ist meine ältere Schwester.' },
+ { target: 'Si Dodong nako.', gloss: 'Das ist mein jüngerer Bruder.' },
+ { target: 'Si Inday nako.', gloss: 'Das ist meine jüngere Schwester.' },
{ target: 'Si Lola nako.', gloss: 'Das ist meine Großmutter.' },
{ target: 'Si Lolo nako.', gloss: 'Das ist mein Großvater.' }
],
grammarFocus: [
- { title: 'Respekt in Familienanreden', text: 'Kuya und Ate werden nicht nur in der Familie, sondern auch respektvoll für ältere Personen benutzt.', example: 'Kuya, palihug.' },
+ { title: 'Respekt in Familienanreden', text: 'Kuya und Ate richtest du an ältere Geschwister (oder respektvoll an andere). Dodong und Inday nutzt du für jüngere Brüder bzw. Schwestern; „Ading“ ist eine weiche Anrede an jüngere Geschwister.', example: 'Kuya, palihug. / Si Dodong nako.' },
{ title: 'si als Personenmarker', text: 'Mit "si" markierst du im einfachen Satz eine konkrete Person.', example: 'Si Nanay. Si Tatay.' }
],
speakingPrompts: [
- { title: 'Meine Familie', prompt: 'Stelle vier Familienmitglieder mit kurzen Sätzen vor.', cue: 'Si Nanay. Si Tatay. Si Lola nako. Si Kuya nako.' }
+ { title: 'Meine Familie', prompt: 'Stelle vier Familienmitglieder mit kurzen Sätzen vor.', cue: 'Si Nanay. Si Tatay. Si Kuya nako. Si Dodong nako.' }
],
practicalTasks: [
- { title: 'Familienpraxis', text: 'Nenne laut sechs Familienwörter und bilde danach drei Mini-Sätze über deine Familie.' }
+ { title: 'Familienpraxis', text: 'Nenne laut die acht Kern-Familienwörter und bilde danach drei Mini-Sätze über deine Familie.' }
]
},
'Überlebenssätze - Teil 1': {
@@ -118,6 +120,8 @@ const LESSON_DIDACTICS = {
{ target: 'Asa si Tatay?', gloss: 'Wo ist Papa?' },
{ target: 'Naa siya sa balay.', gloss: 'Er ist zu Hause.' },
{ target: 'Kumusta na ang Kuya?', gloss: 'Wie geht es dem älteren Bruder?' },
+ { target: 'Kumusta na ang Dodong?', gloss: 'Wie geht es dem jüngeren Bruder?' },
+ { target: 'Kumusta na ang Inday?', gloss: 'Wie geht es der jüngeren Schwester?' },
{ target: 'Gutom na ko, Nanay.', gloss: 'Ich habe Hunger, Mama.' },
{ target: 'Hapit na ang pagkaon.', gloss: 'Das Essen ist fast fertig.' }
],
diff --git a/backend/scripts/update-family-words-exercises.js b/backend/scripts/update-family-words-exercises.js
index 2451d59..e95c470 100755
--- a/backend/scripts/update-family-words-exercises.js
+++ b/backend/scripts/update-family-words-exercises.js
@@ -48,6 +48,22 @@ const FAMILY_WORDS = {
it: 'sorella maggiore',
pt: 'irmã mais velha'
},
+ 'jüngerer Bruder': {
+ de: 'jüngerer Bruder',
+ en: 'younger brother',
+ es: 'hermano menor',
+ fr: 'frère cadet',
+ it: 'fratello minore',
+ pt: 'irmão mais novo'
+ },
+ 'jüngere Schwester': {
+ de: 'jüngere Schwester',
+ en: 'younger sister',
+ es: 'hermana menor',
+ fr: 'sœur cadette',
+ it: 'sorella minore',
+ pt: 'irmã mais nova'
+ },
Großmutter: {
de: 'Großmutter',
en: 'Grandmother',
@@ -72,6 +88,8 @@ const BISAYA_TRANSLATIONS = {
'Vater': 'Tatay',
'älterer Bruder': 'Kuya',
'ältere Schwester': 'Ate',
+ 'jüngerer Bruder': 'Dodong',
+ 'jüngere Schwester': 'Inday',
'Großmutter': 'Lola',
'Großvater': 'Lolo'
};
@@ -98,7 +116,7 @@ function createFamilyWordsExercises(nativeLanguageName) {
// Multiple Choice Übungen für jedes Familienwort
const familyWords = Object.keys(FAMILY_WORDS);
- const bisayaWords = ['Nanay', 'Tatay', 'Kuya', 'Ate', 'Lola', 'Lolo'];
+ const bisayaWords = ['Nanay', 'Tatay', 'Kuya', 'Ate', 'Dodong', 'Inday', 'Lola', 'Lolo'];
familyWords.forEach((key, index) => {
const nativeWord = FAMILY_WORDS[key][langCode];
diff --git a/backend/services/adminService.js b/backend/services/adminService.js
index d43d239..1a3c2ee 100644
--- a/backend/services/adminService.js
+++ b/backend/services/adminService.js
@@ -34,6 +34,7 @@ import RelationshipType from "../models/falukant/type/relationship.js";
import RelationshipState from "../models/falukant/data/relationship_state.js";
import { sequelize } from '../utils/sequelize.js';
import npcCreationJobService from './npcCreationJobService.js';
+import VocabService from './vocabService.js';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import path from 'path';
@@ -1957,6 +1958,21 @@ class AdminService {
}
return job;
}
+
+ async adminResetUserVocabLessonProgress(requesterHashedId, targetHashedId, lessonId) {
+ if (!(await this.hasUserAccess(requesterHashedId, 'useradministration'))) {
+ throw new Error('noaccess');
+ }
+ const vocab = new VocabService();
+ try {
+ return await vocab.adminResetLessonProgressForUser(targetHashedId, Number(lessonId));
+ } catch (e) {
+ if (e.status === 404) {
+ throw new Error('lessonnotfound');
+ }
+ throw e;
+ }
+ }
}
export default new AdminService();
diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js
index 9d07935..116d584 100644
--- a/backend/services/vocabService.js
+++ b/backend/services/vocabService.js
@@ -2485,6 +2485,66 @@ export default class VocabService {
return this._serializeLessonProgress(progress, lessonData);
}
+ /**
+ * Löscht nur den Fortschritt zu einer Lektion (Zeile vocab_course_progress + zugehörige grammar-exercise-progress).
+ * Gesamtkurs / andere Lektionen bleiben unberührt.
+ */
+ async _purgeLessonProgressForUser(userId, lessonId) {
+ const numericLessonId = Number(lessonId);
+ const exercises = await VocabGrammarExercise.findAll({
+ where: { lessonId: numericLessonId },
+ attributes: ['id']
+ });
+ const exerciseIds = exercises.map((e) => e.id);
+ let deletedExerciseProgressRows = 0;
+ if (exerciseIds.length > 0) {
+ deletedExerciseProgressRows = await VocabGrammarExerciseProgress.destroy({
+ where: { userId, exerciseId: { [Op.in]: exerciseIds } }
+ });
+ }
+ const deletedLessonProgressRows = await VocabCourseProgress.destroy({
+ where: { userId, lessonId: numericLessonId }
+ });
+ return {
+ success: true,
+ lessonId: numericLessonId,
+ deletedLessonProgressRows,
+ deletedExerciseProgressRows
+ };
+ }
+
+ /** Eingeloggter Nutzer setzt eigene Lektion zurück (nur bei Kurs-Einschreibung). */
+ async resetMyLessonProgress(hashedUserId, lessonId) {
+ const user = await this._getUserByHashedId(hashedUserId);
+ const lesson = await VocabCourseLesson.findByPk(Number(lessonId));
+ if (!lesson) {
+ const err = new Error('Lesson not found');
+ err.status = 404;
+ throw err;
+ }
+ const enrollment = await VocabCourseEnrollment.findOne({
+ where: { userId: user.id, courseId: lesson.courseId }
+ });
+ if (!enrollment) {
+ const err = new Error('Not enrolled in this course');
+ err.status = 403;
+ throw err;
+ }
+ return this._purgeLessonProgressForUser(user.id, lesson.id);
+ }
+
+ /** Admin: Zielnutzer per Hash, ohne Einschreibungszwang (idempotentes Löschen). */
+ async adminResetLessonProgressForUser(targetHashedUserId, lessonId) {
+ const user = await this._getUserByHashedId(targetHashedUserId);
+ const lesson = await VocabCourseLesson.findByPk(Number(lessonId));
+ if (!lesson) {
+ const err = new Error('Lesson not found');
+ err.status = 404;
+ throw err;
+ }
+ return this._purgeLessonProgressForUser(user.id, lesson.id);
+ }
+
// ========== GRAMMAR EXERCISE METHODS ==========
async getExerciseTypes() {
diff --git a/docs/BISAYA_COURSE_EXAMPLE.md b/docs/BISAYA_COURSE_EXAMPLE.md
index d88eaff..dae7047 100644
--- a/docs/BISAYA_COURSE_EXAMPLE.md
+++ b/docs/BISAYA_COURSE_EXAMPLE.md
@@ -29,6 +29,8 @@ Dieses Dokument zeigt, wie du einen strukturierten Sprachkurs wie den beschriebe
- Papa / Tatay – Vater
- Kuya – älterer Bruder
- Ate – ältere Schwester
+ - Dodong – jüngerer Bruder
+ - Inday – jüngere Schwester
- Lola / Lolo – Oma / Opa
- **Kulturelle Notizen:** Kuya und Ate werden auch für Nicht-Verwandte verwendet – sehr respektvoll!
diff --git a/frontend/src/i18n/locales/ceb/admin.json b/frontend/src/i18n/locales/ceb/admin.json
index 7370512..a8ea9d7 100644
--- a/frontend/src/i18n/locales/ceb/admin.json
+++ b/frontend/src/i18n/locales/ceb/admin.json
@@ -21,6 +21,20 @@
"actions": "Mga aksyon",
"search": "Pangita"
},
+ "vocabLessonReset": {
+ "title": "Kurso sa pinulongan: pag-uswag sa leksiyon",
+ "intro": "Tangtanga ang pag-uswag, mga resulta sa ehersisyo ug natipig nga kahimtang sa usa ka leksiyon lamang (dili ang tibuok kurso). Makita ra ang mga kurso nga makita sa imong admin account (publiko o imoha).",
+ "loadCourses": "Ikarga ang mga kurso",
+ "selectCourse": "Kurso",
+ "selectLesson": "Leksiyon",
+ "reset": "I-reset ang leksiyon niining user",
+ "confirm": "Tinuod nga tangtangon ang pag-uswag sa leksiyon nga «{lesson}» ni {username}?",
+ "success": "Na-reset na ang pag-uswag sa leksiyon.",
+ "error": "Dili ma-reset.",
+ "pickUserFirst": "Una pagpili ug user.",
+ "noCourses": "Walay nakarga nga kurso o walay makita nga kurso.",
+ "loadingLessons": "Nagkarga sa mga leksiyon …"
+ },
"rights": {
"add": "Idugang ang katungod",
"select": "Palihog pagpili",
diff --git a/frontend/src/i18n/locales/ceb/socialnetwork.json b/frontend/src/i18n/locales/ceb/socialnetwork.json
index 0ddf9e8..03948e1 100644
--- a/frontend/src/i18n/locales/ceb/socialnetwork.json
+++ b/frontend/src/i18n/locales/ceb/socialnetwork.json
@@ -381,7 +381,11 @@
"lessonReviewHintNextDue": "Sunod nga petsa: {due}.",
"reviewTimeNow": "karon",
"reviewTimeTomorrow": "ugma",
- "reviewTimeInDays": "sulod sa {count} ka adlaw"
+ "reviewTimeInDays": "sulod sa {count} ka adlaw",
+ "resetLessonProgress": "I-reset ang leksiyon",
+ "resetLessonProgressConfirm": "I-reset ang pag-uswag niining leksiyona? Mawala ang natipig nga kahimtang, mga resulta sa ehersisyo ug sa trainer. Ang ubang leksiyon dili maapektuhan.",
+ "resetLessonProgressSuccess": "Na-reset na ang pag-uswag sa leksiyon.",
+ "resetLessonProgressError": "Dili ma-reset ang leksiyon."
}
}
}
diff --git a/frontend/src/i18n/locales/de/admin.json b/frontend/src/i18n/locales/de/admin.json
index 7eedd29..5392395 100644
--- a/frontend/src/i18n/locales/de/admin.json
+++ b/frontend/src/i18n/locales/de/admin.json
@@ -30,6 +30,20 @@
"actions": "Aktionen",
"search": "Suchen"
},
+ "vocabLessonReset": {
+ "title": "Sprachkurs: Lektionsfortschritt",
+ "intro": "Fortschritt, Übungsergebnisse und gespeicherter Lektionszustand für eine einzelne Lektion löschen (nicht der ganze Kurs). Es werden nur Kurse gelistet, die du als Admin sehen kannst (öffentlich oder eigene).",
+ "loadCourses": "Kurse laden",
+ "selectCourse": "Kurs",
+ "selectLesson": "Lektion",
+ "reset": "Lektion für diesen Nutzer zurücksetzen",
+ "confirm": "Fortschritt der Lektion „{lesson}“ für {username} wirklich löschen?",
+ "success": "Lektionsfortschritt wurde zurückgesetzt.",
+ "error": "Zurücksetzen fehlgeschlagen.",
+ "pickUserFirst": "Zuerst einen Benutzer auswählen.",
+ "noCourses": "Keine Kurse geladen oder keine sichtbaren Kurse.",
+ "loadingLessons": "Lektionen werden geladen …"
+ },
"adultVerification": {
"title": "[Admin] - Erotik-Freigaben",
"intro": "Volljährige Nutzer können den Erotikbereich beantragen. Hier werden Anfragen geprüft und freigegeben oder abgelehnt.",
diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json
index f6d2a93..7dc9481 100644
--- a/frontend/src/i18n/locales/de/socialnetwork.json
+++ b/frontend/src/i18n/locales/de/socialnetwork.json
@@ -487,6 +487,10 @@
"score": "Punktzahl",
"review": "Wiederholen",
"start": "Starten",
+ "resetLessonProgress": "Lektion zurücksetzen",
+ "resetLessonProgressConfirm": "Fortschritt dieser Lektion zurücksetzen? Gespeicherter Stand, Übungsergebnisse und Trainer-Zustand werden gelöscht. Andere Lektionen bleiben unverändert.",
+ "resetLessonProgressSuccess": "Die Lektion wurde zurückgesetzt.",
+ "resetLessonProgressError": "Die Lektion konnte nicht zurückgesetzt werden.",
"noLessons": "Dieser Kurs hat noch keine Lektionen.",
"lessonNumber": "Lektionsnummer",
"chapter": "Kapitel",
diff --git a/frontend/src/i18n/locales/en/admin.json b/frontend/src/i18n/locales/en/admin.json
index 987bb50..5a90fec 100644
--- a/frontend/src/i18n/locales/en/admin.json
+++ b/frontend/src/i18n/locales/en/admin.json
@@ -30,6 +30,20 @@
"actions": "Actions",
"search": "Search"
},
+ "vocabLessonReset": {
+ "title": "Language course: lesson progress",
+ "intro": "Delete progress, exercise results and saved lesson state for a single lesson (not the whole course). Only courses you can see as this admin account are listed (public or your own).",
+ "loadCourses": "Load courses",
+ "selectCourse": "Course",
+ "selectLesson": "Lesson",
+ "reset": "Reset lesson for this user",
+ "confirm": "Really delete progress for lesson “{lesson}” for {username}?",
+ "success": "Lesson progress was reset.",
+ "error": "Reset failed.",
+ "pickUserFirst": "Select a user first.",
+ "noCourses": "No courses loaded or no visible courses.",
+ "loadingLessons": "Loading lessons…"
+ },
"adultVerification": {
"title": "[Admin] - Erotic approvals",
"intro": "Adult users can request access to the erotic area. Requests can be reviewed, approved or rejected here.",
diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json
index 7767069..2548457 100644
--- a/frontend/src/i18n/locales/en/socialnetwork.json
+++ b/frontend/src/i18n/locales/en/socialnetwork.json
@@ -487,6 +487,10 @@
"score": "Score",
"review": "Review",
"start": "Start",
+ "resetLessonProgress": "Reset lesson",
+ "resetLessonProgressConfirm": "Reset progress for this lesson? Saved state, exercise results, and trainer progress will be cleared. Other lessons stay unchanged.",
+ "resetLessonProgressSuccess": "Lesson progress was reset.",
+ "resetLessonProgressError": "Could not reset the lesson.",
"noLessons": "This course has no lessons yet.",
"lessonNumber": "Lesson Number",
"chapter": "Chapter",
diff --git a/frontend/src/i18n/locales/es/admin.json b/frontend/src/i18n/locales/es/admin.json
index 479bf88..d1c8faf 100644
--- a/frontend/src/i18n/locales/es/admin.json
+++ b/frontend/src/i18n/locales/es/admin.json
@@ -30,6 +30,20 @@
"actions": "Acciones",
"search": "Buscar"
},
+ "vocabLessonReset": {
+ "title": "Curso de idiomas: progreso de lección",
+ "intro": "Elimina el progreso, los resultados de ejercicios y el estado guardado de una sola lección (no todo el curso). Solo se listan cursos visibles para tu cuenta de administración (públicos o propios).",
+ "loadCourses": "Cargar cursos",
+ "selectCourse": "Curso",
+ "selectLesson": "Lección",
+ "reset": "Restablecer lección para este usuario",
+ "confirm": "¿Borrar de verdad el progreso de la lección «{lesson}» para {username}?",
+ "success": "Se restableció el progreso de la lección.",
+ "error": "No se pudo restablecer.",
+ "pickUserFirst": "Primero elige un usuario.",
+ "noCourses": "No hay cursos cargados o no hay cursos visibles.",
+ "loadingLessons": "Cargando lecciones…"
+ },
"adultVerification": {
"title": "[Admin] - Aprobaciones eróticas",
"intro": "Los usuarios adultos pueden solicitar acceso al área erótica. Aquí se revisan, aprueban o rechazan las solicitudes.",
diff --git a/frontend/src/i18n/locales/es/socialnetwork.json b/frontend/src/i18n/locales/es/socialnetwork.json
index 99aa1a4..313f5ed 100644
--- a/frontend/src/i18n/locales/es/socialnetwork.json
+++ b/frontend/src/i18n/locales/es/socialnetwork.json
@@ -485,6 +485,10 @@
"score": "Puntuación",
"review": "Repasar",
"start": "Empezar",
+ "resetLessonProgress": "Restablecer lección",
+ "resetLessonProgressConfirm": "¿Restablecer el progreso de esta lección? Se borrarán el estado guardado, los resultados de ejercicios y el progreso del entrenador. Las demás lecciones no cambian.",
+ "resetLessonProgressSuccess": "Se restableció el progreso de la lección.",
+ "resetLessonProgressError": "No se pudo restablecer la lección.",
"noLessons": "Este curso aún no tiene lecciones.",
"lessonNumber": "Número de lección",
"chapter": "Capítulo",
diff --git a/frontend/src/views/admin/UsersView.vue b/frontend/src/views/admin/UsersView.vue
index 731b062..2dba86a 100644
--- a/frontend/src/views/admin/UsersView.vue
+++ b/frontend/src/views/admin/UsersView.vue
@@ -30,6 +30,56 @@
+
+ {{ $t('admin.vocabLessonReset.intro') }}
+ {{ $t('admin.vocabLessonReset.noCourses') }}
+ {{ $t('admin.vocabLessonReset.title') }}
+