// === 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 const adaptedContent = { // Métadonnées (préservées mais adaptées) name: jsonContent.name || 'Contenu Sans Nom', description: jsonContent.description || '', difficulty: jsonContent.difficulty || 'medium', language: jsonContent.language || 'english', icon: jsonContent.icon || '📝', // === VOCABULAIRE === vocabulary: this.adaptVocabulary(jsonContent.vocabulary), // === PHRASES ET SENTENCES === sentences: this.adaptSentences(jsonContent.sentences), // === 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), // === 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 → Legacy) */ adaptVocabulary(vocabulary) { if (!vocabulary || typeof vocabulary !== 'object') { return {}; } const adapted = {}; for (const [word, definition] of Object.entries(vocabulary)) { if (typeof definition === 'string') { // Format simple: "cat": "chat" adapted[word] = definition; } else if (typeof definition === 'object') { // Format enrichi: "cat": { translation: "chat", type: "noun", ... } adapted[word] = { translation: definition.translation || definition.french || definition.chinese || '', english: word, type: definition.type || 'word', pronunciation: definition.pronunciation || definition.prononciation, difficulty: definition.difficulty, examples: definition.examples, grammarNotes: definition.grammarNotes, // Compatibilité avec anciens formats french: definition.french || definition.translation, chinese: definition.chinese || definition.translation, image: definition.image, audio: definition.audio || definition.pronunciation }; } } 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 */ adaptMatching(matching) { if (!Array.isArray(matching)) { return []; } return matching; } /** * 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]); } }); } /** * Crée un contenu vide par défaut */ createEmptyContent() { return { name: 'Contenu Vide', description: 'Aucun contenu disponible', difficulty: 'easy', vocabulary: {}, sentences: [], texts: [], dialogues: [], grammar: {}, exercises: {}, _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; }