// ============================================================ // UmkhoAI Classroom Translator - Main Application Script // ============================================================ // --- Language Data --- const SA_LANGUAGES = [ { code: 'af', name: 'Afrikaans', flag: 'πΏπ¦' }, { code: 'en', name: 'English', flag: 'πΏπ¦' }, { code: 'nr', name: 'isiNdebele', flag: 'πΏπ¦' }, { code: 'xh', name: 'isiXhosa', flag: 'πΏπ¦' }, { code: 'zu', name: 'isiZulu', flag: 'πΏπ¦' }, { code: 'nso', name: 'Northern Sotho', flag: 'πΏπ¦' }, { code: 'st', name: 'Sesotho', flag: 'πΏπ¦' }, { code: 'tn', name: 'Setswana', flag: 'πΏπ¦' }, { code: 'ss', name: 'siSwati', flag: 'πΏπ¦' }, { code: 've', name: 'Tshivenda', flag: 'πΏπ¦' }, { code: 'ts', name: 'Xitsonga', flag: 'πΏπ¦' }, ]; // --- Simulated Translation Dictionary --- // In production, this would call the UmkhoAI offline translation engine. // For this demo, we use a phrase-based simulation with realistic output. const TRANSLATIONS = { af: { greeting: 'Welkom by die les', students: 'studente', teacher: 'onderwyser', classroom: 'klaskamer', lesson: 'les', question: 'vraag', answer: 'antwoord', read: 'lees', write: 'skryf', learn: 'leer', numbers: 'getalle', water: 'water', sun: 'son', earth: 'aarde', plant: 'plant', animal: 'dier', history: 'geskiedenis', freedom: 'vryheid', democracy: 'demokrasie', rights: 'regte', today: 'vandag', important: 'belangrik', remember: 'onthou', example: 'voorbeeld', chapter: 'hoofstuk', }, nr: { greeting: 'Wamukelekile esifundweni', students: 'abafundi', teacher: 'uthisha', classroom: 'ikilasi', lesson: 'isifundo', question: 'umbuzo', answer: 'imphendulo', read: 'funda', write: 'bhala', learn: 'funda', numbers: 'amanombolo', water: 'amanzi', sun: 'ilanga', earth: 'umhlaba', plant: 'isitshalo', animal: 'isilwane', history: 'umlando', freedom: 'inkululeko', democracy: 'intando yeningi', rights: 'amalungelo', today: 'namuhla', important: 'kubalulekile', remember: 'khumbula', example: 'isibonelo', chapter: 'isahluko', }, xh: { greeting: 'Wamkelekile kwisifundo', students: 'abafundi', teacher: 'uthishabhala', classroom: 'klasi', lesson: 'isifundo', question: 'umbuzo', answer: 'impendulo', read: 'funda', write: 'bhala', learn: 'funda', numbers: 'amanani', water: 'amanzi', sun: 'ilanga', earth: 'umhlaba', plant: 'isityalo', animal: 'isilwanyana', history: 'imbali', freedom: 'inkululeko', democracy: 'intando yeningi', rights: 'amalungelo', today: 'namhlanje', important: 'kubalulekile', remember: 'khumbula', example: 'umzekelo', chapter: 'isahluko', }, zu: { greeting: 'Siyakwamukela esifundweni', students: 'abafundi', teacher: 'thisha', classroom: 'klasini', lesson: 'isifundo', question: 'umbuzo', answer: 'impendulo', read: 'funda', write: 'bhala', learn: 'funda', numbers: 'amanombolo', water: 'amanzi', sun: 'ilanga', earth: 'umhlaba', plant: 'isitshalo', animal: 'isilwane', history: 'umlando', freedom: 'inkululeko', democracy: 'intando yeningi', rights: 'amalungelo', today: 'namuhla', important: 'kubalulekile', remember: 'khumbula', example: 'isibonelo', chapter: 'isahluko', }, nso: { greeting: 'O amogela go ya lesong', students: 'bahi', teacher: 'morutΕ‘i', classroom: 'klaseng', lesson: 'leso', question: 'potsiso', answer: 'karabo', read: 'bala', write: 'ngwala', learn: 'ithuta', numbers: 'dinomoro', water: 'metsi', sun: 'letsatsi', earth: 'lefase', plant: 'setshedi', animal: 'phoofolo', history: 'histori', freedom: 'tokologo', democracy: 'demokerasi', rights: 'ditokelo', today: 'lehono', important: 'kgolo', remember: 'gopola', example: 'mohlala', chapter: 'kgaolo', }, st: { greeting: 'O amohetseng thutong', students: 'baithuti', teacher: 'moruti', classroom: 'kalaseng', lesson: 'thuto', question: 'potso', answer: 'karabo', read: 'bala', write: 'ngola', learn: 'ithuta', numbers: 'dinomoro', water: 'metsi', sun: 'letsatsi', earth: 'lefatshe', plant: 'setshedi', animal: 'phoofolo', history: 'histori', freedom: 'tokoloho', democracy: 'demokrasi', rights: 'ditokelo', today: 'kajeno', important: 'kgolo', remember: 'hopola', example: 'mohlala', chapter: 'kara', }, tn: { greeting: 'O amogela mo thutong', students: 'baithuti', teacher: 'morutabana', classroom: 'kalaseng', lesson: 'thuto', question: 'potso', answer: 'karabo', read: 'bala', write: 'kwala', learn: 'ithuta', numbers: 'dinomoro', water: 'metsi', sun: 'letsatsi', earth: 'lefatshe', plant: 'setshedi', animal: 'phoofolo', history: 'tiri', freedom: 'boipuso', democracy: 'demokerasi', rights: 'ditokelo', today: 'gompieno', important: 'botlhokwa', remember: 'gopola', example: 'mohlala', chapter: 'karolo', }, ss: { greeting: 'Siyakwemukela esifundweni', students: 'bafundi', teacher: 'thisha', classroom: 'kilasini', lesson: 'sifundo', question: 'umbuzo', answer: 'imphendvuto', read: 'fundza', write: 'bhala', learn: 'funda', numbers: 'emanombolo', water: 'emanti', sun: 'ilanga', earth: 'umhlaba', plant: 'sitjalo', animal: 'silwane', history: 'umlandvo', freedom: 'inkululeko', democracy: 'intandvo yeningi', rights: 'emalungelo', today: 'namuhla', important: 'kubalulekile', remember: 'khumbula', example: 'sicatsetse', chapter: 'sichatsi', }, ve: { greeting: 'Vhuedzedzani ha thendelo', students: 'vhadidini', teacher: 'mugudisi', classroom: 'kilasini', lesson: 'thendelo', question: 'mubvumo', answer: 'phendulo', read: 'vhala', write: 'nwala', learn: 'guda', numbers: 'mimanomboro', water: 'madi', sun: 'dzimu', earth: 'shango', plant: 'muri', animal: 'phukha', history: 'divhazwakale', freedom: 'tshiphiri', democracy: 'demokirasi', rights: 'zwitouwa', today: 'nuvhini', important: 'zwithu zwikulu', remember: 'humbula', example: 'tsiere', chapter: 'tshitanga', }, ts: { greeting: 'U amukeriwa eka dyondzo', students: 'vadyondzi', teacher: 'tisara', classroom: 'kilasini', lesson: 'dyondzo', question: 'xivutiso', answer: 'nxaviso', read: 'hlaya', write: 'tsala', learn: 'dyondza', numbers: 'tinomboro', water: 'matti', sun: 'dzuha', earth: 'misava', plant: 'mbuma', animal: 'xiharhi', history: 'xisiko', freedom: 'ntshuxeko', democracy: 'demokirasi', rights: 'switshovo', today: 'namuntlha', important: 'nkoka', remember: 'khumbula', example: 'xilandza', chapter: 'kavanyisa', }, en: { greeting: 'Welcome to the lesson', students: 'students', teacher: 'teacher', classroom: 'classroom', lesson: 'lesson', question: 'question', answer: 'answer', read: 'read', write: 'write', learn: 'learn', numbers: 'numbers', water: 'water', sun: 'sun', earth: 'earth', plant: 'plant', animal: 'animal', history: 'history', freedom: 'freedom', democracy: 'democracy', rights: 'rights', today: 'today', important: 'important', remember: 'remember', example: 'example', chapter: 'chapter', } }; // --- Sample Lessons --- const SAMPLES = { maths: `Today we are learning about numbers. In this lesson, students will learn to count from 1 to 100. The teacher will show examples of addition and subtraction. Remember: numbers are important in our daily life. Question: What is 5 + 3? Answer: 8. Chapter 1 is about whole numbers.`, science: `Welcome to the lesson on water and the sun. In this chapter, students will learn how water is important for plants and animals on earth. The teacher will explain how the sun helps plants grow. Question: Why is water important? Answer: All living things need water to survive. Remember: the sun gives energy to the earth.`, history: `Today we are learning about freedom and democracy in South Africa. In this lesson, students will learn about the history of our country. The teacher will explain the rights of all people. Question: What is democracy? Answer: Democracy means the people have the power. Remember: freedom is important for everyone. Chapter 3 is about our rights.`, literacy: `Welcome to the lesson! Today students will read and write new words. The teacher will help you learn to read short stories. In this chapter, we practice writing sentences. Question: Can you write your name? Answer: Yes, I can write my name! Remember: reading and writing are important skills for every student.`, }; // --- State --- let selectedLangs = new Set(); let translationHistory = JSON.parse(localStorage.getItem('umkho_history') || '[]'); // --- Initialize --- document.addEventListener('DOMContentLoaded', () => { renderLangGrid(); updateCharCount(); checkOnlineStatus(); // Character count listener document.getElementById('sourceText').addEventListener('input', updateCharCount); // Online/offline listeners window.addEventListener('online', checkOnlineStatus); window.addEventListener('offline', checkOnlineStatus); // Register service worker if ('serviceWorker' in navigator) { // In production, this would register sw.js // navigator.serviceWorker.register('sw.js'); } }); // --- Render Language Grid --- function renderLangGrid() { const grid = document.getElementById('langGrid'); const sourceLang = document.getElementById('sourceLang').value; grid.innerHTML = SA_LANGUAGES .filter(lang => lang.code !== sourceLang) .map(lang => ` `).join(''); document.getElementById('langCount').textContent = `${selectedLangs.size} selected`; } // --- Toggle Language Selection --- function toggleLang(code) { if (selectedLangs.has(code)) { selectedLangs.delete(code); } else { selectedLangs.add(code); } renderLangGrid(); } function selectAllLangs() { const sourceLang = document.getElementById('sourceLang').value; SA_LANGUAGES.forEach(l => { if (l.code !== sourceLang) selectedLangs.add(l.code); }); renderLangGrid(); } function deselectAllLangs() { selectedLangs.clear(); renderLangGrid(); } // --- Update source language and refresh grid --- document.addEventListener('DOMContentLoaded', () => { document.getElementById('sourceLang').addEventListener('change', () => { // Remove source lang from selected targets const src = document.getElementById('sourceLang').value; if (selectedLangs.has(src)) selectedLangs.delete(src); renderLangGrid(); }); }); // --- Character Count --- function updateCharCount() { const text = document.getElementById('sourceText').value; document.getElementById('charCount').textContent = `${text.length} characters`; } // --- Clear Source --- function clearSource() { document.getElementById('sourceText').value = ''; updateCharCount(); document.getElementById('resultsSection').classList.add('hidden'); } // --- Paste from Clipboard --- async function pasteFromClipboard() { try { const text = await navigator.clipboard.readText(); document.getElementById('sourceText').value = text; updateCharCount(); showToast('Text pasted from clipboard'); } catch (err) { showToast('Cannot access clipboard β please paste manually'); } } // --- File Upload --- function handleFileUpload(event) { const file = event.target.files[0]; if (!file) return; if (file.name.endsWith('.txt')) { const reader = new FileReader(); reader.onload = (e) => { document.getElementById('sourceText').value = e.target.result; updateCharCount(); showToast(`Loaded: ${file.name}`); }; reader.readAsText(file); } else { showToast('Please upload a .txt file'); } event.target.value = ''; } // --- Load Sample --- function loadSample(type) { document.getElementById('sourceText').value = SAMPLES[type] || ''; updateCharCount(); showToast('Sample lesson loaded'); } // --- Simulated Translation Engine --- // This simulates the UmkhoAI offline translation engine. // In production, this would call the actual on-device model. function simulateTranslation(text, targetCode) { const dict = TRANSLATIONS[targetCode]; if (!dict) return `[${targetCode}] ` + text; // Smart word-by-word replacement with some phrase detection let result = text; // Sort keys by length (longest first) for better matching const keys = Object.keys(TRANSLATIONS.en).sort((a, b) => b.length - a.length); const srcDict = TRANSLATIONS[document.getElementById('sourceLang').value] || TRANSLATIONS.en; keys.forEach(key => { const englishWord = TRANSLATIONS.en[key]; const targetWord = dict[key]; if (englishWord && targetWord) { // Case-insensitive whole word replacement const regex = new RegExp(`\\b${englishWord}\\b`, 'gi'); result = result.replace(regex, targetWord); } }); // Add language-specific prefix to indicate it's translated const langName = SA_LANGUAGES.find(l => l.code === targetCode)?.name || targetCode; result = `γ${langName}γ\n` + result; return result; } // --- Main Translate Function --- async function translateText() { const sourceText = document.getElementById('sourceText').value.trim(); const sourceLang = document.getElementById('sourceLang').value; if (!sourceText) { showToast('Please enter lesson text to translate'); return; } if (selectedLangs.size === 0) { showToast('Please select at least one target language'); return; } // Show loading const btn = document.getElementById('translateBtn'); btn.disabled = true; const loading = document.getElementById('loadingIndicator'); const progressBar = document.getElementById('progressBar'); const loadingMsg = document.getElementById('loadingMsg'); loading.classList.remove('hidden'); loading.classList.add('flex', 'justify-center'); document.getElementById('resultsSection').classList.add('hidden'); const langs = Array.from(selectedLangs); const speed = document.getElementById('speedSelect')?.value || 'balanced'; const delay = speed === 'fast' ? 200 : speed === 'accurate' ? 600 : 350; const results = []; for (let i = 0; i < langs.length; i++) { const langCode = langs[i]; const langName = SA_LANGUAGES.find(l => l.code === langCode)?.name || langCode; loadingMsg.textContent = `Translating to ${langName}... (${i + 1}/${langs.length})`; progressBar.style.width = `${((i + 0.5) / langs.length) * 100}%`; // Simulate processing delay await new Promise(r => setTimeout(r, delay)); const translated = simulateTranslation(sourceText, langCode); results.push({ code: langCode, name: langName, text: translated }); progressBar.style.width = `${((i + 1) / langs.length) * 100}%`; } // Done loading.classList.add('hidden'); loading.classList.remove('flex', 'justify-center'); btn.disabled = false; // Render results renderResults(results); // Save to history if (document.getElementById('autoSave')?.checked !== false) { saveToHistory(sourceText, results, sourceLang); } showToast(`Translated to ${results.length} language${results.length > 1 ? 's' : ''}!`); } // --- Render Results --- function renderResults(results) { const container = document.getElementById('resultsContainer'); const section = document.getElementById('resultsSection'); container.innerHTML = results.map(r => { const langInfo = SA_LANGUAGES.find(l => l.code === r.code); return `
${escapeHtml(h.sourceText)}
${h.targetLangs.join(', ')}