/** * TranslationModule - AI-validated translation exercises * Presents translation challenges with intelligent AI feedback on accuracy and fluency * Implements DRSExerciseInterface for strict contract enforcement */ import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; class TranslationModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { super('TranslationModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { throw new Error('TranslationModule requires all service dependencies'); } this.orchestrator = orchestrator; this.llmValidator = llmValidator; this.prerequisiteEngine = prerequisiteEngine; this.contextMemory = contextMemory; // Module state this.initialized = false; this.container = null; this.currentExerciseData = null; this.currentTranslations = []; this.translationIndex = 0; this.userTranslations = []; this.validationInProgress = false; this.lastValidationResult = null; // Configuration this.config = { requiredProvider: 'openai', // Translation needs nuanced understanding model: 'gpt-4o-mini', temperature: 0.2, // Lower for accuracy in translation maxTokens: 1000, timeout: 50000, translationsPerExercise: 4, // Number of phrases to translate supportedLanguagePairs: [ { from: 'French', to: 'English' }, { from: 'English', to: 'French' }, { from: 'Spanish', to: 'English' }, { from: 'English', to: 'Spanish' } ], defaultLanguagePair: { from: 'French', to: 'English' }, allowMultipleAttempts: true, accuracyThreshold: 0.7, // Minimum score for acceptable translation showContext: true // Show context/situation for translation }; // Progress tracking this.progress = { translationsCompleted: 0, translationsAccurate: 0, averageAccuracy: 0, averageFluency: 0, vocabularyLearned: new Set(), phrasesLearned: new Set(), culturalNotesLearned: new Set(), totalTimeSpent: 0, lastActivity: null, improvementAreas: { vocabulary: 0, grammar: 0, idioms: 0, cultural: 0, fluency: 0 } }; // UI elements cache this.elements = { sourceContainer: null, sourceText: null, translationArea: null, submitButton: null, feedbackContainer: null, progressIndicator: null, contextPanel: null, languageSelector: null }; } /** * Check if module can run with current content */ canRun(prerequisites, chapterContent) { // Check if we have translation content or can generate it const hasTranslationContent = chapterContent && (chapterContent.translations || chapterContent.phrases || chapterContent.texts); if (!hasTranslationContent) { console.log('β TranslationModule: No translation content available'); return false; } // Check if LLM validator is available if (!this.llmValidator || !this.llmValidator.isAvailable()) { console.log('β TranslationModule: LLM validator not available'); return false; } console.log('β TranslationModule: Can run with available content'); return true; } /** * Present exercise UI and content */ async present(container, exerciseData) { console.log('π TranslationModule: Starting presentation'); this.container = container; this.currentExerciseData = exerciseData; try { // Clear container container.innerHTML = ''; // Prepare translation content await this._prepareTranslations(); // Create UI layout this._createUI(); // Show first translation this._displayCurrentTranslation(); this.initialized = true; console.log('β TranslationModule: Presentation ready'); } catch (error) { console.error('β TranslationModule presentation failed:', error); this._showError('Failed to load translation exercise. Please try again.'); } } /** * Validate user translation with AI */ async validate(userTranslation, context) { console.log('π TranslationModule: Validating translation'); if (this.validationInProgress) { console.log('β³ Validation already in progress'); return this.lastValidationResult; } const currentTranslation = this.currentTranslations[this.translationIndex]; // Basic input validation if (!userTranslation || userTranslation.trim().length === 0) { return { success: false, score: 0, feedback: 'Please provide a translation for the text.', suggestions: ['Read the source text carefully', 'Consider the context', 'Translate the meaning, not just words'] }; } // Check minimum effort (not just source text copied) if (userTranslation.trim().toLowerCase() === currentTranslation.source.toLowerCase()) { return { success: false, score: 0, feedback: 'You\'ve copied the original text. Please provide a translation.', suggestions: [ 'Translate the text into the target language', 'Focus on conveying the meaning', 'Don\'t just copy the original' ] }; } this.validationInProgress = true; try { // Prepare context for translation validation const translationContext = { sourceText: currentTranslation.source, targetLanguage: currentTranslation.targetLanguage || this.config.defaultLanguagePair.to, sourceLanguage: currentTranslation.sourceLanguage || this.config.defaultLanguagePair.from, userTranslation: userTranslation.trim(), context: currentTranslation.context || '', difficulty: this.currentExerciseData.difficulty || 'medium', expectedTranslation: currentTranslation.expected || null, culturalNotes: currentTranslation.culturalNotes || null, ...context }; // Use LLMValidator for translation analysis const result = await this.llmValidator.validateTranslation( currentTranslation.source, userTranslation.trim(), translationContext ); // Process and enhance the result const enhancedResult = this._processTranslationResult(result, userTranslation, currentTranslation); // Store result and update progress this.lastValidationResult = enhancedResult; this._updateProgress(enhancedResult, currentTranslation); console.log('β TranslationModule: Validation completed', enhancedResult); return enhancedResult; } catch (error) { console.error('β TranslationModule validation failed:', error); return { success: false, score: 0, feedback: 'Unable to analyze your translation due to a technical issue. Please try again.', error: error.message, suggestions: ['Check your internet connection', 'Try a simpler translation', 'Contact support if the problem persists'] }; } finally { this.validationInProgress = false; } } /** * Get current progress */ getProgress() { return { ...this.progress, currentTranslation: this.translationIndex + 1, totalTranslations: this.currentTranslations.length, moduleType: 'translation', completionRate: this._calculateCompletionRate(), accuracyRate: this.progress.translationsCompleted > 0 ? this.progress.translationsAccurate / this.progress.translationsCompleted : 0 }; } /** * Clean up resources */ cleanup() { // Remove event listeners if (this.elements.submitButton) { this.elements.submitButton.removeEventListener('click', this._handleSubmit.bind(this)); } if (this.elements.translationArea) { this.elements.translationArea.removeEventListener('input', this._handleInputChange.bind(this)); } // Clear references this.container = null; this.currentExerciseData = null; this.elements = {}; this.initialized = false; console.log('π§Ή TranslationModule: Cleaned up'); } /** * Get module metadata */ getMetadata() { return { name: 'TranslationModule', version: '1.0.0', description: 'AI-validated translation exercises', author: 'DRS System', capabilities: [ 'translation-validation', 'cultural-context', 'fluency-assessment', 'vocabulary-learning', 'progress-tracking' ], requiredServices: ['llmValidator', 'orchestrator', 'prerequisiteEngine', 'contextMemory'], supportedLanguages: ['English', 'French', 'Spanish'], exerciseTypes: ['text-translation', 'phrase-translation', 'contextual-translation'] }; } // Private methods async _prepareTranslations() { // Extract or generate translation content if (this.currentExerciseData.translations && this.currentExerciseData.translations.length > 0) { // Use provided translations this.currentTranslations = this.currentExerciseData.translations.slice(0, this.config.translationsPerExercise); } else if (this.currentExerciseData.phrases && this.currentExerciseData.phrases.length > 0) { // Use phrases for translation this.currentTranslations = this.currentExerciseData.phrases.slice(0, this.config.translationsPerExercise); } else { // Generate translations using AI await this._generateTranslationsWithAI(); } // Normalize translation format this.currentTranslations = this.currentTranslations.map(translation => { if (typeof translation === 'string') { return { source: translation, sourceLanguage: this.config.defaultLanguagePair.from, targetLanguage: this.config.defaultLanguagePair.to, context: '', difficulty: this.currentExerciseData.difficulty || 'medium' }; } return { source: translation.source || translation.text || translation, sourceLanguage: translation.sourceLanguage || translation.from || this.config.defaultLanguagePair.from, targetLanguage: translation.targetLanguage || translation.to || this.config.defaultLanguagePair.to, context: translation.context || translation.situation || '', expected: translation.expected || translation.translation || null, culturalNotes: translation.culturalNotes || translation.notes || null, difficulty: translation.difficulty || this.currentExerciseData.difficulty || 'medium' }; }); console.log('π Translation content prepared:', this.currentTranslations.length); } async _generateTranslationsWithAI() { // Use the orchestrator's IAEngine to generate translation exercises try { const difficulty = this.currentExerciseData.difficulty || 'medium'; const topic = this.currentExerciseData.topic || 'everyday situations'; const languagePair = this.currentExerciseData.languagePair || this.config.defaultLanguagePair; const prompt = `Generate 4 translation exercises from ${languagePair.from} to ${languagePair.to} for ${difficulty} level learners. Topic: ${topic} Requirements: - Create practical phrases/sentences for real-world situations - Include variety: greetings, daily activities, emotions, descriptions - Make them appropriate for ${difficulty} level - Include context when helpful - Focus on useful, commonly needed expressions Return ONLY valid JSON: [ { "source": "Bonjour, comment allez-vous aujourd'hui?", "context": "Polite greeting in a formal setting", "difficulty": "${difficulty}", "culturalNotes": "Use 'vous' for formal situations" } ]`; const sharedServices = this.orchestrator.getSharedServices(); if (sharedServices && sharedServices.iaEngine) { const result = await sharedServices.iaEngine.validateEducationalContent(prompt, { systemPrompt: 'You are a language learning expert. Create realistic translation exercises.', temperature: 0.4 }); if (result && result.content) { try { this.currentTranslations = JSON.parse(result.content); console.log('β Generated translations with AI:', this.currentTranslations.length); return; } catch (parseError) { console.warn('Failed to parse AI-generated translations'); } } } } catch (error) { console.warn('Failed to generate translations with AI:', error); } // Fallback: basic translations this.currentTranslations = [ { source: "Bonjour, comment allez-vous?", context: "Formal greeting", difficulty: "easy", culturalNotes: "Use 'vous' for politeness" }, { source: "Je voudrais rΓ©server une table pour ce soir.", context: "Restaurant reservation", difficulty: "medium", culturalNotes: "Conditional form shows politeness" }, { source: "Pourriez-vous m'indiquer le chemin vers la gare?", context: "Asking for directions", difficulty: "medium", culturalNotes: "Very polite way to ask for help" }, { source: "J'ai eu beaucoup de mal Γ comprendre cette explication.", context: "Expressing difficulty in understanding", difficulty: "hard", culturalNotes: "Idiomatic expression 'avoir du mal Γ '" } ]; } _createUI() { const container = this.container; container.innerHTML = `