| |
| |
| |
|
|
| |
| 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: '🇿🇦' }, |
| ]; |
|
|
| |
| |
| |
| 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', |
| } |
| }; |
|
|
| |
| 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.`, |
| }; |
|
|
| |
| let selectedLangs = new Set(); |
| let translationHistory = JSON.parse(localStorage.getItem('umkho_history') || '[]'); |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| renderLangGrid(); |
| updateCharCount(); |
| checkOnlineStatus(); |
|
|
| |
| document.getElementById('sourceText').addEventListener('input', updateCharCount); |
|
|
| |
| window.addEventListener('online', checkOnlineStatus); |
| window.addEventListener('offline', checkOnlineStatus); |
|
|
| |
| if ('serviceWorker' in navigator) { |
| |
| |
| } |
| }); |
|
|
| |
| function renderLangGrid() { |
| const grid = document.getElementById('langGrid'); |
| const sourceLang = document.getElementById('sourceLang').value; |
|
|
| grid.innerHTML = SA_LANGUAGES |
| .filter(lang => lang.code !== sourceLang) |
| .map(lang => ` |
| <button class="lang-chip ${selectedLangs.has(lang.code) ? 'selected' : ''}" |
| onclick="toggleLang('${lang.code}')" |
| aria-pressed="${selectedLangs.has(lang.code)}" |
| aria-label="Select ${lang.name}"> |
| <span class="check-icon"> |
| ${selectedLangs.has(lang.code) ? '<svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>' : ''} |
| </span> |
| <span>${lang.flag} ${lang.name}</span> |
| </button> |
| `).join(''); |
|
|
| document.getElementById('langCount').textContent = `${selectedLangs.size} selected`; |
| } |
|
|
| |
| 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(); |
| } |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| document.getElementById('sourceLang').addEventListener('change', () => { |
| |
| const src = document.getElementById('sourceLang').value; |
| if (selectedLangs.has(src)) selectedLangs.delete(src); |
| renderLangGrid(); |
| }); |
| }); |
|
|
| |
| function updateCharCount() { |
| const text = document.getElementById('sourceText').value; |
| document.getElementById('charCount').textContent = `${text.length} characters`; |
| } |
|
|
| |
| function clearSource() { |
| document.getElementById('sourceText').value = ''; |
| updateCharCount(); |
| document.getElementById('resultsSection').classList.add('hidden'); |
| } |
|
|
| |
| 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'); |
| } |
| } |
|
|
| |
| 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 = ''; |
| } |
|
|
| |
| function loadSample(type) { |
| document.getElementById('sourceText').value = SAMPLES[type] || ''; |
| updateCharCount(); |
| showToast('Sample lesson loaded'); |
| } |
|
|
| |
| |
| |
| function simulateTranslation(text, targetCode) { |
| const dict = TRANSLATIONS[targetCode]; |
| if (!dict) return `[${targetCode}] ` + text; |
|
|
| |
| let result = text; |
|
|
| |
| 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) { |
| |
| const regex = new RegExp(`\\b${englishWord}\\b`, 'gi'); |
| result = result.replace(regex, targetWord); |
| } |
| }); |
|
|
| |
| const langName = SA_LANGUAGES.find(l => l.code === targetCode)?.name || targetCode; |
| result = `【${langName}】\n` + result; |
|
|
| return result; |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| 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}%`; |
|
|
| |
| 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}%`; |
| } |
|
|
| |
| loading.classList.add('hidden'); |
| loading.classList.remove('flex', 'justify-center'); |
| btn.disabled = false; |
|
|
| |
| renderResults(results); |
|
|
| |
| if (document.getElementById('autoSave')?.checked !== false) { |
| saveToHistory(sourceText, results, sourceLang); |
| } |
|
|
| showToast(`Translated to ${results.length} language${results.length > 1 ? 's' : ''}!`); |
| } |
|
|
| |
| 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 ` |
| <div class="result-card"> |
| <div class="result-header bg-sa-greenPale"> |
| <span class="font-bold text-sa-green flex items-center gap-2"> |
| ${langInfo?.flag || '🇿🇦'} ${r.name} |
| </span> |
| <div class="flex gap-1 no-print"> |
| <button onclick="copyTranslation('${r.code}')" class="p-1.5 hover:bg-green-200 rounded-lg transition" title="Copy translation" aria-label="Copy ${r.name} translation"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-sa-green" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> |
| </button> |
| <button onclick="speakTranslation('${r.code}')" class="p-1.5 hover:bg-green-200 rounded-lg transition" title="Read aloud" aria-label="Read ${r.name} translation aloud"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-sa-green" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg> |
| </button> |
| </div> |
| </div> |
| <div class="result-body" id="result-${r.code}">${escapeHtml(r.text)}</div> |
| </div> |
| `; |
| }).join(''); |
|
|
| section.classList.remove('hidden'); |
| section.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
| } |
|
|
| |
| let currentResults = []; |
| const originalRenderResults = renderResults; |
| |
| renderResults = function(results) { |
| currentResults = results; |
| originalRenderResults(results); |
| }; |
|
|
| |
| function copyTranslation(code) { |
| const result = currentResults.find(r => r.code === code); |
| if (result) { |
| navigator.clipboard.writeText(result.text).then(() => { |
| showToast(`${result.name} translation copied!`); |
| }); |
| } |
| } |
|
|
| |
| function copyAllTranslations() { |
| const all = currentResults.map(r => `=== ${r.name} ===\n${r.text}`).join('\n\n'); |
| navigator.clipboard.writeText(all).then(() => { |
| showToast('All translations copied!'); |
| }); |
| } |
|
|
| |
| function speakTranslation(code) { |
| const result = currentResults.find(r => r.code === code); |
| if (!result) return; |
|
|
| if ('speechSynthesis' in window) { |
| window.speechSynthesis.cancel(); |
| const utterance = new SpeechSynthesisUtterance(result.text.replace(/【[^】]+】\n?/, '')); |
| utterance.lang = code === 'af' ? 'af-ZA' : |
| code === 'zu' ? 'zu-ZA' : |
| code === 'xh' ? 'xh-ZA' : |
| code === 'st' ? 'st-ZA' : |
| code === 'tn' ? 'tn-ZA' : |
| code === 'ts' ? 'ts-ZA' : |
| code === 'ss' ? 'ss-ZA' : |
| code === 've' ? 've-ZA' : |
| code === 'nr' ? 'nr-ZA' : |
| code === 'nso' ? 'nso-ZA' : |
| 'en-ZA'; |
| utterance.rate = 0.85; |
| window.speechSynthesis.speak(utterance); |
| showToast(`Reading in ${result.name}...`); |
| } else { |
| showToast('Speech not supported on this device'); |
| } |
| } |
|
|
| |
| function printTranslations() { |
| window.print(); |
| } |
|
|
| |
| function saveToHistory(sourceText, results, sourceLang) { |
| const entry = { |
| id: Date.now(), |
| date: new Date().toLocaleString('en-ZA'), |
| sourceLang, |
| sourceText: sourceText.substring(0, 100) + (sourceText.length > 100 ? '...' : ''), |
| targetLangs: results.map(r => r.name), |
| translations: results, |
| }; |
|
|
| translationHistory.unshift(entry); |
| if (translationHistory.length > 50) translationHistory.pop(); |
| localStorage.setItem('umkho_history', JSON.stringify(translationHistory)); |
| } |
|
|
| |
| function clearHistory() { |
| translationHistory = []; |
| localStorage.removeItem('umkho_history'); |
| renderHistoryList(); |
| showToast('History cleared'); |
| } |
|
|
| |
| function renderHistoryList() { |
| const list = document.getElementById('historyList'); |
| const empty = document.getElementById('historyEmpty'); |
|
|
| if (translationHistory.length === 0) { |
| list.innerHTML = ''; |
| empty.classList.remove('hidden'); |
| return; |
| } |
|
|
| empty.classList.add('hidden'); |
| list.innerHTML = translationHistory.map(h => ` |
| <div class="history-item" onclick="loadFromHistory(${h.id})"> |
| <div class="flex items-center justify-between"> |
| <span class="text-xs text-gray-400">${h.date}</span> |
| <span class="text-xs px-2 py-0.5 bg-sa-greenPale text-sa-green rounded-full font-semibold">${h.targetLangs.length} lang${h.targetLangs.length > 1 ? 's' : ''}</span> |
| </div> |
| <p class="text-sm font-semibold text-gray-700 mt-1 truncate">${escapeHtml(h.sourceText)}</p> |
| <p class="text-xs text-gray-400 mt-0.5">${h.targetLangs.join(', ')}</p> |
| </div> |
| `).join(''); |
| } |
|
|
| |
| function loadFromHistory(id) { |
| const entry = translationHistory.find(h => h.id === id); |
| if (!entry) return; |
|
|
| document.getElementById('sourceLang').value = entry.sourceLang; |
| |
| renderResults(entry.translations); |
| toggleHistory(); |
| showToast('Loaded from history'); |
| } |
|
|
| |
| function toggleHelp() { |
| const modal = document.getElementById('helpModal'); |
| modal.classList.toggle('hidden'); |
| if (!modal.classList.contains('hidden')) { |
| modal.querySelector('div').classList.add('modal-enter'); |
| } |
| } |
|
|
| function toggleHistory() { |
| const modal = document.getElementById('historyModal'); |
| modal.classList.toggle('hidden'); |
| if (!modal.classList.contains('hidden')) { |
| renderHistoryList(); |
| modal.querySelector('div').classList.add('modal-enter'); |
| } |
| } |
|
|
| function toggleSettings() { |
| const modal = document.getElementById('settingsModal'); |
| modal.classList.toggle('hidden'); |
| if (!modal.classList.contains('hidden')) { |
| modal.querySelector('div').classList.add('modal-enter'); |
| } |
| } |
|
|
| |
| function changeFontSize(size) { |
| document.getElementById('fontSizeLabel').textContent = `${size}px`; |
| document.getElementById('sourceText').style.fontSize = `${size}px`; |
| document.querySelectorAll('.result-body').forEach(el => { |
| el.style.fontSize = `${size}px`; |
| }); |
| } |
|
|
| |
| function checkOnlineStatus() { |
| const banner = document.getElementById('offlineBanner'); |
| if (!navigator.onLine) { |
| banner.classList.remove('hidden'); |
| } else { |
| banner.classList.add('hidden'); |
| } |
| } |
|
|
| |
| function showToast(message) { |
| const toast = document.getElementById('toast'); |
| const msg = document.getElementById('toastMsg'); |
| msg.textContent = message; |
| toast.classList.remove('hidden'); |
|
|
| |
| toast.style.animation = 'none'; |
| toast.offsetHeight; |
| toast.style.animation = ''; |
|
|
| clearTimeout(toast._timeout); |
| toast._timeout = setTimeout(() => { |
| toast.classList.add('hidden'); |
| }, 3000); |
| } |
|
|
| |
| function escapeHtml(str) { |
| const div = document.createElement('div'); |
| div.textContent = str; |
| return div.innerHTML; |
| } |
|
|
| |
| document.addEventListener('keydown', (e) => { |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { |
| e.preventDefault(); |
| translateText(); |
| } |
| if (e.key === 'Escape') { |
| document.getElementById('helpModal').classList.add('hidden'); |
| document.getElementById('historyModal').classList.add('hidden'); |
| document.getElementById('settingsModal').classList.add('hidden'); |
| } |
| }); |