diff --git a/backend/scripts/bisaya-course-phase1.js b/backend/scripts/bisaya-course-phase1.js index af29eed..e151413 100644 --- a/backend/scripts/bisaya-course-phase1.js +++ b/backend/scripts/bisaya-course-phase1.js @@ -154,6 +154,7 @@ export const BISAYA_DIDACTICS_FRAGMENTS = { { target: 'Dinhi ko.', gloss: 'Ich bin hier.' }, { target: 'Naa ko dinhi.', gloss: 'Ich bin hier.' }, { target: 'Didto siya.', gloss: 'Er/sie ist dort.' }, + { target: 'Adto ko sa merkado.', gloss: 'Ich gehe zum Markt.' }, { target: 'Adto ta didto.', gloss: 'Lass uns dorthin gehen.' }, { target: 'Padulong ko sa merkado.', gloss: 'Ich bin auf dem Weg zum Markt.' }, { target: 'Padulong ta didto.', gloss: 'Lass uns dorthin aufbrechen.' }, @@ -169,6 +170,16 @@ export const BISAYA_DIDACTICS_FRAGMENTS = { title: 'dinhi / didto', text: '„dinhi“ = hier, „didto“ = dort (weg vom Sprecher).', example: 'Naa ko dinhi. Didto ang simbahan.' + }, + { + title: 'Naa + Person + Ort', + text: 'Mit „naa“ sagst du, dass jemand an einem Ort ist. Danach steht der Ort oder das Ortswort.', + example: 'Naa ko dinhi. Naa siya didto.' + }, + { + title: 'adto vs. padulong', + text: '„adto“ ist gehen/fahren zu einem Ziel. „padulong“ betont, dass jemand bereits unterwegs in diese Richtung ist.', + example: 'Adto ko sa merkado. Padulong ko sa merkado.' } ], speakingPrompts: [ diff --git a/backend/scripts/count_corepatterns.js b/backend/scripts/count_corepatterns.js new file mode 100644 index 0000000..225ac64 --- /dev/null +++ b/backend/scripts/count_corepatterns.js @@ -0,0 +1,77 @@ +#!/usr/bin/env node +import fs from 'fs'; +import path from 'path'; + +function readFile(p) { + return fs.readFileSync(path.resolve(p), 'utf8'); +} + +const base = readFile('./backend/scripts/create-bisaya-course.js'); +const bisaya = readFile('./backend/scripts/bisaya-course-plan-24-43.js'); + +function extractLessons(text) { + const lessons = new Map(); + const re = /\{[\s\S]*?num:\s*(\d+),[\s\S]*?title:\s*'([^']+)'[\s\S]*?\}/g; + let m; + while ((m = re.exec(text))) { + const num = Number(m[1]); + const title = m[2]; + if (num >= 1 && num <= 30) lessons.set(num, title); + } + return lessons; +} + +function extractCorePatternsForTitle(title, text) { + // Try to find 'title': { ... corePatterns: [ ... ] } + const escTitle = title.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + const re = new RegExp("'" + escTitle + "'\\s*:\\s*\\{[\\s\\S]*?corePatterns\\s*:\\s*\\[([\\s\\S]*?)\\]", 'm'); + const m = re.exec(text); + if (!m) return null; + const inside = m[1]; + // Count entries: either objects { target: ... } or string entries + const lines = inside.split(/\r?\n/).map(l => l.trim()).filter(l => l.length > 0); + let count = 0; + for (const ln of lines) { + if (ln.startsWith('{') || ln.startsWith("'") || ln.startsWith('"')) count++; + } + return count; +} + +const lessons = extractLessons(base + '\n' + bisaya); +const results = []; +for (let i = 1; i <= 30; i++) { + const title = lessons.get(i); + if (!title) { + results.push({ num: i, title: null, count: 0, note: 'title not found' }); + continue; + } + let count = extractCorePatternsForTitle(title, base); + let source = 'create-bisaya-course.js'; + if (count === null) { + count = extractCorePatternsForTitle(title, bisaya); + source = 'bisaya-course-plan-24-43.js'; + } + if (count === null) { + // try relationship anchor + const relText = bisaya; + count = extractCorePatternsForTitle(title, relText); + source = 'bisaya-course-plan-24-43.js'; + } + if (count === null) { + results.push({ num: i, title, count: 0, note: 'corePatterns not found' }); + } else { + results.push({ num: i, title, count, source }); + } +} + +let total = 0; +for (const r of results) { + total += r.count || 0; +} + +console.log('Core pattern counts for lessons 1..30:'); +results.forEach(r => { + console.log(`${r.num}. ${r.title || ''} -> ${r.count} ${r.source ? '('+r.source+')' : ''} ${r.note?'- '+r.note:''}`); +}); +console.log('---'); +console.log('Total core patterns (1..30):', total); diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index 87ac683..3213c02 100644 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -13,6 +13,7 @@ import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; import VocabCourse from '../models/community/vocab_course.js'; import User from '../models/community/user.js'; +import { BISAYA_DIDACTICS_FRAGMENTS, BISAYA_PHASE1_DIDACTICS } from './bisaya-course-phase1.js'; import { BISAYA_DIDACTICS_24_43, BISAYA_LESSONS_24_43_BY_NUMBER, 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'; @@ -26,6 +27,8 @@ function withTypeName(exerciseTypeName, exercise) { } const GENERATED_BISAYA_DIDACTICS = { + ...BISAYA_PHASE1_DIDACTICS, + ...BISAYA_DIDACTICS_FRAGMENTS, ...BISAYA_RELATIONSHIP_ANCHOR_DIDACTICS, ...BISAYA_DIDACTICS_24_43, ...BISAYA_PHASE3_DIDACTICS, @@ -449,11 +452,23 @@ function rotateArray(values, offset) { function getLessonDidactics(lesson) { const staticDidactics = GENERATED_BISAYA_DIDACTICS[lesson.title] || {}; - const learningGoals = Array.isArray(lesson.learningGoals) ? lesson.learningGoals : (staticDidactics.learningGoals || []); - const corePatterns = Array.isArray(lesson.corePatterns) ? lesson.corePatterns : (staticDidactics.corePatterns || []); - const grammarFocus = Array.isArray(lesson.grammarFocus) ? lesson.grammarFocus : (staticDidactics.grammarFocus || []); - const speakingPrompts = Array.isArray(lesson.speakingPrompts) ? lesson.speakingPrompts : (staticDidactics.speakingPrompts || []); - const practicalTasks = Array.isArray(lesson.practicalTasks) ? lesson.practicalTasks : (staticDidactics.practicalTasks || []); + const learningGoals = Array.isArray(lesson.learningGoals) && lesson.learningGoals.length > 0 + ? lesson.learningGoals + : (staticDidactics.learningGoals || []); + const corePatterns = [ + ...(staticDidactics.corePatterns || []), + ...(Array.isArray(lesson.corePatterns) ? lesson.corePatterns : []) + ]; + const grammarFocus = mergeGrammarFocusForLesson( + staticDidactics.grammarFocus || [], + Array.isArray(lesson.grammarFocus) ? lesson.grammarFocus : [] + ); + const speakingPrompts = Array.isArray(lesson.speakingPrompts) && lesson.speakingPrompts.length > 0 + ? lesson.speakingPrompts + : (staticDidactics.speakingPrompts || []); + const practicalTasks = Array.isArray(lesson.practicalTasks) && lesson.practicalTasks.length > 0 + ? lesson.practicalTasks + : (staticDidactics.practicalTasks || []); return { learningGoals, @@ -2361,14 +2376,14 @@ const BISAYA_EXERCISES = { instruction: 'Fülle die Lücken mit den richtigen Bisaya-Wörtern.', questionData: { type: 'gap_fill', - text: '{gap} ko sa merkado. (Ich gehe zum Markt.) | {gap} ta didto. (Lass uns dorthin gehen.)', + text: '{gap} ko sa merkado. (Ich bin auf dem Weg zum Markt.) | {gap} ta didto. (Lass uns dorthin gehen.)', gaps: 2 }, answerData: { type: 'gap_fill', - answers: ['Padulong', 'Padulong'] + answers: ['Padulong', 'Adto'] }, - explanation: '\"Padulong\" beschreibt, dass man unterwegs zu einem Ziel ist.' + explanation: '„Padulong“ betont die Richtung/Bewegung; „Adto ta didto“ ist die natürliche Aufforderung, dorthin zu gehen.' }, { exerciseTypeId: 4, // transformation @@ -2386,6 +2401,86 @@ const BISAYA_EXERCISES = { alternatives: ['Asa dapit ang simbahan?', 'Asa man ang simbahan?'] }, explanation: '\"simbahan\" = Kirche, \"Asa ang ...?\" = Wo ist ...?' + }, + { + exerciseTypeId: 4, + title: 'Nach der Person fragen', + instruction: 'Übersetze ins Bisaya.', + questionData: { + type: 'transformation', + text: 'Wo bist du?', + sourceLanguage: 'Deutsch', + targetLanguage: 'Bisaya' + }, + answerData: { + type: 'transformation', + correct: 'Asa ka?', + alternatives: ['Asa man ka?'] + }, + explanation: '„Asa ka?“ kann je nach Kontext nach Ort oder Ziel fragen.' + }, + { + exerciseTypeId: 4, + title: 'Hier antworten', + instruction: 'Übersetze ins Bisaya.', + questionData: { + type: 'transformation', + text: 'Ich bin hier.', + sourceLanguage: 'Deutsch', + targetLanguage: 'Bisaya' + }, + answerData: { + type: 'transformation', + correct: 'Naa ko dinhi.', + alternatives: ['Dinhi ko.', 'Naa ko diri.', 'Diri ko.'] + }, + explanation: '„Naa ko dinhi“ und „Dinhi ko“ sind beide brauchbare Antworten.' + }, + { + exerciseTypeId: 4, + title: 'Dort antworten', + instruction: 'Übersetze ins Bisaya.', + questionData: { + type: 'transformation', + text: 'Er ist dort.', + sourceLanguage: 'Deutsch', + targetLanguage: 'Bisaya' + }, + answerData: { + type: 'transformation', + correct: 'Didto siya.', + alternatives: ['Naa siya didto.', 'Siya didto.'] + }, + explanation: '„siya“ kann er oder sie bedeuten; „didto“ markiert dort.' + }, + { + exerciseTypeId: 3, + title: 'Mini-Route bauen', + instruction: 'Ordne die Sätze zu einer kurzen Route.', + questionData: { + type: 'sentence_building', + question: 'Baue: Wo ist der Markt? Ich bin hier. Ich bin auf dem Weg zum Markt.', + tokens: ['Asa ang merkado?', 'Naa ko dinhi.', 'Padulong ko sa merkado.'] + }, + answerData: { + correct: ['Asa ang merkado? Naa ko dinhi. Padulong ko sa merkado.'] + }, + explanation: 'Die Reihenfolge verbindet Ortsfrage, Standort und Richtung.' + }, + { + exerciseTypeId: 10, + title: 'Weg im Alltag', + instruction: 'Antworte kurz mit Ort und Richtung.', + questionData: { + type: 'situational_response', + question: 'Jemand fragt, wo du bist und wohin du gehst. Sage, dass du hier bist und zum Markt unterwegs bist.', + keywords: ['naa', 'dinhi', 'padulong', 'merkado'] + }, + answerData: { + modelAnswer: 'Naa ko dinhi. Padulong ko sa merkado.', + keywords: ['naa', 'dinhi', 'padulong', 'merkado'] + }, + explanation: 'Die Antwort kombiniert Standort und Zielrichtung in zwei kurzen Sätzen.' } ], diff --git a/frontend/src/i18n/locales/ceb/falukant.json b/frontend/src/i18n/locales/ceb/falukant.json index 9a8660b..e420c21 100644 --- a/frontend/src/i18n/locales/ceb/falukant.json +++ b/frontend/src/i18n/locales/ceb/falukant.json @@ -931,7 +931,7 @@ }, "mid": { "label": "tunga-tunga nga kahimtang", - "effects": "Ang mga relasyon makaimpacto pag-iban sa reputasyon ug visibility; ang regular nga suporta makatabang sa pag-stabilize sa relasyon." + "effects": "May kombinadong epekto: ang maayo nga pag-atiman sa relasyon makahatag og dali nga pagtaas sa reputasyon ug mapuslanong koneksyon, apan ang pagtaas sa visibility ug taas o dili maayo nga pagdumala sa suporta makapataas sa risgo sa iskandalo ug politikal nga problema. Ang pagka-public nagdugang kaayo sa visibility; ang diskreto mas luwas apan makahatag og gamay nga reputasyon." }, "high": { "label": "taas nga kahimtang", diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index 8e8169d..90dec0f 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -808,7 +808,7 @@ }, "mid": { "label": "mittlerer Stand", - "effects": "Liebschaften können moderat Ansehen und Sichtbarkeit beeinflussen; regelmäßiger Unterhalt stabilisiert Beziehungen." + "effects": "Hat gemischte Effekte: gepflegte Liebschaften können kurzfristig Ansehen und nützliche Kontakte bringen, erhöhen aber bei wachsender Sichtbarkeit und schlecht verwaltetem Unterhalt das Skandalrisiko und politische Probleme. Öffentlich bekannte Beziehungen steigern die Sichtbarkeit deutlich; diskrete Beziehungen sind sicherer, liefern jedoch weniger Reputation." }, "high": { "label": "hoher Stand", diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json index 8485bb8..06eb45b 100644 --- a/frontend/src/i18n/locales/en/falukant.json +++ b/frontend/src/i18n/locales/en/falukant.json @@ -1007,7 +1007,7 @@ }, "mid": { "label": "middle stand", - "effects": "Affairs can moderately affect reputation and visibility; regular upkeep helps stabilise relations." + "effects": "Has mixed effects: well-maintained affairs can yield short-term reputation gains and useful contacts, but increased visibility and high or mismanaged upkeep raise scandal risk and political fallout. Public affairs greatly increase visibility; discreet affairs are safer but offer less reputation." }, "high": { "label": "higher stand", diff --git a/frontend/src/i18n/locales/es/falukant.json b/frontend/src/i18n/locales/es/falukant.json index 5f79038..f0df6a1 100644 --- a/frontend/src/i18n/locales/es/falukant.json +++ b/frontend/src/i18n/locales/es/falukant.json @@ -808,7 +808,7 @@ }, "mid": { "label": "estamento medio", - "effects": "Las relaciones pueden afectar moderadamente la reputación y la visibilidad; un mantenimiento regular estabiliza la relación." + "effects": "Efectos mixtos: las relaciones bien mantenidas pueden otorgar ganancias de reputación a corto plazo y contactos útiles, pero la mayor visibilidad y un mantenimiento alto o mal gestionado aumentan el riesgo de escándalo y las consecuencias políticas. Hacerla pública incrementa mucho la visibilidad; la discreción reduce riesgos pero ofrece menos reputación." }, "high": { "label": "estamento alto", diff --git a/frontend/src/i18n/locales/fr/falukant.json b/frontend/src/i18n/locales/fr/falukant.json index 5e141de..afd4008 100644 --- a/frontend/src/i18n/locales/fr/falukant.json +++ b/frontend/src/i18n/locales/fr/falukant.json @@ -806,7 +806,7 @@ }, "mid": { "label": "rang moyen", - "effects": "Les relations peuvent modérément influencer réputation et visibilité ; un entretien régulier stabilise la relation." + "effects": "Effets mitigés : des relations bien entretenues peuvent apporter un gain d'image à court terme et des contacts utiles, mais une visibilité accrue et un entretien mal géré augmentent le risque de scandale et les répercussions politiques. Une relation publique augmente fortement la visibilité ; la discrétion réduit les risques mais rapporte moins de réputation." }, "high": { "label": "rang élevé",