Class_generator/Legacy/js/core/json-content-loader.js
StillHammer 38920cc858 Complete architectural rewrite with ultra-modular system
Major Changes:
- Moved legacy system to Legacy/ folder for archival
- Built new modular architecture with strict separation of concerns
- Created core system: Module, EventBus, ModuleLoader, Router
- Added Application bootstrap with auto-start functionality
- Implemented development server with ES6 modules support
- Created comprehensive documentation and project context
- Converted SBS-7-8 content to JSON format
- Copied all legacy games and content to new structure

New Architecture Features:
- Sealed modules with WeakMap private data
- Strict dependency injection system
- Event-driven communication only
- Inviolable responsibility patterns
- Auto-initialization without commands
- Component-based UI foundation ready

Technical Stack:
- Vanilla JS/HTML/CSS only
- ES6 modules with proper imports/exports
- HTTP development server (no file:// protocol)
- Modular CSS with component scoping
- Comprehensive error handling and debugging

Ready for Phase 2: Converting legacy modules to new architecture

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-22 07:08:39 +08:00

561 lines
18 KiB
JavaScript

// === JSON CONTENT LOADER ===
// Transforme les contenus JSON en format compatible avec les jeux existants
class JSONContentLoader {
constructor() {
this.loadedContent = new Map();
}
/**
* Charge et adapte un contenu JSON pour les jeux
* @param {Object} jsonContent - Le contenu JSON brut
* @returns {Object} - Contenu adapté au format legacy
*/
adapt(jsonContent) {
if (!jsonContent) {
logSh('⚠️ JSONContentLoader - Contenu vide reçu', 'WARN');
return this.createEmptyContent();
}
logSh(`🔄 JSONContentLoader - Adaptation du contenu: ${jsonContent.name || 'Sans nom'}`, 'INFO');
// Créer l'objet de base au format legacy avec nouvelles métadonnées
const adaptedContent = {
// Métadonnées ultra-modulaires
id: jsonContent.id,
name: jsonContent.name || 'Contenu Sans Nom',
description: jsonContent.description || '',
// Difficulty system (1-10 scale support)
difficulty: this.adaptDifficulty(jsonContent.difficulty_level || jsonContent.difficulty),
difficulty_level: jsonContent.difficulty_level, // Preserve numeric scale
// Language system (original_lang/user_lang pattern)
language: jsonContent.user_lang || jsonContent.language || 'english',
original_lang: jsonContent.original_lang,
user_lang: jsonContent.user_lang,
// Icon system with fallback
icon: this.adaptIcon(jsonContent.icon, jsonContent.fallback_icon),
// New metadata fields
tags: jsonContent.tags || [],
skills_covered: jsonContent.skills_covered || [],
prerequisites: jsonContent.prerequisites || [],
estimated_duration: jsonContent.estimated_duration,
target_audience: jsonContent.target_audience || {},
pedagogical_approach: jsonContent.pedagogical_approach,
// === VOCABULAIRE ===
vocabulary: this.adaptVocabulary(jsonContent.vocabulary),
// === PHRASES ET SENTENCES ===
sentences: this.adaptSentences(jsonContent.sentences),
// === STORY STRUCTURE (Dragon's Pearl format) ===
story: jsonContent.story ? this.adaptStory(jsonContent.story) : null,
// === TEXTES ===
texts: this.adaptTexts(jsonContent.texts),
// === DIALOGUES ===
dialogues: this.adaptDialogues(jsonContent.dialogues),
// === GRAMMAIRE ===
grammar: this.adaptGrammar(jsonContent.grammar),
// === AUDIO ===
audio: this.adaptAudio(jsonContent.audio),
// === POÈMES ET CULTURE ===
poems: this.adaptPoems(jsonContent.poems),
// === EXERCICES AVANCÉS ===
fillInBlanks: this.adaptFillInBlanks(jsonContent.fillInBlanks),
corrections: this.adaptCorrections(jsonContent.corrections),
comprehension: this.adaptComprehension(jsonContent.comprehension),
matching: this.adaptMatching(jsonContent.matching),
// === EXERCICES GÉNÉRIQUES ===
exercises: this.adaptExercises(jsonContent.exercises),
// === CULTURE CONTENT ===
culture: this.adaptCulture(jsonContent.culture),
// === PARAMETRIC SENTENCES ===
parametric_sentences: this.adaptParametricSentences(jsonContent.parametric_sentences),
// === LISTENING (format SBS) ===
listening: this.adaptListening(jsonContent.listening),
// === MÉTADONNÉES DE COMPATIBILITÉ ===
_adapted: true,
_source: 'json',
_adaptedAt: new Date().toISOString()
};
// Nettoyage des valeurs undefined
this.cleanUndefinedValues(adaptedContent);
logSh(`✅ JSONContentLoader - Contenu adapté avec succès`, 'INFO');
logSh(`📊 Stats: ${Object.keys(adaptedContent.vocabulary || {}).length} mots, ${(adaptedContent.sentences || []).length} phrases`, 'INFO');
return adaptedContent;
}
/**
* Adapte le vocabulaire (format JSON ultra-modulaire → Legacy)
* Supports 6 levels of vocabulary complexity from ultra_commented specification
*/
adaptVocabulary(vocabulary) {
if (!vocabulary || typeof vocabulary !== 'object') {
return {};
}
const adapted = {};
for (const [word, definition] of Object.entries(vocabulary)) {
if (typeof definition === 'string') {
// Level 1: Simple string "cat": "chat"
adapted[word] = definition;
} else if (typeof definition === 'object') {
// Levels 2-6: Rich vocabulary objects
adapted[word] = {
// Core translation (supports original_lang/user_lang pattern)
translation: definition.user_language || definition.translation || definition.french || definition.chinese || '',
original: definition.original_language || word,
english: word,
// Metadata
type: definition.type || 'word',
// Pronunciation (IPA format support)
pronunciation: definition.pronunciation || definition.prononciation,
ipa: definition.ipa, // IPA phonetic notation
audio: definition.audio_file || definition.audio || definition.pronunciation,
// Learning data
examples: definition.examples || definition.example_sentences,
grammarNotes: definition.grammar_notes || definition.grammarNotes,
usage_context: definition.usage_context,
// Advanced features (Level 4+)
etymology: definition.etymology,
word_family: definition.word_family,
// Cultural context (Level 5+)
cultural_significance: definition.cultural_significance,
regional_usage: definition.regional_usage,
// Memory aids (Level 6)
memory_techniques: definition.memory_techniques,
visual_associations: definition.visual_associations,
// Legacy compatibility
french: definition.french || definition.user_language || definition.translation,
chinese: definition.chinese || definition.user_language || definition.translation,
image: definition.image || definition.visual_aid,
difficulty: definition.difficulty_level
};
}
}
return adapted;
}
/**
* Adapte les phrases
*/
adaptSentences(sentences) {
if (!Array.isArray(sentences)) {
return [];
}
return sentences.map(sentence => {
if (typeof sentence === 'string') {
return { english: sentence, translation: '' };
}
return {
english: sentence.english || '',
french: sentence.french || sentence.translation,
chinese: sentence.chinese || sentence.translation,
translation: sentence.translation || sentence.french || sentence.chinese,
prononciation: sentence.prononciation || sentence.pinyin,
audio: sentence.audio,
difficulty: sentence.difficulty
};
});
}
/**
* Adapte les textes
*/
adaptTexts(texts) {
if (!Array.isArray(texts)) {
return [];
}
return texts.map(text => ({
title: text.title || 'Sans titre',
content: text.content || '',
translation: text.translation || '',
french: text.french || text.translation,
chinese: text.chinese || text.translation,
audio: text.audio,
difficulty: text.difficulty
}));
}
/**
* Adapte les dialogues
*/
adaptDialogues(dialogues) {
if (!Array.isArray(dialogues)) {
return [];
}
return dialogues.map(dialogue => ({
title: dialogue.title || 'Dialogue',
conversation: Array.isArray(dialogue.conversation)
? dialogue.conversation.map(line => ({
speaker: line.speaker || 'Speaker',
english: line.english || '',
french: line.french || line.translation,
chinese: line.chinese || line.translation,
translation: line.translation || line.french || line.chinese,
prononciation: line.prononciation || line.pinyin,
audio: line.audio
}))
: []
}));
}
/**
* Adapte la grammaire
*/
adaptGrammar(grammar) {
if (!grammar || typeof grammar !== 'object') {
return {};
}
// La grammaire reste en format objet (compatible)
return grammar;
}
/**
* Adapte le contenu audio
*/
adaptAudio(audio) {
if (!audio || typeof audio !== 'object') {
return {};
}
return {
withText: Array.isArray(audio.withText) ? audio.withText : [],
withoutText: Array.isArray(audio.withoutText) ? audio.withoutText : []
};
}
/**
* Adapte les poèmes
*/
adaptPoems(poems) {
if (!Array.isArray(poems)) {
return [];
}
return poems.map(poem => ({
title: poem.title || 'Poème',
content: poem.content || '',
translation: poem.translation || '',
audioFile: poem.audioFile,
culturalContext: poem.culturalContext
}));
}
/**
* Adapte les exercices Fill-in-the-Blanks
*/
adaptFillInBlanks(fillInBlanks) {
if (!Array.isArray(fillInBlanks)) {
return [];
}
return fillInBlanks;
}
/**
* Adapte les exercices de correction
*/
adaptCorrections(corrections) {
if (!Array.isArray(corrections)) {
return [];
}
return corrections;
}
/**
* Adapte les exercices de compréhension
*/
adaptComprehension(comprehension) {
if (!Array.isArray(comprehension)) {
return [];
}
return comprehension;
}
/**
* Adapte les exercices de matching (supports multi-column system)
*/
adaptMatching(matching) {
if (!Array.isArray(matching)) {
return [];
}
return matching.map(exercise => {
// Handle traditional two-column format
if (exercise.leftColumn && exercise.rightColumn) {
return exercise;
}
// Handle new multi-column format with numeric IDs
if (exercise.columns && Array.isArray(exercise.columns)) {
return {
...exercise,
title: exercise.title || 'Matching Exercise',
type: exercise.type || 'multi_column',
columns: exercise.columns,
correct_matches: exercise.correct_matches || exercise.correctMatches || []
};
}
return exercise;
});
}
/**
* Adapte les exercices génériques
*/
adaptExercises(exercises) {
if (!exercises || typeof exercises !== 'object') {
return {};
}
return exercises;
}
/**
* Adapte le contenu listening (format SBS)
*/
adaptListening(listening) {
if (!listening || typeof listening !== 'object') {
return {};
}
return listening;
}
/**
* Nettoie les valeurs undefined de l'objet
*/
cleanUndefinedValues(obj) {
Object.keys(obj).forEach(key => {
if (obj[key] === undefined) {
delete obj[key];
} else if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
this.cleanUndefinedValues(obj[key]);
}
});
}
/**
* Adapte le système de difficulté (1-10 scale → legacy)
*/
adaptDifficulty(difficulty) {
if (typeof difficulty === 'number') {
// Convert 1-10 scale to legacy terms
if (difficulty <= 3) return 'easy';
if (difficulty <= 6) return 'medium';
if (difficulty <= 8) return 'hard';
return 'expert';
}
return difficulty || 'medium';
}
/**
* Adapte le système d'icônes avec fallback
*/
adaptIcon(icon, fallbackIcon) {
if (typeof icon === 'object') {
return icon.primary || icon.emoji || fallbackIcon || '📝';
}
return icon || fallbackIcon || '📝';
}
/**
* Adapte une structure story (Dragon's Pearl format)
*/
adaptStory(story) {
if (!story || typeof story !== 'object') {
return null;
}
logSh(`🐉 JSONContentLoader - Adaptation de la structure story: ${story.title}`, 'DEBUG');
const adaptedStory = {
title: story.title || 'Histoire Sans Titre',
totalSentences: story.totalSentences || 0,
chapters: []
};
if (story.chapters && Array.isArray(story.chapters)) {
logSh(`🐉 JSONContentLoader - ${story.chapters.length} chapitres trouvés`, 'DEBUG');
adaptedStory.chapters = story.chapters.map((chapter, index) => {
const adaptedChapter = {
title: chapter.title || `Chapitre ${index + 1}`,
sentences: []
};
if (chapter.sentences && Array.isArray(chapter.sentences)) {
logSh(`🐉 JSONContentLoader - Chapitre ${index}: ${chapter.sentences.length} phrases`, 'DEBUG');
adaptedChapter.sentences = chapter.sentences.map(sentence => ({
id: sentence.id,
original: sentence.original,
translation: sentence.translation,
pronunciation: sentence.pronunciation,
words: sentence.words || []
}));
}
return adaptedChapter;
});
const totalSentences = adaptedStory.chapters.reduce((sum, chapter) =>
sum + (chapter.sentences ? chapter.sentences.length : 0), 0);
logSh(`🐉 JSONContentLoader - Story adaptée: ${totalSentences} phrases au total`, 'INFO');
}
return adaptedStory;
}
/**
* Adapte le contenu culturel
*/
adaptCulture(culture) {
if (!culture || typeof culture !== 'object') {
return {};
}
return culture;
}
/**
* Adapte les phrases paramétriques
*/
adaptParametricSentences(parametricSentences) {
if (!Array.isArray(parametricSentences)) {
return [];
}
return parametricSentences;
}
/**
* Crée un contenu vide par défaut
*/
createEmptyContent() {
return {
id: 'empty_content_' + Date.now(),
name: 'Contenu Vide',
description: 'Aucun contenu disponible',
difficulty: 'easy',
difficulty_level: 1,
language: 'english',
user_lang: 'french',
original_lang: 'english',
tags: [],
skills_covered: [],
vocabulary: {},
sentences: [],
texts: [],
dialogues: [],
grammar: {},
exercises: {},
culture: {},
_adapted: true,
_source: 'empty',
_adaptedAt: new Date().toISOString()
};
}
/**
* Analyse la richesse du contenu et génère des statistiques
*/
analyzeContent(adaptedContent) {
const stats = {
vocabularyCount: Object.keys(adaptedContent.vocabulary || {}).length,
sentenceCount: (adaptedContent.sentences || []).length,
textCount: (adaptedContent.texts || []).length,
dialogueCount: (adaptedContent.dialogues || []).length,
grammarTopics: Object.keys(adaptedContent.grammar || {}).length,
audioContent: !!(adaptedContent.audio && (adaptedContent.audio.withText || adaptedContent.audio.withoutText)),
poemCount: (adaptedContent.poems || []).length,
exerciseTypes: Object.keys(adaptedContent.exercises || {}).length,
totalItems: 0
};
stats.totalItems = stats.vocabularyCount + stats.sentenceCount +
stats.textCount + stats.dialogueCount + stats.poemCount;
stats.richness = stats.totalItems > 50 ? 'rich' :
stats.totalItems > 20 ? 'medium' : 'basic';
return stats;
}
/**
* Teste si un objet est au format JSON (vs legacy JS)
*/
isJSONFormat(content) {
return content && (
content.hasOwnProperty('name') ||
content.hasOwnProperty('description') ||
content.hasOwnProperty('language') ||
content._adapted === true
);
}
/**
* Point d'entrée principal - décide s'il faut adapter ou pas
*/
loadContent(content) {
if (!content) {
return this.createEmptyContent();
}
// Si déjà adapté, retourner tel quel
if (content._adapted === true) {
return content;
}
// Si format JSON, adapter
if (this.isJSONFormat(content)) {
return this.adapt(content);
}
// Sinon, c'est du format legacy, retourner tel quel
return content;
}
}
// Export global
window.JSONContentLoader = JSONContentLoader;
// Export Node.js (optionnel)
if (typeof module !== 'undefined' && module.exports) {
module.exports = JSONContentLoader;
}