// === 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; }