feat(bisaya-course): refine phase 4 didactics and enhance course content generation
All checks were successful
Deploy to production / deploy (push) Successful in 5m19s
All checks were successful
Deploy to production / deploy (push) Successful in 5m19s
- Corrected grammatical errors and improved the phrasing in the BISAYA_PHASE4_DIDACTICS, ensuring clarity and accuracy in the learning materials. - Updated the course content generation script to include lessons from phase 5, enhancing the overall structure and flow of the course. - Introduced a new vocabulary course content synchronization process, improving the integration of vocabulary resources across different modules. - Enhanced the VocabService to dynamically adjust temperature settings based on the mode, optimizing response generation for different contexts. - Added new localized titles and vocabulary entries in multiple languages, enriching the learning experience for users.
This commit is contained in:
@@ -31,8 +31,8 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
],
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Routine in 5 Saetzen',
|
||||
text: 'Sprich eine Routine mit fuenf kurzen Saetzen (Essen, Tasche, Haende, Schule, Losgehen).'
|
||||
title: 'Routine in 5 Sätzen',
|
||||
text: 'Sprich eine Routine mit fünf kurzen Sätzen (Essen, Tasche, Hände, Schule, Losgehen).'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -79,7 +79,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
{ target: 'Kinsa ni?', gloss: 'Wer ist das?' },
|
||||
{ target: 'Asa imong bag?', gloss: 'Wo ist deine Tasche?' },
|
||||
{ target: 'Andam na ka?', gloss: 'Bist du fertig?' },
|
||||
{ target: 'Ganahan ka?', gloss: 'Moechtest du?' },
|
||||
{ target: 'Ganahan ka?', gloss: 'Möchtest du?' },
|
||||
{ target: 'Ali diri.', gloss: 'Komm her.' },
|
||||
{ target: 'Ayaw.', gloss: 'Nicht (tu das nicht).' }
|
||||
],
|
||||
@@ -100,7 +100,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Frage oder Aufforderung',
|
||||
text: 'Forme drei deutsche Saetze einmal als Frage und einmal als kurze Aufforderung auf Bisaya.'
|
||||
text: 'Forme drei deutsche Sätze einmal als Frage und einmal als kurze Aufforderung auf Bisaya.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -146,7 +146,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
{ target: 'Andam na ka?', gloss: 'Bist du fertig?' },
|
||||
{ target: 'Asa imong bag?', gloss: 'Wo ist deine Tasche?' },
|
||||
{ target: 'Magtuon ta.', gloss: 'Lass uns lernen.' },
|
||||
{ target: 'Hugas sa kamot.', gloss: 'Wasch dir die Haende.' },
|
||||
{ target: 'Hugas sa kamot.', gloss: 'Wasch dir die Hände.' },
|
||||
{ target: 'Pagkahuman, magdula ta.', gloss: 'Danach spielen wir.' },
|
||||
{ target: 'Sunod, matulog na ta.', gloss: 'Danach gehen wir schlafen.' }
|
||||
],
|
||||
@@ -160,7 +160,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
practicalTasks: [
|
||||
{
|
||||
title: '8 Situationen',
|
||||
text: 'Beantworte acht Situationen: Hunger, Tasche, Haende, Hausaufgaben, Spielen, Schlafen.'
|
||||
text: 'Beantworte acht Situationen: Hunger, Tasche, Hände, Hausaufgaben, Spielen, Schlafen.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -177,20 +177,20 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
{ target: 'pamilya', gloss: 'Familie' },
|
||||
{ target: 'eskwela', gloss: 'Schule' },
|
||||
{ target: 'tabang', gloss: 'Hilfe' },
|
||||
{ target: 'Kapoy na ka?', gloss: 'Bist du muede?' },
|
||||
{ target: 'Kapoy na ka?', gloss: 'Bist du müde?' },
|
||||
{ target: 'Ayaw kabalaka.', gloss: 'Mach dir keine Sorgen.' }
|
||||
],
|
||||
speakingPrompts: [
|
||||
{
|
||||
title: 'Familien-Fuersorge',
|
||||
prompt: 'Sprich zwei kurze Fuersorgesetze zu Kind und Schule.',
|
||||
title: 'Familien-Fürsorge',
|
||||
prompt: 'Sprich zwei kurze Fürsorgesätze zu Kind und Schule.',
|
||||
cue: 'Ayaw kabalaka. Andam na ka sa eskwela?'
|
||||
}
|
||||
],
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Spiralabruf',
|
||||
text: 'Nimm vier alte Woerter (Nanay/Tatay/bata/eskwela) und baue vier kurze Saetze.'
|
||||
text: 'Nimm vier alte Wörter (Nanay/Tatay/bata/eskwela) und baue vier kurze Sätze.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -202,8 +202,8 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
],
|
||||
corePatterns: [
|
||||
{ target: 'Magdula ta.', gloss: 'Lass uns spielen.' },
|
||||
{ target: 'Ganahan ka modula?', gloss: 'Moechtest du spielen?' },
|
||||
{ target: 'Lingaw ka?', gloss: 'Hast du Spass?' },
|
||||
{ target: 'Ganahan ka modula?', gloss: 'Möchtest du spielen?' },
|
||||
{ target: 'Lingaw ka?', gloss: 'Hast du Spaß?' },
|
||||
{ target: 'Human na ang duwa.', gloss: 'Das Spiel ist vorbei.' },
|
||||
{ target: 'Pahuway sa.', gloss: 'Mach erst mal Pause.' },
|
||||
{ target: 'Dali na.', gloss: 'Komm, beeil dich.' },
|
||||
@@ -238,19 +238,19 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
{ target: 'sayaw', gloss: 'Tanz' },
|
||||
{ target: 'drawing', gloss: 'Malen/Zeichnen' },
|
||||
{ target: 'Ganahan ka ani?', gloss: 'Magst du das?' },
|
||||
{ target: 'Pilia.', gloss: 'Waehle.' }
|
||||
{ target: 'Pilia.', gloss: 'Wähle.' }
|
||||
],
|
||||
speakingPrompts: [
|
||||
{
|
||||
title: 'Auswahl anbieten',
|
||||
prompt: 'Biete zwei Aktivitaeten an und lasse das Kind waehlen.',
|
||||
prompt: 'Biete zwei Aktivitäten an und lasse das Kind wählen.',
|
||||
cue: 'Ganahan ka og kanta o sayaw? Pilia.'
|
||||
}
|
||||
],
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Wortschatz aktiv',
|
||||
text: 'Nenne sechs Spielsachen/Aktivitaeten und bilde zwei kurze Fragen dazu.'
|
||||
text: 'Nenne sechs Spielsachen/Aktivitäten und bilde zwei kurze Fragen dazu.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -266,7 +266,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
{ target: 'Magdula ta.', gloss: 'Lass uns spielen.' },
|
||||
{ target: 'Human na ka?', gloss: 'Bist du fertig?' },
|
||||
{ target: 'Naa kay assignment?', gloss: 'Hast du Hausaufgaben?' },
|
||||
{ target: 'Hugas sa kamot.', gloss: 'Wasch dir die Haende.' },
|
||||
{ target: 'Hugas sa kamot.', gloss: 'Wasch dir die Hände.' },
|
||||
{ target: 'Ayaw pagdali.', gloss: 'Kein Stress.' },
|
||||
{ target: 'Sunod, matulog na ta.', gloss: 'Danach schlafen wir.' }
|
||||
],
|
||||
@@ -280,7 +280,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Tempo',
|
||||
text: 'Sag acht Saetze laut und schnell (aber klar), ohne zu stolpern.'
|
||||
text: 'Sag acht Sätze laut und schnell (aber klar), ohne zu stolpern.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -303,14 +303,14 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
speakingPrompts: [
|
||||
{
|
||||
title: 'Checkpoint-Szene',
|
||||
prompt: 'Loese eine kurze Szene: Schule, Tasche, Hausaufgabe, Routine.',
|
||||
prompt: 'Löse eine kurze Szene: Schule, Tasche, Hausaufgabe, Routine.',
|
||||
cue: 'Asa imong bag? Andam na ka sa eskwela? Naa kay assignment? Sunod, matulog na ta.'
|
||||
}
|
||||
],
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Diagnose',
|
||||
text: 'Markiere die Saetze, die nicht sofort kamen, und wiederhole sie dreimal im Typing.'
|
||||
text: 'Markiere die Sätze, die nicht sofort kamen, und wiederhole sie dreimal im Typing.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -324,7 +324,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
{ target: 'Adto ta sa doktor.', gloss: 'Wir gehen zum Arzt.' },
|
||||
{ target: 'Naa mi appointment.', gloss: 'Wir haben einen Termin.' },
|
||||
{ target: 'Naa moy appointment?', gloss: 'Haben Sie einen Termin?' },
|
||||
{ target: 'Unsay gibati nimo?', gloss: 'Was fuehlst du / was hast du?' },
|
||||
{ target: 'Unsay gibati nimo?', gloss: 'Was fühlst du / was hast du?' },
|
||||
{ target: 'Sakit diri.', gloss: 'Es tut hier weh.' },
|
||||
{ target: 'Pila ka oras ang hulat?', gloss: 'Wie lange ist die Wartezeit?' },
|
||||
{ target: 'Maghulat ta.', gloss: 'Wir warten.' },
|
||||
@@ -340,7 +340,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
practicalTasks: [
|
||||
{
|
||||
title: '3 Fragen',
|
||||
text: 'Uebe drei Fragen fuer den Arztbesuch: Termin, Beschwerden, Wartezeit.'
|
||||
text: 'Übe drei Fragen für den Arztbesuch: Termin, Beschwerden, Wartezeit.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -357,7 +357,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
{ target: 'dose', gloss: 'Dosis' },
|
||||
{ target: 'Pila ang dose?', gloss: 'Wie ist die Dosierung?' },
|
||||
{ target: 'Pila ka adlaw?', gloss: 'Wie viele Tage?' },
|
||||
{ target: 'Naa moy tambal ani?', gloss: 'Haben Sie Medizin dafuer?' },
|
||||
{ target: 'Naa moy tambal ani?', gloss: 'Haben Sie Medizin dafür?' },
|
||||
{ target: 'Unsaon pag-inom?', gloss: 'Wie nimmt man das ein?' }
|
||||
],
|
||||
speakingPrompts: [
|
||||
@@ -437,7 +437,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Notfallanker',
|
||||
text: 'Uebe vier Notfallsaetze, bis sie ohne Lesen kommen.'
|
||||
text: 'Übe vier Notfallsätze, bis sie ohne Lesen kommen.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -450,7 +450,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
corePatterns: [
|
||||
{ target: 'Adto ta sa doktor.', gloss: 'Wir gehen zum Arzt.' },
|
||||
{ target: 'Naa mi appointment.', gloss: 'Wir haben einen Termin.' },
|
||||
{ target: 'Naa moy tambal ani?', gloss: 'Haben Sie Medizin dafuer?' },
|
||||
{ target: 'Naa moy tambal ani?', gloss: 'Haben Sie Medizin dafür?' },
|
||||
{ target: 'Unsaon pag-inom?', gloss: 'Wie nimmt man das ein?' },
|
||||
{ target: 'Sakit kaayo.', gloss: 'Es tut sehr weh.' },
|
||||
{ target: 'Nagsugod ganiha.', gloss: 'Es fing vorhin an.' },
|
||||
@@ -467,7 +467,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Tempo-Mix',
|
||||
text: 'Sag acht Saetze schnell hintereinander, ohne zu stolpern.'
|
||||
text: 'Sag acht Sätze schnell hintereinander, ohne zu stolpern.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -489,15 +489,15 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
],
|
||||
speakingPrompts: [
|
||||
{
|
||||
title: 'Fuersorgefolge',
|
||||
prompt: 'Reagiere mit drei Fuersorgesatzen auf eine Beschwerde.',
|
||||
title: 'Fürsorgefolge',
|
||||
prompt: 'Reagiere mit drei Fürsorgesätzen auf eine Beschwerde.',
|
||||
cue: 'Ayaw kabalaka. Magpahuway sa. Uminom og tubig.'
|
||||
}
|
||||
],
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Problem -> Antwort',
|
||||
text: 'Bilde fuenf Paare aus Problem und Antwort (z.B. sakit -> magpahuway).'
|
||||
text: 'Bilde fünf Paare aus Problem und Antwort (z.B. sakit -> magpahuway).'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -519,7 +519,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
],
|
||||
speakingPrompts: [
|
||||
{
|
||||
title: 'Pflegegespraech',
|
||||
title: 'Pflegegespräch',
|
||||
prompt: 'Biete Essen, Wasser, Ruhe an und frage nach Besserung.',
|
||||
cue: 'Mokaon sa ka. Uminom og tubig. Magpahuway sa. Mas maayo na ka?'
|
||||
}
|
||||
@@ -527,7 +527,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Pflegekette',
|
||||
text: 'Sprich eine Pflegekette mit mindestens fuenf kurzen Saetzen.'
|
||||
text: 'Sprich eine Pflegekette mit mindestens fünf kurzen Sätzen.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -540,7 +540,7 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
corePatterns: [
|
||||
{ target: 'ulo', gloss: 'Kopf' },
|
||||
{ target: 'tiyan', gloss: 'Bauch' },
|
||||
{ target: 'likod', gloss: 'Ruecken' },
|
||||
{ target: 'likod', gloss: 'Rücken' },
|
||||
{ target: 'tutunlan', gloss: 'Hals' },
|
||||
{ target: 'hilanat', gloss: 'Fieber' },
|
||||
{ target: 'ubo', gloss: 'Husten' },
|
||||
@@ -550,15 +550,15 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
],
|
||||
speakingPrompts: [
|
||||
{
|
||||
title: 'Koerper + Schmerz',
|
||||
prompt: 'Sage drei Saetze: wo es weh tut und was du brauchst.',
|
||||
title: 'Körper + Schmerz',
|
||||
prompt: 'Sage drei Sätze: wo es weh tut und was du brauchst.',
|
||||
cue: 'Sakit akong ulo. Sakit akong tiyan. Asa ang tambal?'
|
||||
}
|
||||
],
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Koerperrunde',
|
||||
text: 'Nenne vier Koerperteile und bilde zu jedem einen kurzen Satz mit sakit.'
|
||||
title: 'Körperrunde',
|
||||
text: 'Nenne vier Körperteile und bilde zu jedem einen kurzen Satz mit sakit.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -611,14 +611,14 @@ export const BISAYA_PHASE4_DIDACTICS = {
|
||||
speakingPrompts: [
|
||||
{
|
||||
title: 'Checkpoint-Szene',
|
||||
prompt: 'Loese eine Szene: Termin, Beschwerden, Apotheke, Notfall.',
|
||||
prompt: 'Löse eine Szene: Termin, Beschwerden, Apotheke, Notfall.',
|
||||
cue: 'Naa mi appointment. Sakit kaayo. Naa moy tambal ani? Tawag ug doktor.'
|
||||
}
|
||||
],
|
||||
practicalTasks: [
|
||||
{
|
||||
title: 'Diagnose',
|
||||
text: 'Markiere die unsicheren Saetze und wiederhole sie im Typing bis sie sofort kommen.'
|
||||
text: 'Markiere die unsicheren Sätze und wiederhole sie im Typing bis sie sofort kommen.'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -980,17 +980,17 @@ export const BISAYA_PHASE4_LESSONS = [
|
||||
{ week: 7, day: 3, num: 68, type: 'review', title: 'Woche 7 - Intensivwiederholung I', desc: 'Kinder, Schule und Familienroutine intensiv wiederholen (Abruf + Rollenwechsel)', targetMin: 34, targetScore: 82, review: false, cultural: null },
|
||||
{ week: 7, day: 3, num: 69, type: 'vocab', title: 'Spiralwiederholung - Familie, Kinder & Fürsorge', desc: 'Frühe Kernmuster in Kinder-/Schulszenen reaktivieren', targetMin: 24, targetScore: 85, review: true, cultural: null },
|
||||
{ week: 7, day: 4, num: 70, type: 'conversation', title: 'Spielen & Freizeit', desc: 'Spiel, Pause, Regeln und ruhige Korrektur im Kinderalltag sprechen', targetMin: 24, targetScore: 80, review: false, cultural: null },
|
||||
{ week: 7, day: 4, num: 71, type: 'vocab', title: 'Spielsachen & Aktivitäten', desc: 'Spiel- und Freizeitwortschatz aktiv nutzen (Auswahl, Fragen, kurze Saetze)', targetMin: 24, targetScore: 85, review: true, cultural: null },
|
||||
{ week: 7, day: 4, num: 71, type: 'vocab', title: 'Spielsachen & Aktivitäten', desc: 'Spiel- und Freizeitwortschatz aktiv nutzen (Auswahl, Fragen, kurze Sätze)', targetMin: 24, targetScore: 85, review: true, cultural: null },
|
||||
{ week: 7, day: 5, num: 72, type: 'review', title: 'Woche 7 - Intensivwiederholung II', desc: 'Große Mischwiederholung zur Kinder- und Schulwoche (Szene bauen)', targetMin: 34, targetScore: 82, review: false, cultural: null },
|
||||
{ week: 7, day: 5, num: 73, type: 'vocab', title: 'Woche 7 - Checkpoint', desc: 'Checkpoint zu Kindern, Schule, Hausaufgaben und Routine (Diagnose + freie Szene)', targetMin: 24, targetScore: 84, review: true, cultural: null },
|
||||
{ week: 8, day: 1, num: 74, type: 'conversation', title: 'Arzt & Termin', desc: 'Arzttermine, Beschwerden und Wartezeit als Mini-Dialog organisieren', targetMin: 26, targetScore: 80, review: false, cultural: null },
|
||||
{ week: 8, day: 1, num: 75, type: 'vocab', title: 'Apotheke & Medikamente', desc: 'Apotheke, Rezept, Dosierung und Einnahmefragen aktiv abrufen', targetMin: 24, targetScore: 85, review: true, cultural: null },
|
||||
{ week: 8, day: 2, num: 76, type: 'grammar', title: 'Beschwerden genauer beschreiben', desc: 'Staerke, Verlauf, Zeitpunkte und wiederkehrende Beschwerden ausdruecken', targetMin: 26, targetScore: 78, review: true, cultural: null },
|
||||
{ week: 8, day: 2, num: 76, type: 'grammar', title: 'Beschwerden genauer beschreiben', desc: 'Stärke, Verlauf, Zeitpunkte und wiederkehrende Beschwerden ausdrücken', targetMin: 26, targetScore: 78, review: true, cultural: null },
|
||||
{ week: 8, day: 2, num: 77, type: 'conversation', title: 'Notfälle & Hilfe', desc: 'Hilfe rufen, kurze Anweisungen geben und Notfallanker sicher sprechen', targetMin: 26, targetScore: 80, review: false, cultural: null },
|
||||
{ week: 8, day: 3, num: 78, type: 'review', title: 'Woche 8 - Intensivwiederholung I', desc: 'Arzt, Apotheke, Beschwerden und Hilfe intensiv wiederholen (Tempo + Abruf)', targetMin: 34, targetScore: 82, review: false, cultural: null },
|
||||
{ week: 8, day: 3, num: 79, type: 'vocab', title: 'Spiralwiederholung - Gesundheit', desc: 'Fürsorge und Gesundheit als Langzeitabruf (Problem -> Antwort)', targetMin: 24, targetScore: 85, review: true, cultural: null },
|
||||
{ week: 8, day: 4, num: 80, type: 'conversation', title: 'Essen, Ruhe & Genesung', desc: 'Pflegegespraeche: Essen, Wasser, Ruhe, Medizin und Besserung verbinden', targetMin: 24, targetScore: 80, review: false, cultural: null },
|
||||
{ week: 8, day: 4, num: 81, type: 'vocab', title: 'Körper, Symptome & Pflege', desc: 'Koerper, Symptome und Pflegewortschatz mit kurzen Schmerzsaetzen kombinieren', targetMin: 24, targetScore: 85, review: true, cultural: null },
|
||||
{ week: 8, day: 4, num: 80, type: 'conversation', title: 'Essen, Ruhe & Genesung', desc: 'Pflegegespräche: Essen, Wasser, Ruhe, Medizin und Besserung verbinden', targetMin: 24, targetScore: 80, review: false, cultural: null },
|
||||
{ week: 8, day: 4, num: 81, type: 'vocab', title: 'Körper, Symptome & Pflege', desc: 'Körper, Symptome und Pflegewortschatz mit kurzen Schmerzsätzen kombinieren', targetMin: 24, targetScore: 85, review: true, cultural: null },
|
||||
{ week: 8, day: 5, num: 82, type: 'review', title: 'Woche 8 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Gesundheit und Hilfe (Kontraste + freie Antworten)', targetMin: 34, targetScore: 82, review: false, cultural: null },
|
||||
{ week: 8, day: 5, num: 83, type: 'vocab', title: 'Woche 8 - Checkpoint', desc: 'Checkpoint zu Arzt, Apotheke, Beschwerden und Notfall (Diagnose + Typing)', targetMin: 24, targetScore: 84, review: true, cultural: null },
|
||||
{ week: 9, day: 1, num: 84, type: 'conversation', title: 'Einkaufen vertiefen', desc: 'Komplexere Einkaufs- und Auswahlgespräche führen', targetMin: 18, targetScore: 80, review: false, cultural: null },
|
||||
|
||||
@@ -16,7 +16,7 @@ import User from '../models/community/user.js';
|
||||
import { BISAYA_DIDACTICS_24_43, BISAYA_RELATIONSHIP_ANCHOR_DIDACTICS } from './bisaya-course-plan-24-43.js';
|
||||
import { BISAYA_PHASE3_DIDACTICS, BISAYA_PHASE3_LESSONS } from './bisaya-course-phase3-extension.js';
|
||||
import { BISAYA_PHASE4_DIDACTICS, BISAYA_PHASE4_LESSONS } from './bisaya-course-phase4-extension.js';
|
||||
import { BISAYA_PHASE5_DIDACTICS } from './bisaya-course-phase5-extension.js';
|
||||
import { BISAYA_PHASE5_DIDACTICS, BISAYA_PHASE5_LESSONS } from './bisaya-course-phase5-extension.js';
|
||||
|
||||
function withTypeName(exerciseTypeName, exercise) {
|
||||
return {
|
||||
@@ -35,7 +35,8 @@ const GENERATED_BISAYA_DIDACTICS = {
|
||||
|
||||
const SAFE_EXERCISE_UPDATE_TITLES = new Set([
|
||||
...BISAYA_PHASE3_LESSONS.map((lesson) => lesson.title),
|
||||
...BISAYA_PHASE4_LESSONS.map((lesson) => lesson.title)
|
||||
...BISAYA_PHASE4_LESSONS.map((lesson) => lesson.title),
|
||||
...BISAYA_PHASE5_LESSONS.map((lesson) => lesson.title)
|
||||
]);
|
||||
|
||||
function normalizeText(value) {
|
||||
|
||||
@@ -31,6 +31,7 @@ const SAFE_SYNC_STEPS = {
|
||||
'backend/scripts/create-bisaya-course-content.js'
|
||||
],
|
||||
'german-for-bisaya': [
|
||||
'backend/scripts/create-german-for-bisaya-course.js',
|
||||
'backend/scripts/extend-german-for-bisaya-course-phase3.js',
|
||||
'backend/scripts/extend-german-for-bisaya-course-phase4.js',
|
||||
'backend/scripts/extend-german-for-bisaya-course-phase5.js',
|
||||
|
||||
@@ -2798,6 +2798,13 @@ export default class VocabService {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 30000);
|
||||
|
||||
const temperatureByMode = {
|
||||
explain: 0.4,
|
||||
practice: 0.5,
|
||||
correct: 0.1
|
||||
};
|
||||
const temperature = Number.isFinite(temperatureByMode[mode]) ? temperatureByMode[mode] : 0.5;
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(endpoint, {
|
||||
@@ -2806,7 +2813,7 @@ export default class VocabService {
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
temperature: 0.7,
|
||||
temperature,
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -24,18 +24,18 @@ ollama --version
|
||||
|
||||
## 2) Modell laden
|
||||
|
||||
Empfohlen fuer freien Schreib- und Korrekturmodus:
|
||||
|
||||
```bash
|
||||
ollama pull qwen2.5:7b-instruct
|
||||
```
|
||||
|
||||
Optional kleinere Alternative (weniger RAM):
|
||||
Empfohlen fuer freien Schreib- und Korrekturmodus (CPU-freundlich):
|
||||
|
||||
```bash
|
||||
ollama pull qwen2.5:3b-instruct
|
||||
```
|
||||
|
||||
Optional groessere Alternative (bessere Qualitaet, aber langsamer auf CPU):
|
||||
|
||||
```bash
|
||||
ollama pull qwen2.5:7b-instruct
|
||||
```
|
||||
|
||||
## 3) Ollama-Server starten
|
||||
|
||||
```bash
|
||||
@@ -55,7 +55,7 @@ Der Server laeuft dann standardmaessig auf:
|
||||
Der Preset setzt:
|
||||
|
||||
- Base URL: `http://127.0.0.1:11434/v1`
|
||||
- Modell: `qwen2.5:7b-instruct`
|
||||
- Modell: `qwen2.5:3b-instruct`
|
||||
- API-Key: nicht erforderlich
|
||||
|
||||
## 5) Funktionstest
|
||||
@@ -79,12 +79,12 @@ Wenn die Antwort kommt, ist alles korrekt verbunden.
|
||||
- Modell erneut laden:
|
||||
|
||||
```bash
|
||||
ollama pull qwen2.5:7b-instruct
|
||||
ollama pull qwen2.5:3b-instruct
|
||||
```
|
||||
|
||||
### Antwort langsam
|
||||
### Antwort langsam (haeufig bei CPU-only Servern)
|
||||
|
||||
- Kleineres Modell nutzen (`qwen2.5:3b-instruct`)
|
||||
- `qwen2.5:3b-instruct` als Standard nutzen
|
||||
- Andere GPU/CPU-Auslastung reduzieren
|
||||
|
||||
## Hinweise fuer A2-Ziel
|
||||
|
||||
@@ -63,8 +63,8 @@ export default {
|
||||
falukantDisplayName() {
|
||||
const d = this.falukantData;
|
||||
if (!d) return this.$t('widgets.falukant.emptyValue');
|
||||
const titleKey = d.titleLabelTr;
|
||||
const gender = d.gender;
|
||||
const gender = this._normalizeGenderKey(d.gender);
|
||||
const titleKey = this._normalizeTitleKey(d.titleLabelTr, gender);
|
||||
const nameWithoutTitle = d.nameWithoutTitle ?? d.characterName;
|
||||
if (titleKey && gender) {
|
||||
const key = `falukant.titles.${gender}.${titleKey}`;
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
return d.characterName || nameWithoutTitle || this.$t('widgets.falukant.emptyValue');
|
||||
},
|
||||
falukantGenderLabel() {
|
||||
const g = this.falukantData?.gender;
|
||||
const g = this._normalizeGenderKey(this.falukantData?.gender);
|
||||
if (g == null || g === '') return this.$t('widgets.falukant.emptyValue');
|
||||
|
||||
// Altersabhängige, (auf Wunsch) altertümlichere Bezeichnungen
|
||||
@@ -143,6 +143,45 @@ export default {
|
||||
// Fallback, falls Konfig kaputt ist
|
||||
return 'adult';
|
||||
},
|
||||
_normalizeGenderKey(rawGender) {
|
||||
const g = String(rawGender || '').trim();
|
||||
if (!g) return '';
|
||||
const lower = g.toLowerCase();
|
||||
const direct = new Set(['male', 'female', 'transmale', 'transfemale', 'nonbinary']);
|
||||
if (direct.has(lower)) return lower;
|
||||
// Legacy/locale labels that can appear from backend data drifts.
|
||||
if (['mann', 'männlich', 'lalaki'].includes(lower)) return 'male';
|
||||
if (['frau', 'weiblich', 'babaye'].includes(lower)) return 'female';
|
||||
if (['trans-mann', 'transmann'].includes(lower)) return 'transmale';
|
||||
if (['trans-frau', 'transfrau'].includes(lower)) return 'transfemale';
|
||||
if (['non-binary', 'nichtbinär', 'dili binaryo'].includes(lower)) return 'nonbinary';
|
||||
return lower;
|
||||
},
|
||||
_normalizeTitleKey(rawTitle, genderKey) {
|
||||
const title = String(rawTitle || '').trim();
|
||||
if (!title) return '';
|
||||
const known = new Set([
|
||||
'noncivil', 'civil', 'sir', 'townlord', 'by', 'landlord',
|
||||
'knight', 'baron', 'count', 'palsgrave', 'margrave', 'landgrave',
|
||||
'ruler', 'elector', 'imperial-prince', 'duke', 'grand-duke',
|
||||
'prince-regent', 'king'
|
||||
]);
|
||||
if (known.has(title)) return title;
|
||||
|
||||
const candidates = [];
|
||||
const locale = this.$i18n?.locale;
|
||||
if (locale) candidates.push(locale);
|
||||
if (!candidates.includes('de')) candidates.push('de');
|
||||
if (!candidates.includes('en')) candidates.push('en');
|
||||
|
||||
for (const loc of candidates) {
|
||||
const entries = this.$i18n?.messages?.[loc]?.falukant?.titles?.[genderKey];
|
||||
if (!entries || typeof entries !== 'object') continue;
|
||||
const found = Object.entries(entries).find(([, value]) => String(value || '').trim().toLowerCase() === title.toLowerCase());
|
||||
if (found?.[0]) return found[0];
|
||||
}
|
||||
return title;
|
||||
},
|
||||
formatMoney(value) {
|
||||
const n = Number(value);
|
||||
if (Number.isNaN(n)) return this.$t('widgets.falukant.emptyValue');
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<ul v-else class="vocab-courses-widget__list">
|
||||
<li v-for="c in courses" :key="c.courseId" class="vocab-courses-widget__item">
|
||||
<div class="vocab-courses-widget__main">
|
||||
<strong class="vocab-courses-widget__course-title">{{ c.title || $t('widgets.vocabCourses.unnamedCourse') }}</strong>
|
||||
<strong class="vocab-courses-widget__course-title">{{ displayCourseTitle(c) }}</strong>
|
||||
<span v-if="c.currentLesson" class="vocab-courses-widget__lesson">
|
||||
{{ $t('widgets.vocabCourses.lessonLine', { number: c.currentLesson.lessonNumber, title: c.currentLesson.title }) }}
|
||||
</span>
|
||||
@@ -42,6 +42,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { localizeVocabCourseTitle } from '@/utils/vocabCourseTitle.js';
|
||||
|
||||
export default {
|
||||
name: 'VocabCoursesWidget',
|
||||
props: {
|
||||
@@ -58,6 +60,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
displayCourseTitle(course) {
|
||||
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || this.$t('widgets.vocabCourses.unnamedCourse');
|
||||
},
|
||||
goToLesson(courseId, lessonId) {
|
||||
this.$router.push({
|
||||
name: 'VocabLesson',
|
||||
|
||||
@@ -202,6 +202,18 @@
|
||||
"stockHint": "Mubo nga tan-aw sa mga baligya ug stock sa tanang rehiyon.",
|
||||
"open": "Ablihi"
|
||||
},
|
||||
"productions": {
|
||||
"title": "Mga produksyon"
|
||||
},
|
||||
"stock": {
|
||||
"title": "Bodega"
|
||||
},
|
||||
"branches": {
|
||||
"title": "Mga branch",
|
||||
"level": {
|
||||
"city": "Siyudad"
|
||||
}
|
||||
},
|
||||
"routine": {
|
||||
"branch": {
|
||||
"kicker": "Rutina",
|
||||
@@ -1262,6 +1274,84 @@
|
||||
"partial_success": "Partial success",
|
||||
"major_success": "Major success"
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"male": {
|
||||
"noncivil": "Ubos nga lumulupyo",
|
||||
"civil": "Luwas nga lumulupyo",
|
||||
"sir": "Ginoo",
|
||||
"townlord": "Pangulo sa lungsod",
|
||||
"by": "sa",
|
||||
"landlord": "Tag-iya sa yuta",
|
||||
"knight": "Kabalyero",
|
||||
"baron": "Baron",
|
||||
"count": "Konde",
|
||||
"palsgrave": "Palatine nga Konde",
|
||||
"margrave": "Markgraf",
|
||||
"landgrave": "Landgraf",
|
||||
"ruler": "Prinsipe",
|
||||
"elector": "Elektor",
|
||||
"imperial-prince": "Prinsipe sa imperyo",
|
||||
"duke": "Duke",
|
||||
"grand-duke": "Dakong Duke",
|
||||
"prince-regent": "Prinsipe-Regente",
|
||||
"king": "Hari"
|
||||
},
|
||||
"female": {
|
||||
"noncivil": "Ubos nga lumulupyo",
|
||||
"civil": "Luwas nga lumulupyo",
|
||||
"sir": "Ginang",
|
||||
"townlord": "Pangulo sa lungsod",
|
||||
"by": "sa",
|
||||
"landlord": "Tag-iya sa yuta",
|
||||
"knight": "Kabalyera",
|
||||
"baron": "Baronesa",
|
||||
"count": "Kondesa",
|
||||
"palsgrave": "Palatine nga Kondesa",
|
||||
"margrave": "Margrabin",
|
||||
"landgrave": "Landgrabin",
|
||||
"ruler": "Prinsesa",
|
||||
"elector": "Elektora",
|
||||
"imperial-prince": "Prinsesa sa imperyo",
|
||||
"duke": "Dukesa",
|
||||
"grand-duke": "Dakong Dukesa",
|
||||
"prince-regent": "Prinsesa-Regente",
|
||||
"king": "Rayna"
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"wheat": "Trigo",
|
||||
"grain": "Grano",
|
||||
"carrot": "Karot",
|
||||
"fish": "Isda",
|
||||
"meat": "Karne",
|
||||
"leather": "Panit",
|
||||
"wood": "Kahoy",
|
||||
"stone": "Bato",
|
||||
"milk": "Gatas",
|
||||
"cheese": "Keso",
|
||||
"bread": "Pan",
|
||||
"beer": "Serbesa",
|
||||
"iron": "Puthaw",
|
||||
"copper": "Tumbaga",
|
||||
"spices": "Panakot",
|
||||
"salt": "Asin",
|
||||
"sugar": "Asukal",
|
||||
"vinegar": "Suka",
|
||||
"cotton": "Gapas",
|
||||
"wine": "Bino",
|
||||
"gold": "Bulawan",
|
||||
"diamond": "Diamante",
|
||||
"furniture": "Muwebles",
|
||||
"clothing": "Sinina",
|
||||
"jewelry": "Alahas",
|
||||
"painting": "Pintura",
|
||||
"book": "Libro",
|
||||
"weapon": "Hinagiban",
|
||||
"armor": "Armadura",
|
||||
"shield": "Taming",
|
||||
"horse": "Kabayo",
|
||||
"ox": "Baka"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,6 +510,10 @@
|
||||
"lessonReviewHeadlineScheduled": "Gitakda kini nga leksiyon para sa sunod nga review wave.",
|
||||
"lessonReviewHintDone": "Nahuman na ang 1/3/7 ka adlaw nga balik-balik. Mahimo na nimo kining praktison sa mas luag nga paagi.",
|
||||
"lessonReviewHintNextDue": "Sunod nga petsa: {due}.",
|
||||
"dailyEnoughTitle": "Para karon, igo na gyud ni.",
|
||||
"dailyEnoughBody": "Naabot na nimo ang girekomenda nga target para niining leksiyona. Kung gusto gyud ka, pwede ra ka mopadayon:",
|
||||
"dailyEnoughStop": "Undang sa karon",
|
||||
"dailyEnoughContinue": "Padayon gihapon ug praktis",
|
||||
"reviewTimeNow": "karon",
|
||||
"reviewTimeTomorrow": "ugma",
|
||||
"reviewTimeInDays": "sulod sa {count} ka adlaw",
|
||||
|
||||
@@ -817,6 +817,10 @@
|
||||
"lessonReviewHeadlineScheduled": "Diese Lektion ist für die nächste Review-Welle vorgemerkt.",
|
||||
"lessonReviewHintDone": "Die 1/3/7-Tage-Wiederholung ist abgeschlossen. Du kannst die Lektion jetzt flexibel weitertrainieren.",
|
||||
"lessonReviewHintNextDue": "Nächste Fälligkeit: {due}.",
|
||||
"dailyEnoughTitle": "Für heute ist das eigentlich genug.",
|
||||
"dailyEnoughBody": "Du hast die empfohlenen Ziele für diese Lektion erreicht. Wenn du unbedingt möchtest, kannst du natürlich weiter üben:",
|
||||
"dailyEnoughStop": "Für heute beenden",
|
||||
"dailyEnoughContinue": "Trotzdem weiter üben",
|
||||
"reviewTimeNow": "jetzt",
|
||||
"reviewTimeTomorrow": "morgen",
|
||||
"reviewTimeInDays": "in {count} Tagen",
|
||||
|
||||
@@ -817,6 +817,10 @@
|
||||
"lessonReviewHeadlineScheduled": "This lesson is scheduled for the next review wave.",
|
||||
"lessonReviewHintDone": "The 1/3/7-day review cycle is complete. You can now continue practicing this lesson freely.",
|
||||
"lessonReviewHintNextDue": "Next due date: {due}.",
|
||||
"dailyEnoughTitle": "That is basically enough for today.",
|
||||
"dailyEnoughBody": "You have reached the recommended targets for this lesson. If you really want to, you can keep going:",
|
||||
"dailyEnoughStop": "Stop for today",
|
||||
"dailyEnoughContinue": "Keep practicing anyway",
|
||||
"reviewTimeNow": "now",
|
||||
"reviewTimeTomorrow": "tomorrow",
|
||||
"reviewTimeInDays": "in {count} days",
|
||||
|
||||
@@ -806,6 +806,10 @@
|
||||
"lessonReviewHeadlineScheduled": "Esta lección está prevista para la siguiente ola de repaso.",
|
||||
"lessonReviewHintDone": "El ciclo de repaso de 1/3/7 días está completado. Ahora puedes seguir practicando esta lección libremente.",
|
||||
"lessonReviewHintNextDue": "Próximo vencimiento: {due}.",
|
||||
"dailyEnoughTitle": "En principio, por hoy ya es suficiente.",
|
||||
"dailyEnoughBody": "Has alcanzado los objetivos recomendados para esta lección. Si de verdad quieres, puedes seguir:",
|
||||
"dailyEnoughStop": "Parar por hoy",
|
||||
"dailyEnoughContinue": "Seguir practicando de todos modos",
|
||||
"reviewTimeNow": "ahora",
|
||||
"reviewTimeTomorrow": "mañana",
|
||||
"reviewTimeInDays": "en {count} días"
|
||||
|
||||
@@ -806,6 +806,10 @@
|
||||
"lessonReviewHeadlineScheduled": "Cette leçon est réservée à la prochaine vague de critiques.",
|
||||
"lessonReviewHintDone": "La répétition 1/3/7 jour est terminée. Vous pouvez désormais continuer à entraîner la leçon de manière flexible.",
|
||||
"lessonReviewHintNextDue": "Prochaine date d'échéance : {due}.",
|
||||
"dailyEnoughTitle": "Pour aujourd'hui, c'est en principe suffisant.",
|
||||
"dailyEnoughBody": "Vous avez atteint les objectifs recommandés pour cette leçon. Si vous le souhaitez vraiment, vous pouvez continuer :",
|
||||
"dailyEnoughStop": "Arrêter pour aujourd'hui",
|
||||
"dailyEnoughContinue": "Continuer quand même",
|
||||
"reviewTimeNow": "maintenant",
|
||||
"reviewTimeTomorrow": "matin",
|
||||
"reviewTimeInDays": "dans {count} jours"
|
||||
|
||||
17
frontend/src/utils/vocabCourseTitle.js
Normal file
17
frontend/src/utils/vocabCourseTitle.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const COURSE_TITLE_TRANSLATIONS = {
|
||||
'Deutsch für Bisaya-Lernende - Alltag & Stabilisierung': {
|
||||
ceb: 'Aleman para sa mga Bisaya nga Tigkat-on - Adlaw-adlaw ug Pagpalig-on'
|
||||
},
|
||||
'Bisaya für Familien - Alltag & Stabilisierung': {
|
||||
ceb: 'Bisaya para sa mga Pamilya - Adlaw-adlaw ug Pagpalig-on'
|
||||
}
|
||||
};
|
||||
|
||||
export function localizeVocabCourseTitle(rawTitle, locale) {
|
||||
const title = String(rawTitle || '').trim();
|
||||
if (!title) return '';
|
||||
const normalizedLocale = String(locale || 'de').toLowerCase();
|
||||
const entry = COURSE_TITLE_TRANSLATIONS[title];
|
||||
if (!entry) return title;
|
||||
return entry[normalizedLocale] || title;
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<p class="language-assistant-settings__preset-hint">
|
||||
Der Ollama-Preset setzt <code>http://127.0.0.1:11434/v1</code> und
|
||||
<code>qwen2.5:7b-instruct</code>. Kein API-Key erforderlich.
|
||||
<code>qwen2.5:3b-instruct</code>. Kein API-Key erforderlich.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
applyOllamaPreset() {
|
||||
this.form.enabled = true;
|
||||
this.form.baseUrl = 'http://127.0.0.1:11434/v1';
|
||||
this.form.model = 'qwen2.5:7b-instruct';
|
||||
this.form.model = 'qwen2.5:3b-instruct';
|
||||
this.form.apiKey = '';
|
||||
this.form.clearKey = false;
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<div class="course-content">
|
||||
<div class="course-info">
|
||||
<div class="course-title-row">
|
||||
<span class="course-title">{{ course.title }}</span>
|
||||
<span class="course-title">{{ displayCourseTitle(course) }}</span>
|
||||
<span v-if="course.isOwner" class="badge owner">{{ $t('socialnetwork.vocab.courses.owner') }}</span>
|
||||
<span v-else-if="course.enrolledAt" class="badge enrolled">{{ $t('socialnetwork.vocab.courses.enrolled') }}</span>
|
||||
</div>
|
||||
@@ -142,6 +142,7 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { showApiError, showError } from '@/utils/feedback.js';
|
||||
import { localizeVocabCourseTitle } from '@/utils/vocabCourseTitle.js';
|
||||
|
||||
export default {
|
||||
name: 'VocabCourseListView',
|
||||
@@ -172,6 +173,9 @@ export default {
|
||||
...mapGetters(['user', 'language']),
|
||||
},
|
||||
methods: {
|
||||
displayCourseTitle(course) {
|
||||
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale || this.language) || this.$t('widgets.vocabCourses.unnamedCourse');
|
||||
},
|
||||
async loadLanguages() {
|
||||
try {
|
||||
// Verwende /languages/all für die Kursliste, um alle Sprachen anzuzeigen
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<section class="course-hero surface-card">
|
||||
<div>
|
||||
<span class="course-kicker">{{ $t('socialnetwork.vocab.courses.courseKicker') }}</span>
|
||||
<h2>{{ course.title }}</h2>
|
||||
<h2>{{ displayCourseTitle(course) }}</h2>
|
||||
<p v-if="course.description">{{ course.description }}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -323,6 +323,7 @@ import { mapGetters } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { confirmAction, showApiError, showInfo, showSuccess } from '@/utils/feedback.js';
|
||||
import VocabPracticeDialog from '@/dialogues/socialnetwork/VocabPracticeDialog.vue';
|
||||
import { localizeVocabCourseTitle } from '@/utils/vocabCourseTitle.js';
|
||||
|
||||
export default {
|
||||
name: 'VocabCourseView',
|
||||
@@ -517,6 +518,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
displayCourseTitle(course) {
|
||||
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || '';
|
||||
},
|
||||
async loadCourse() {
|
||||
this.loading = true;
|
||||
try {
|
||||
|
||||
@@ -102,6 +102,22 @@
|
||||
<p>{{ $t('socialnetwork.vocab.courses.intensiveReviewIntro') }}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showDailyEnoughBanner"
|
||||
class="lesson-enough-banner"
|
||||
>
|
||||
<strong>{{ $t('socialnetwork.vocab.courses.dailyEnoughTitle') }}</strong>
|
||||
<p>{{ $t('socialnetwork.vocab.courses.dailyEnoughBody') }}</p>
|
||||
<div class="lesson-enough-banner__actions">
|
||||
<button type="button" class="btn-secondary" @click="dismissDailyEnoughBanner">
|
||||
{{ $t('socialnetwork.vocab.courses.dailyEnoughStop') }}
|
||||
</button>
|
||||
<button type="button" class="btn-primary" @click="continueDailyEnoughBanner">
|
||||
{{ $t('socialnetwork.vocab.courses.dailyEnoughContinue') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="lesson-primary-flow surface-card">
|
||||
<div class="lesson-primary-flow__header">
|
||||
<span class="lesson-primary-flow__eyebrow">{{ $t('socialnetwork.vocab.courses.learningPathLabel') }}</span>
|
||||
@@ -1092,6 +1108,7 @@ export default {
|
||||
chapterExamFailedDetails: [],
|
||||
/** Index in scrambledChapterExamExercises bei Ein-Frage-Ansicht */
|
||||
exerciseSequentialIndex: 0,
|
||||
dailyEnoughBannerDismissed: false,
|
||||
/** Aus vorherigen Lektionen (MC-Optionen nach Fragentyp Ziel-/Muttersprache) */
|
||||
distractorPool: { target: [], native: [] },
|
||||
courseLanguageName: '',
|
||||
@@ -1438,12 +1455,21 @@ export default {
|
||||
if (!reference) return;
|
||||
const key = this.normalizeLessonVocabTerm(reference);
|
||||
if (!vocabByReference.has(key)) {
|
||||
vocabByReference.set(key, { learning, reference });
|
||||
const variants = new Set();
|
||||
if (learning) variants.add(learning);
|
||||
vocabByReference.set(key, { learning, reference, learningVariants: variants });
|
||||
return;
|
||||
}
|
||||
const existing = vocabByReference.get(key);
|
||||
if (!existing.learning && learning) {
|
||||
existing.learning = learning;
|
||||
if (learning) {
|
||||
if (!existing.learning) {
|
||||
existing.learning = learning;
|
||||
}
|
||||
if (existing.learningVariants instanceof Set) {
|
||||
existing.learningVariants.add(learning);
|
||||
} else {
|
||||
existing.learningVariants = new Set([existing.learning, learning].filter(Boolean));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1458,7 +1484,18 @@ export default {
|
||||
addEntry(item);
|
||||
});
|
||||
|
||||
return Array.from(vocabByReference.values());
|
||||
return Array.from(vocabByReference.values()).map((entry) => {
|
||||
const variants = entry.learningVariants instanceof Set
|
||||
? Array.from(entry.learningVariants)
|
||||
: (Array.isArray(entry.learningVariants) ? entry.learningVariants : []);
|
||||
const uniqueVariants = [...new Set([entry.learning, ...variants].filter(Boolean))];
|
||||
return {
|
||||
learning: entry.learning,
|
||||
reference: entry.reference,
|
||||
// Für den Trainer: alternative Übersetzungen als gleichwertig akzeptieren.
|
||||
learningVariants: uniqueVariants
|
||||
};
|
||||
});
|
||||
},
|
||||
vocabOverviewTotalPages() {
|
||||
const n = this.lessonVocab.length;
|
||||
@@ -1673,6 +1710,23 @@ export default {
|
||||
due: this.formatLessonReviewDue(progress.reviewNextDueAt)
|
||||
});
|
||||
},
|
||||
showDailyEnoughBanner() {
|
||||
if (this.dailyEnoughBannerDismissed) return false;
|
||||
const progress = this.lessonProgress;
|
||||
// Wenn eine Review-Welle fällig ist, ist "für heute genug" nicht korrekt.
|
||||
if (progress?.completed && progress?.reviewDue) return false;
|
||||
|
||||
if (progress?.completed) return true;
|
||||
|
||||
// Falls der Fortschritt noch nicht gespeichert ist: Übungen sind vollständig + bestanden.
|
||||
if (this.hasExercises && this.effectiveExercises.length > 0) {
|
||||
const allAnswered = this.exerciseAnsweredCount >= this.effectiveExercises.length;
|
||||
if (allAnswered && this.exerciseProgressPercent >= this.exerciseTargetScore) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
assistantAvailable() {
|
||||
if (!this.assistantSettings) {
|
||||
return false;
|
||||
@@ -1748,6 +1802,22 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dismissDailyEnoughBanner() {
|
||||
this.dailyEnoughBannerDismissed = true;
|
||||
},
|
||||
continueDailyEnoughBanner() {
|
||||
this.dailyEnoughBannerDismissed = true;
|
||||
if (!this.vocabTrainerActive && this.trainableLessonVocab.length > 0) {
|
||||
// Startet Trainer nur, wenn die Vorbereitung abgeschlossen ist.
|
||||
if (this.canStartVocabTrainerPrep) {
|
||||
this.startVocabTrainer();
|
||||
} else {
|
||||
this.vocabTrainerActive = true;
|
||||
this.vocabTrainerPool = [...this.trainableLessonVocab];
|
||||
this.$nextTick(() => this.nextVocabQuestion());
|
||||
}
|
||||
}
|
||||
},
|
||||
vocabOverviewGoPrev() {
|
||||
if (this.vocabOverviewPagerMeta.page > 1) {
|
||||
this.vocabOverviewPage -= 1;
|
||||
@@ -2419,6 +2489,22 @@ export default {
|
||||
|
||||
// Nur extrahieren, wenn Muttersprache-Hinweise (Klammern) vorhanden sind
|
||||
if (nativeWords.length > 0) {
|
||||
// Wenn möglich: rekonstruiere die vollständige Phrase um die Lücke herum.
|
||||
// Beispiel: "{gap} ka mubalik? (Bitte wiederholen)" + "Palihug" => "Palihug ka mubalik?"
|
||||
const stripHints = (s) => String(s || '')
|
||||
.replace(/\([^)]*\)/g, '')
|
||||
.replace(/\[[^\]]*\]/g, '')
|
||||
.replace(/([^)]*)/g, '');
|
||||
const stopChars = [',', '.', '|', ';', ':', '\n'];
|
||||
const lastStopIndex = (s) => Math.max(...stopChars.map((c) => s.lastIndexOf(c)));
|
||||
const firstStopIndex = (s) => {
|
||||
const indices = stopChars
|
||||
.map((c) => s.indexOf(c))
|
||||
.filter((idx) => idx >= 0);
|
||||
return indices.length ? Math.min(...indices) : -1;
|
||||
};
|
||||
const parts = String(text).split('{gap}');
|
||||
|
||||
answers.forEach((answer, index) => {
|
||||
if (answer && answer.trim()) {
|
||||
const nativeWord = nativeWords[index];
|
||||
@@ -2429,13 +2515,38 @@ export default {
|
||||
const nativeLooksSentence = /[?.!]/.test(nativeText) || nativeWordCount >= 4;
|
||||
const answerLooksShortToken = answerWordCount <= 2;
|
||||
const likelyFragmentMismatch = nativeLooksSentence && answerLooksShortToken;
|
||||
if (nativeText && answerText && nativeText !== answerText && !likelyFragmentMismatch) {
|
||||
// Die answer ist normalerweise Bisaya, nativeWord ist Muttersprache
|
||||
// Nur hinzufügen, wenn sie unterschiedlich sind (verhindert "ko" -> "ko")
|
||||
vocabMap.set(`${nativeText}-${answerText}`, { learning: nativeText, reference: answerText });
|
||||
debugLog(`[importantVocab] Gap Fill extrahiert - Muttersprache:`, nativeText, `Bisaya:`, answerText);
|
||||
// Prefer full phrase reconstruction when we have matching "{gap}" placeholders.
|
||||
let reconstructed = '';
|
||||
if (parts.length === answers.length + 1) {
|
||||
const leftRaw = stripHints(parts[index] || '');
|
||||
const rightRaw = stripHints(parts[index + 1] || '');
|
||||
const leftCut = lastStopIndex(leftRaw);
|
||||
const leftTail = leftCut >= 0 ? leftRaw.slice(leftCut + 1) : leftRaw;
|
||||
const rightCut = firstStopIndex(rightRaw);
|
||||
const rightHead = rightCut >= 0 ? rightRaw.slice(0, rightCut) : rightRaw;
|
||||
reconstructed = `${leftTail}${answerText}${rightHead}`
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
const reconstructedIsUseful =
|
||||
reconstructed &&
|
||||
reconstructed !== answerText &&
|
||||
(reconstructed.includes(' ') || /[?!]/.test(reconstructed));
|
||||
|
||||
if (nativeText && (reconstructedIsUseful || answerText) && nativeText !== answerText && !likelyFragmentMismatch) {
|
||||
const targetText = reconstructedIsUseful ? reconstructed : answerText;
|
||||
// Sicherheitsfilter: wir wollen primär echte Phrasen, nicht einzelne Tokens als "Vokabelkarte".
|
||||
const targetWordCount = targetText.split(/\s+/).filter(Boolean).length;
|
||||
if (targetWordCount < 2 && !/[?!]/.test(targetText)) {
|
||||
debugLog(`[importantVocab] Gap Fill übersprungen - Ziel wirkt zu kurz:`, nativeText, targetText);
|
||||
return;
|
||||
}
|
||||
// Muttersprache (learning) -> Zielsprache (reference)
|
||||
vocabMap.set(`${nativeText}-${targetText}`, { learning: nativeText, reference: targetText });
|
||||
debugLog(`[importantVocab] Gap Fill extrahiert - Muttersprache:`, nativeText, `Bisaya:`, targetText);
|
||||
} else {
|
||||
debugLog(`[importantVocab] Gap Fill übersprungen - Satz/Fragment-Mismatch oder gleich:`, nativeText, answerText);
|
||||
debugLog(`[importantVocab] Gap Fill übersprungen - Satz/Fragment-Mismatch oder gleich:`, nativeText, answerText, reconstructed);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -2478,6 +2589,7 @@ export default {
|
||||
this.exerciseRetryPending = false;
|
||||
this.exerciseRetryPendingSinceAttempts = 0;
|
||||
this.exerciseSequentialIndex = 0;
|
||||
this.dailyEnoughBannerDismissed = false;
|
||||
this.vocabOverviewPage = 1;
|
||||
this.exercisePreparationCompleted = false;
|
||||
this.lessonPrepStage = 0;
|
||||
@@ -3403,10 +3515,12 @@ export default {
|
||||
const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase();
|
||||
const isReviewLesson = ['review', 'vocab_review'].includes(lessonType)
|
||||
|| ['review', 'vocab_review', 'intensive_review'].includes(didacticMode);
|
||||
// Reviews sollen nicht nur aus Multiple Choice bestehen:
|
||||
// früherer Wechsel zu Typing, damit aktiver Abruf im Vordergrund steht.
|
||||
const switchAfterAttempts = isReviewLesson
|
||||
? Math.max(4, Math.ceil(this.trainerExerciseUnlockAttempts * 0.25))
|
||||
? Math.max(2, Math.ceil(this.trainerExerciseUnlockAttempts * 0.15))
|
||||
: this.trainerExerciseUnlockAttempts;
|
||||
const requiredSuccessRate = isReviewLesson ? 70 : 80;
|
||||
const requiredSuccessRate = isReviewLesson ? 60 : 80;
|
||||
|
||||
if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= switchAfterAttempts) {
|
||||
const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
|
||||
@@ -3617,20 +3731,17 @@ export default {
|
||||
this.vocabTrainerDirection = Math.random() < 0.5 ? 'L2R' : 'R2L';
|
||||
const allTrainerVocabs = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool];
|
||||
const prompt = this.vocabTrainerDirection === 'L2R' ? vocab.learning : vocab.reference;
|
||||
const acceptableAnswers = this.getEquivalentVocabAnswers(
|
||||
prompt,
|
||||
this.vocabTrainerDirection,
|
||||
allTrainerVocabs
|
||||
);
|
||||
// Akzeptiere mehrere Übersetzungen für denselben Prompt (z. B. "Bitte wiederholen" UND "Kannst du das wiederholen?").
|
||||
const acceptableAnswers = this.vocabTrainerDirection === 'L2R'
|
||||
? [vocab.reference]
|
||||
: (Array.isArray(vocab.learningVariants) && vocab.learningVariants.length > 0
|
||||
? vocab.learningVariants
|
||||
: [vocab.learning]);
|
||||
this.currentVocabQuestion = {
|
||||
vocab: vocab,
|
||||
prompt,
|
||||
answers: acceptableAnswers.length > 0
|
||||
? acceptableAnswers
|
||||
: [this.vocabTrainerDirection === 'L2R' ? vocab.reference : vocab.learning],
|
||||
answer: acceptableAnswers.length > 0
|
||||
? acceptableAnswers.join(' / ')
|
||||
: (this.vocabTrainerDirection === 'L2R' ? vocab.reference : vocab.learning),
|
||||
answers: acceptableAnswers.filter(Boolean),
|
||||
answer: acceptableAnswers.filter(Boolean).join(' / ') || (this.vocabTrainerDirection === 'L2R' ? vocab.reference : vocab.learning),
|
||||
key: this.getVocabKey(vocab),
|
||||
source: questionSource
|
||||
};
|
||||
@@ -4149,6 +4260,31 @@ export default {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.lesson-enough-banner {
|
||||
margin-bottom: 18px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(34, 96, 164, 0.18);
|
||||
background: rgba(235, 244, 255, 0.72);
|
||||
color: #21598f;
|
||||
}
|
||||
|
||||
.lesson-enough-banner strong,
|
||||
.lesson-enough-banner p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.lesson-enough-banner p {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.lesson-enough-banner__actions {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.lesson-overview-more {
|
||||
border-top: 1px solid rgba(160, 120, 40, 0.18);
|
||||
padding-top: 14px;
|
||||
|
||||
Reference in New Issue
Block a user