document.addEventListener('DOMContentLoaded', async () => { const translateButton = document.getElementById('translate-button'); const clearButton = document.getElementById('clear-button'); const copyButton = document.getElementById('copy-button'); const downloadButton = document.getElementById('download-button'); const shareButton = document.getElementById('share-button'); const textToTranslate = document.getElementById('text-to-translate'); const sourceLanguage = document.getElementById('source-language'); const outputDiv = document.getElementById('output'); const batchToggle = document.getElementById('batch-toggle'); const langDetectHint = document.getElementById('lang-detect-hint'); const themeToggle = document.getElementById('theme-toggle'); const processDataButton = document.getElementById('process-data-button'); const processDataStatus = document.getElementById('process-data-status'); // Debounce timer for detection let detectTimer = null; const detectDelay = 250; // Populate languages dynamically try { const langRes = await fetch('/languages'); const langData = await langRes.json(); const langs = (langData && langData.supported_languages) || ['nepali', 'sinhala']; sourceLanguage.innerHTML = ''; langs.forEach(l => { const opt = document.createElement('option'); opt.value = l; opt.textContent = l.charAt(0).toUpperCase() + l.slice(1); sourceLanguage.appendChild(opt); }); } catch (e) { // Fallback sourceLanguage.innerHTML = ''; } // Theme toggle // Ensure default gradient theme on first load unless user saved preference (function() { const savedTheme = localStorage.getItem('theme'); if (!savedTheme) { document.documentElement.setAttribute('data-theme', 'gradient'); } })(); themeToggle.addEventListener('click', () => { const html = document.documentElement; const isDark = html.getAttribute('data-theme') === 'dark'; html.setAttribute('data-theme', isDark ? 'light' : 'dark'); themeToggle.textContent = isDark ? 'Light mode' : 'Dark mode'; localStorage.setItem('anuvaad_theme', isDark ? 'light' : 'dark'); }); const savedTheme = localStorage.getItem('anuvaad_theme'); if (savedTheme) { document.documentElement.setAttribute('data-theme', savedTheme); themeToggle.textContent = savedTheme === 'dark' ? 'Dark mode' : 'Light mode'; } function setLoading(isLoading) { translateButton.disabled = isLoading; translateButton.textContent = isLoading ? 'Translating…' : 'Translate'; outputDiv.setAttribute('aria-busy', String(isLoading)); } // Basic language auto-detect by script characters (debounced) textToTranslate.addEventListener('input', () => { clearTimeout(detectTimer); detectTimer = setTimeout(async () => { const sample = (textToTranslate.value || '').slice(0, 200); let detected = ''; // Backend-assisted detection for robustness try { const res = await fetch('/detect', { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': 'application/json' }, body: JSON.stringify({ text: sample }) }); if (res.ok) { const data = await res.json(); detected = data.detected_language || ''; } } catch (e) { // ignore detection errors, fallback to script-based } if (!detected) { const hasDevanagari = /[\u0900-\u097F]/.test(sample); const hasSinhala = /[\u0D80-\u0DFF]/.test(sample); if (hasDevanagari) detected = 'nepali'; else if (hasSinhala) detected = 'sinhala'; } if (detected) { sourceLanguage.value = detected; langDetectHint.textContent = `Detected: ${detected}`; } else { langDetectHint.textContent = ''; } }, detectDelay); }); translateButton.addEventListener('click', async () => { const text = (textToTranslate.value || '').trim(); const lang = sourceLanguage.value; const isBatch = batchToggle && batchToggle.checked; const borrowedFixEl = document.getElementById('borrowed-toggle'); const borrowedFix = borrowedFixEl ? borrowedFixEl.checked : true; outputDiv.innerHTML = ''; if (!text) { outputDiv.innerText = 'Please enter some text to translate.'; return; } setLoading(true); try { let response; if (isBatch) { const texts = text.split('\n').map(t => t.trim()).filter(Boolean); if (texts.length === 0) { outputDiv.innerText = 'Please provide at least one non-empty line for batch translation.'; setLoading(false); return; } response = await fetch('/batch-translate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': 'application/json' }, body: JSON.stringify({ texts, source_language: lang, borrowed_fix: borrowedFix }) }); } else { response = await fetch('/translate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'accept': 'application/json' }, body: JSON.stringify({ text, source_language: lang, borrowed_fix: borrowedFix }) }); } if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || 'An error occurred while translating.'); } const data = await response.json(); if (isBatch) { const results = data.translated_texts || []; const table = document.createElement('table'); table.className = 'result-table'; const thead = document.createElement('thead'); thead.innerHTML = '#SourceTranslation'; table.appendChild(thead); const tbody = document.createElement('tbody'); const sources = text.split('\n').map(t => t.trim()).filter(Boolean); results.forEach((t, idx) => { const tr = document.createElement('tr'); const tdIdx = document.createElement('td'); tdIdx.textContent = String(idx + 1); const tdSrc = document.createElement('td'); tdSrc.textContent = sources[idx] || ''; const tdDst = document.createElement('td'); tdDst.textContent = t; tr.appendChild(tdIdx); tr.appendChild(tdSrc); tr.appendChild(tdDst); tbody.appendChild(tr); }); table.appendChild(tbody); outputDiv.appendChild(table); downloadButton.dataset.csv = toCSV(sources, results); } else { outputDiv.innerText = data.translated_text || data.translation || ''; downloadButton.dataset.csv = toCSV([text], [outputDiv.innerText]); } } catch (error) { outputDiv.innerText = `Error: ${error.message}`; } finally { setLoading(false); } }); // Allow Ctrl+Enter to trigger translation textToTranslate.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { translateButton.click(); } }); // Clear button clearButton.addEventListener('click', () => { textToTranslate.value = ''; outputDiv.innerHTML = ''; downloadButton.removeAttribute('data-csv'); }); // Hide dataset processing controls from users (keep in DOM, but not visible) if (processDataButton) { const datasetControl = processDataButton.closest('.control'); if (datasetControl) datasetControl.hidden = true; const datasetLabel = document.querySelector('label[for="process-data-button"]'); if (datasetLabel) datasetLabel.hidden = true; if (processDataStatus) processDataStatus.hidden = true; } // Hide borrowed words/names UI text while preserving functionality const borrowedFixEl = document.getElementById('borrowed-toggle'); if (borrowedFixEl) { const borrowedControl = borrowedFixEl.closest('.control'); // Keep the control present only during translation flow but hidden from display if (borrowedControl) borrowedControl.hidden = true; const borrowedLabel = borrowedFixEl.closest('label'); if (borrowedLabel) { borrowedLabel.hidden = true; // Remove any visible text nodes to avoid displaying borrowed words/names text borrowedLabel.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE) { node.textContent = ''; } }); } const borrowedHint = borrowedControl ? borrowedControl.querySelector('small.hint') : null; if (borrowedHint) { borrowedHint.hidden = true; borrowedHint.textContent = ''; } // Remove the input element itself to ensure it never appears on screen borrowedFixEl.remove(); } // Helper to trigger dataset processing without user interaction async function triggerProcessData() { if (!processDataStatus) return; try { const res = await fetch('/process-data', { method: 'POST' }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.detail || 'Failed to process dataset'); } const data = await res.json(); // Update hidden status for diagnostics; users won't see it processDataStatus.textContent = `Processed: ${data.processed_files} files, ${data.total_lines} lines`; } catch (e) { processDataStatus.textContent = `Error: ${e.message}`; } } // Automatically process dataset on page load (runs once) triggerProcessData(); // Dataset processing trigger (kept inside DOMContentLoaded for scope safety) if (processDataButton) { processDataButton.addEventListener('click', async () => { // Even if clicked (hidden), keep behavior consistent processDataStatus.textContent = 'Processing dataset…'; try { const res = await fetch('/process-data', { method: 'POST' }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.detail || 'Failed to process dataset'); } const data = await res.json(); processDataStatus.textContent = `Processed: ${data.processed_files} files, ${data.total_lines} lines`; } catch (e) { processDataStatus.textContent = `Error: ${e.message}`; } }); } // Copy button copyButton.addEventListener('click', async () => { const text = outputDiv.innerText || ''; if (!text) return; try { await navigator.clipboard.writeText(text); } catch (e) { // Fallback for older browsers const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } }); // Download CSV downloadButton.addEventListener('click', () => { const csv = downloadButton.dataset.csv; if (!csv) return; const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'translations.csv'; a.click(); URL.revokeObjectURL(url); }); // Share result shareButton.addEventListener('click', async () => { const text = outputDiv.innerText || ''; if (!text) return; try { await navigator.share({ text }); } catch (e) { // Ignore if not supported } }); function toCSV(sources, results) { const rows = sources.map((s, i) => [s, results[i] || '']); const csvRows = rows.map(r => r.map(v => '"' + String(v).replaceAll('"', '""') + '"').join(',')); return 'source,translation\n' + csvRows.join('\n'); } // Theme select const themeSelect = document.getElementById('theme-select'); if (themeSelect) { const saved = localStorage.getItem('theme'); const initial = saved || 'gradient'; document.documentElement.setAttribute('data-theme', initial); themeSelect.value = initial; themeSelect.addEventListener('change', (e) => { const v = e.target.value; document.documentElement.setAttribute('data-theme', v); localStorage.setItem('theme', v); }); } const themeToggleEl = document.getElementById('theme-toggle'); if (themeToggleEl) { themeToggleEl.addEventListener('click', () => { const html = document.documentElement; const isDark = html.getAttribute('data-theme') === 'dark'; html.setAttribute('data-theme', isDark ? 'light' : 'dark'); localStorage.setItem('theme', isDark ? 'light' : 'dark'); }); } });