feat(bisaya-course): restructure core patterns and enhance vocabulary preparation
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s

- Updated core patterns in various scripts to use an object format with target phrases and glosses, improving clarity and usability for learners.
- Enhanced the VocabService to normalize core pattern entries, ensuring consistent handling of vocabulary data.
- Introduced new vocabulary preparation steps in the VocabLessonView, guiding users through active review processes before engaging with the vocabulary trainer.
- Added localization support for new vocabulary preparation hints and instructions in multiple languages, enhancing user experience across the application.
This commit is contained in:
Torsten Schulz (local)
2026-04-01 08:12:57 +02:00
parent 7e45049e94
commit 0c89c48e68
9 changed files with 312 additions and 59 deletions

View File

@@ -106,7 +106,13 @@ export default class VocabService {
`Lektion: ${lesson?.title || 'Unbekannte Lektion'}`,
lesson?.description ? `Beschreibung: ${lesson.description}` : '',
learningGoals.length ? `Lernziele: ${learningGoals.join(' | ')}` : '',
corePatterns.length ? `Kernmuster: ${corePatterns.join(' | ')}` : '',
corePatterns.length
? `Kernmuster: ${corePatterns.map((p) => {
const n = this._normalizeCorePatternEntry(p);
if (!n) return '';
return n.gloss ? `${n.target} (${n.gloss})` : n.target;
}).filter(Boolean).join(' | ')}`
: '',
speakingPrompts.length
? `Sprechaufträge: ${speakingPrompts.map((item) => item.prompt || item.title || '').filter(Boolean).join(' | ')}`
: '',
@@ -239,14 +245,16 @@ export default class VocabService {
speakingPrompts.forEach((prompt, index) => {
const learning = String(prompt?.prompt || prompt?.title || '').trim();
const reference = String(prompt?.cue || corePatterns[index] || corePatterns[0] || '').trim();
const refEntry = corePatterns[index] ?? corePatterns[0];
const reference = String(prompt?.cue || this._corePatternTarget(refEntry) || '').trim();
if (!learning || !reference || learning === reference) return;
vocabMap.set(`${learning}-${reference}`, { learning, reference });
});
practicalTasks.forEach((task, index) => {
const learning = String(task?.text || task?.title || '').trim();
const reference = String(corePatterns[index] || corePatterns[0] || '').trim();
const refEntry = corePatterns[index] ?? corePatterns[0];
const reference = String(this._corePatternTarget(refEntry) || '').trim();
if (!learning || !reference || learning === reference) return;
vocabMap.set(`${learning}-${reference}`, { learning, reference });
});
@@ -270,6 +278,49 @@ export default class VocabService {
return [];
}
/**
* Kernmuster: Zielsprachen-Phrase + optionale Glossierung (z. B. Deutsch).
* Unterstützt Legacy-Strings, "Phrase|Gloss" und Objekte { target, gloss } / { ceb, de }.
*/
_normalizeCorePatternEntry(entry) {
if (entry === null || entry === undefined || entry === '') {
return null;
}
if (typeof entry === 'object' && !Array.isArray(entry)) {
const target = String(entry.target ?? entry.ceb ?? entry.phrase ?? '').trim();
const gloss = String(entry.gloss ?? entry.de ?? entry.translation ?? '').trim();
if (!target) return null;
return { target, gloss };
}
const s = String(entry).trim();
if (!s) return null;
const pipe = s.indexOf('|');
if (pipe !== -1) {
const target = s.slice(0, pipe).trim();
const gloss = s.slice(pipe + 1).trim();
if (!target) return null;
return { target, gloss };
}
return { target: s, gloss: '' };
}
_normalizeCorePatternList(value) {
if (!value) return [];
const raw = Array.isArray(value)
? value
: (typeof value === 'string'
? value.split(/\r?\n|;/).map((entry) => entry.trim()).filter(Boolean)
: []);
return raw
.map((entry) => this._normalizeCorePatternEntry(entry))
.filter(Boolean);
}
_corePatternTarget(entry) {
const n = this._normalizeCorePatternEntry(entry);
return n ? n.target : '';
}
_normalizeStructuredList(value, keys = ['title', 'text']) {
if (!value) return [];
if (Array.isArray(value)) {
@@ -482,7 +533,7 @@ export default class VocabService {
const uniquePatterns = [...new Set(patterns.map((item) => String(item || '').trim()).filter(Boolean))];
const learningGoals = this._normalizeStringList(plainLesson.learningGoals);
const corePatterns = this._normalizeStringList(plainLesson.corePatterns);
const corePatterns = this._normalizeCorePatternList(plainLesson.corePatterns);
const grammarFocus = this._normalizeStructuredList(plainLesson.grammarFocus, ['title', 'text', 'example']);
const explicitSpeakingPrompts = this._normalizeStructuredList(plainLesson.speakingPrompts, ['title', 'prompt', 'cue']);
const practicalTasks = this._normalizeStructuredList(plainLesson.practicalTasks, ['title', 'text']);
@@ -495,7 +546,9 @@ export default class VocabService {
'Ein bis zwei Satzmuster aktiv anwenden.',
'Kurze Sätze oder Mini-Dialoge zum Thema selbst bilden.'
],
corePatterns: corePatterns.length > 0 ? corePatterns : uniquePatterns.slice(0, 5),
corePatterns: corePatterns.length > 0
? corePatterns
: uniquePatterns.slice(0, 5).map((s) => ({ target: String(s || '').trim(), gloss: '' })).filter((p) => p.target),
grammarFocus: grammarFocus.length > 0 ? grammarFocus : uniqueGrammarExplanations.slice(0, 4),
speakingPrompts: explicitSpeakingPrompts.length > 0 ? explicitSpeakingPrompts : speakingPrompts.slice(0, 4),
practicalTasks: practicalTasks.length > 0
@@ -1775,7 +1828,7 @@ export default class VocabService {
audioUrl: audioUrl || null,
culturalNotes: culturalNotes || null,
learningGoals: this._normalizeStringList(learningGoals),
corePatterns: this._normalizeStringList(corePatterns),
corePatterns: this._normalizeCorePatternList(corePatterns),
grammarFocus: this._normalizeStructuredList(grammarFocus, ['title', 'text', 'example']),
speakingPrompts: this._normalizeStructuredList(speakingPrompts, ['title', 'prompt', 'cue']),
practicalTasks: this._normalizeStructuredList(practicalTasks, ['title', 'text']),
@@ -1822,7 +1875,7 @@ export default class VocabService {
if (audioUrl !== undefined) updates.audioUrl = audioUrl;
if (culturalNotes !== undefined) updates.culturalNotes = culturalNotes;
if (learningGoals !== undefined) updates.learningGoals = this._normalizeStringList(learningGoals);
if (corePatterns !== undefined) updates.corePatterns = this._normalizeStringList(corePatterns);
if (corePatterns !== undefined) updates.corePatterns = this._normalizeCorePatternList(corePatterns);
if (grammarFocus !== undefined) updates.grammarFocus = this._normalizeStructuredList(grammarFocus, ['title', 'text', 'example']);
if (speakingPrompts !== undefined) updates.speakingPrompts = this._normalizeStructuredList(speakingPrompts, ['title', 'prompt', 'cue']);
if (practicalTasks !== undefined) updates.practicalTasks = this._normalizeStructuredList(practicalTasks, ['title', 'text']);