// Wait for the DOM to be fully loaded before attaching event handlers window.onload = function() { console.log('Window fully loaded, initializing Tarjama app'); // Get navigation elements const textTabLink = document.querySelector('nav ul li a[href="#text-translation"]'); const docTabLink = document.querySelector('nav ul li a[href="#document-translation"]'); const textSection = document.getElementById('text-translation'); const docSection = document.getElementById('document-translation'); // Get form elements const textTranslationForm = document.getElementById('text-translation-form'); const docTranslationForm = document.getElementById('doc-translation-form'); // UI elements const textInput = document.getElementById('text-input'); const textResult = document.getElementById('text-result'); const docResult = document.getElementById('doc-result'); const textOutput = document.getElementById('text-output'); const docOutput = document.getElementById('doc-output'); const docInputText = document.getElementById('doc-input-text'); const textLoadingIndicator = document.getElementById('text-loading'); const docLoadingIndicator = document.getElementById('doc-loading'); const errorMessageElement = document.getElementById('error-message'); const notificationElement = document.getElementById('notification'); const charCountElement = document.getElementById('char-count'); const fileNameDisplay = document.getElementById('file-name-display'); const docFilename = document.getElementById('doc-filename'); const docSourceLang = document.getElementById('doc-source-lang'); // Language selectors const sourceLangText = document.getElementById('source-lang-text'); const targetLangText = document.getElementById('target-lang-text'); const sourceLangDoc = document.getElementById('source-lang-doc'); const targetLangDoc = document.getElementById('target-lang-doc'); // Control buttons const swapLanguages = document.getElementById('swap-languages'); const swapLanguagesDoc = document.getElementById('swap-languages-doc'); const copyTranslation = document.getElementById('copy-translation'); const copyDocTranslation = document.getElementById('copy-doc-translation'); const clearSource = document.getElementById('clear-source'); // Get quick phrases elements const phraseButtons = document.querySelectorAll('.phrase-btn'); // RTL language handling - list of languages that use RTL const rtlLanguages = ['ar', 'he']; // Tab navigation if (textTabLink && docTabLink && textSection && docSection) { textTabLink.addEventListener('click', function(e) { e.preventDefault(); docSection.classList.add('hidden'); textSection.classList.remove('hidden'); textTabLink.parentElement.classList.add('active'); docTabLink.parentElement.classList.remove('active'); }); docTabLink.addEventListener('click', function(e) { e.preventDefault(); textSection.classList.add('hidden'); docSection.classList.remove('hidden'); docTabLink.parentElement.classList.add('active'); textTabLink.parentElement.classList.remove('active'); }); } // Character count if (textInput && charCountElement) { textInput.addEventListener('input', function() { const charCount = textInput.value.length; charCountElement.textContent = charCount; // Add warning class if approaching or exceeding character limit if (charCount > 3000) { charCountElement.className = 'char-count-warning'; } else if (charCount > 2000) { charCountElement.className = 'char-count-approaching'; } else { charCountElement.className = ''; } }); } // Quick phrases implementation if (phraseButtons && phraseButtons.length > 0) { console.log('Found phrase buttons:', phraseButtons.length); phraseButtons.forEach(button => { button.addEventListener('click', function(e) { e.preventDefault(); console.log('Phrase button clicked'); const phrase = this.getAttribute('data-text') || this.getAttribute('data-phrase'); if (phrase && textInput) { console.log('Using phrase:', phrase); // First ensure text section is visible if (textSection.classList.contains('hidden')) { console.log('Switching to text tab'); docSection.classList.add('hidden'); textSection.classList.remove('hidden'); textTabLink.parentElement.classList.add('active'); docTabLink.parentElement.classList.remove('active'); } // Insert the phrase at cursor position, or append to end if (typeof textInput.selectionStart === 'number') { const startPos = textInput.selectionStart; const endPos = textInput.selectionEnd; const currentValue = textInput.value; const spaceChar = currentValue && currentValue[startPos - 1] !== ' ' && startPos > 0 ? ' ' : ''; // Insert phrase at cursor position with space if needed textInput.value = currentValue.substring(0, startPos) + spaceChar + phrase + currentValue.substring(endPos); // Move cursor after inserted phrase textInput.selectionStart = startPos + phrase.length + spaceChar.length; textInput.selectionEnd = textInput.selectionStart; } else { // Fallback for browsers that don't support selection const currentValue = textInput.value; const spaceChar = currentValue && currentValue.length > 0 && currentValue[currentValue.length - 1] !== ' ' ? ' ' : ''; textInput.value += spaceChar + phrase; } // Trigger input event to update character count const inputEvent = new Event('input', { bubbles: true }); textInput.dispatchEvent(inputEvent); // Focus back on the input textInput.focus(); // Auto translate if needed const autoTranslate = this.getAttribute('data-auto-translate') === 'true'; if (autoTranslate && textTranslationForm) { textTranslationForm.dispatchEvent(new Event('submit')); } } }); }); } // Language swap functionality if (swapLanguages && sourceLangText && targetLangText) { swapLanguages.addEventListener('click', function(e) { e.preventDefault(); // Don't swap if source is "auto" (language detection) if (sourceLangText.value === 'auto') { showNotification('Cannot swap when source language is set to auto-detect.'); return; } // Store the current values const sourceValue = sourceLangText.value; const targetValue = targetLangText.value; // Swap the values sourceLangText.value = targetValue; targetLangText.value = sourceValue; // If we have translated text, trigger a new translation in the opposite direction if (textOutput.textContent.trim() !== '') { // Update the input with the current output textInput.value = textOutput.textContent; // Update the character count const inputEvent = new Event('input', { bubbles: true }); textInput.dispatchEvent(inputEvent); // Trigger translation if (textTranslationForm) { textTranslationForm.dispatchEvent(new Event('submit')); } } // Apply RTL styling as needed applyRtlStyling(sourceLangText.value, textInput); applyRtlStyling(targetLangText.value, textOutput); }); } // Document language swap functionality if (swapLanguagesDoc && sourceLangDoc && targetLangDoc) { swapLanguagesDoc.addEventListener('click', function(e) { e.preventDefault(); // Don't swap if source is "auto" (language detection) if (sourceLangDoc.value === 'auto') { showNotification('Cannot swap when source language is set to auto-detect.'); return; } // Store the current values const sourceValue = sourceLangDoc.value; const targetValue = targetLangDoc.value; // Swap the values sourceLangDoc.value = targetValue; targetLangDoc.value = sourceValue; }); } // Apply RTL styling based on language function applyRtlStyling(langCode, element) { if (element) { if (rtlLanguages.includes(langCode)) { element.style.direction = 'rtl'; element.style.textAlign = 'right'; } else { element.style.direction = 'ltr'; element.style.textAlign = 'left'; } } } // Handle language change for proper text direction function handleLanguageChange() { // Set text direction based on selected source language if (sourceLangText && textInput) { applyRtlStyling(sourceLangText.value, textInput); } // Set text direction based on selected target language if (targetLangText && textOutput) { applyRtlStyling(targetLangText.value, textOutput); } if (sourceLangDoc && docInputText) { applyRtlStyling(sourceLangDoc.value, docInputText); } if (targetLangDoc && docOutput) { applyRtlStyling(targetLangDoc.value, docOutput); } } // Add event listeners for language changes if (sourceLangText) sourceLangText.addEventListener('change', handleLanguageChange); if (targetLangText) targetLangText.addEventListener('change', handleLanguageChange); if (sourceLangDoc) sourceLangDoc.addEventListener('change', handleLanguageChange); if (targetLangDoc) targetLangDoc.addEventListener('change', handleLanguageChange); // Copy translation to clipboard functionality if (copyTranslation) { copyTranslation.addEventListener('click', function() { if (textOutput && textOutput.textContent.trim() !== '') { navigator.clipboard.writeText(textOutput.textContent) .then(() => { showNotification('Translation copied to clipboard!'); }) .catch(err => { console.error('Error copying text: ', err); showNotification('Failed to copy text. Please try again.'); }); } }); } // Copy document translation to clipboard if (copyDocTranslation) { copyDocTranslation.addEventListener('click', function() { if (docOutput && docOutput.textContent.trim() !== '') { navigator.clipboard.writeText(docOutput.textContent) .then(() => { showNotification('Document translation copied to clipboard!'); }) .catch(err => { console.error('Error copying document: ', err); showNotification('Failed to copy document. Please try again.'); }); } }); } // Clear text functionality if (clearSource) { clearSource.addEventListener('click', function() { if (textInput) { textInput.value = ''; textOutput.textContent = ''; // Update character count const inputEvent = new Event('input', { bubbles: true }); textInput.dispatchEvent(inputEvent); // Focus back on the input textInput.focus(); } }); } // File input handler - Update UI when file is selected const fileInput = document.getElementById('doc-input'); const translateDocumentBtn = document.querySelector('#doc-translation-form .translate-button'); if (fileInput && fileNameDisplay && translateDocumentBtn) { fileInput.addEventListener('change', function() { if (this.files && this.files.length > 0) { const fileName = this.files[0].name; fileNameDisplay.textContent = `File selected: ${fileName}`; fileNameDisplay.style.display = 'block'; // Make translate button colored translateDocumentBtn.classList.add('active-button'); showNotification('Document uploaded successfully!'); } else { fileNameDisplay.textContent = ''; fileNameDisplay.style.display = 'none'; // Make translate button transparent translateDocumentBtn.classList.remove('active-button'); } }); } // Text translation form submission if (textTranslationForm) { textTranslationForm.addEventListener('submit', function(e) { e.preventDefault(); const text = textInput.value.trim(); if (!text) { showNotification('Please enter text to translate.'); return; } const sourceLang = sourceLangText.value; const targetLang = targetLangText.value; translateText(text, sourceLang, targetLang); }); } // Document translation form submission if (docTranslationForm) { docTranslationForm.addEventListener('submit', function(e) { e.preventDefault(); const fileInput = document.getElementById('doc-input'); if (!fileInput.files || fileInput.files.length === 0) { showError('Please select a document to translate.'); return; } const file = fileInput.files[0]; const sourceLang = sourceLangDoc.value; const targetLang = targetLangDoc.value; translateDocument(file, sourceLang, targetLang); }); } // File drag and drop const fileUploadArea = document.querySelector('.file-upload-area'); if (fileUploadArea && fileInput) { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { fileUploadArea.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ['dragenter', 'dragover'].forEach(eventName => { fileUploadArea.addEventListener(eventName, highlight, false); }); ['dragleave', 'drop'].forEach(eventName => { fileUploadArea.addEventListener(eventName, unhighlight, false); }); function highlight() { fileUploadArea.classList.add('highlight'); } function unhighlight() { fileUploadArea.classList.remove('highlight'); } fileUploadArea.addEventListener('drop', handleDrop, false); function handleDrop(e) { const dt = e.dataTransfer; const files = dt.files; if (files && files.length > 0) { fileInput.files = files; const fileName = files[0].name; fileNameDisplay.textContent = `File selected: ${fileName}`; fileNameDisplay.style.display = 'block'; // Make translate button colored if (translateDocumentBtn) { translateDocumentBtn.classList.add('active-button'); } showNotification('Document uploaded successfully!'); } } } // Text translation function function translateText(text, sourceLang, targetLang) { if (textLoadingIndicator) textLoadingIndicator.style.display = 'block'; if (errorMessageElement) errorMessageElement.style.display = 'none'; // Call the API fetch('/translate/text', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ text: text, source_lang: sourceLang, target_lang: targetLang }), }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.json(); }) .then(data => { if (textLoadingIndicator) textLoadingIndicator.style.display = 'none'; if (data.success === false) { throw new Error(data.error || 'Translation failed with an unknown error'); } // Handle language detection result if present if (data.detected_source_lang && sourceLang === 'auto') { showNotification(`Detected language: ${getLanguageName(data.detected_source_lang)}`); // Optionally update the source language dropdown to show the detected language if (sourceLangText && data.detected_source_lang) { // Just for UI feedback - no need to change the actual value since // we want to keep 'auto' selected for future translations const detectedOption = Array.from(sourceLangText.options).find( option => option.value === data.detected_source_lang ); if (detectedOption) { // Visual indication of detected language sourceLangText.parentElement.setAttribute('data-detected', `Detected: ${detectedOption.text}`); } } } // Show translation result if (textOutput) { textOutput.textContent = data.translated_text; // Apply RTL styling based on target language applyRtlStyling(targetLang, textOutput); } // Show the text result container if it was hidden if (textResult) textResult.style.display = 'block'; }) .catch(error => { console.error('Error during translation:', error); if (textLoadingIndicator) textLoadingIndicator.style.display = 'none'; // Show error message showError(`Translation error: ${error.message}`); }); } // Document translation function function translateDocument(file, sourceLang, targetLang) { if (docLoadingIndicator) docLoadingIndicator.style.display = 'block'; if (errorMessageElement) errorMessageElement.style.display = 'none'; const formData = new FormData(); formData.append('file', file); formData.append('source_lang', sourceLang); formData.append('target_lang', targetLang); fetch('/translate/document', { method: 'POST', body: formData, }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.json(); }) .then(data => { if (docLoadingIndicator) docLoadingIndicator.style.display = 'none'; if (data.success === false) { throw new Error(data.error || 'Document translation failed'); } // Handle language detection result if present if (data.detected_source_lang && sourceLang === 'auto') { showNotification(`Detected document language: ${getLanguageName(data.detected_source_lang)}`); } // Show the original text if (docInputText) { docInputText.textContent = data.original_text; // Apply RTL styling based on source language if (data.detected_source_lang && sourceLang === 'auto') { applyRtlStyling(data.detected_source_lang, docInputText); } else { applyRtlStyling(sourceLang, docInputText); } } // Show the translated text if (docOutput) { docOutput.textContent = data.translated_text; // Apply RTL styling based on target language applyRtlStyling(targetLang, docOutput); } // Show the document result container if (docResult) { docResult.classList.remove('hidden'); docResult.style.display = 'flex'; // Ensure the result is visible } // Update filename and detected language if (docFilename) docFilename.textContent = data.original_filename || file.name; if (docSourceLang) docSourceLang.textContent = (data.detected_source_lang ? getLanguageName(data.detected_source_lang) : getLanguageName(sourceLang)); // Add download button const resultArea = document.querySelector('.document-result-area'); if (resultArea) { // Remove existing download button if there was one const existingDownloadBtn = document.getElementById('download-translated-doc'); if (existingDownloadBtn) { existingDownloadBtn.remove(); } // Create download button const downloadBtn = document.createElement('button'); downloadBtn.id = 'download-translated-doc'; downloadBtn.className = 'download-button'; downloadBtn.innerHTML = ' Download Translation'; // Add event listener for download downloadBtn.addEventListener('click', function() { downloadTranslatedDocument(data.translated_text, file.name, file.type); }); // Append to result area resultArea.appendChild(downloadBtn); } }) .catch(error => { console.error('Error during document translation:', error); if (docLoadingIndicator) docLoadingIndicator.style.display = 'none'; // Show error message showError(`Document translation error: ${error.message}`); }); } // Function to download translated document function downloadTranslatedDocument(content, fileName, fileType) { console.log('Downloading translated document:', fileName, fileType); // Determine the file extension let extension = ''; if (fileName.toLowerCase().endsWith('.pdf')) { extension = '.pdf'; } else if (fileName.toLowerCase().endsWith('.docx')) { extension = '.docx'; } else if (fileName.toLowerCase().endsWith('.txt')) { extension = '.txt'; } else { extension = '.txt'; // Default to txt } // Create file name for translated document const baseName = fileName.substring(0, fileName.lastIndexOf('.')); const translatedFileName = `${baseName}_translated${extension}`; // Show notification that download is starting showNotification('Preparing document for download...'); // For text files, we can download directly from the browser if (extension === '.txt') { const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = translatedFileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showNotification('Document downloaded successfully!'); } else { // For PDF and DOCX files, we need the server to create them fetch('/download/translated-document', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content: content, filename: translatedFileName, original_type: fileType || 'text/plain' }), }) .then(response => { if (!response.ok) { throw new Error(`Server error: ${response.status} ${response.statusText}`); } return response.blob(); }) .then(blob => { // Create appropriate MIME type based on extension let mimeType; if (extension === '.pdf') { mimeType = 'application/pdf'; } else if (extension === '.docx') { mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; } else { mimeType = 'text/plain'; } // Create a blob with the correct MIME type const fileBlob = new Blob([blob], { type: mimeType }); const url = URL.createObjectURL(fileBlob); // Create and trigger download link const a = document.createElement('a'); a.href = url; a.download = translatedFileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showNotification('Document downloaded successfully!'); }) .catch(error => { console.error('Error downloading document:', error); showError(`Download error: ${error.message}`); }); } } // Helper function to get language name from code function getLanguageName(code) { // Hard-coded mapping for common languages const langMap = { 'ar': 'Arabic', 'en': 'English', 'fr': 'French', 'es': 'Spanish', 'de': 'German', 'zh': 'Chinese', 'ru': 'Russian', 'ja': 'Japanese', 'hi': 'Hindi', 'auto': 'Auto-detect' }; // Try to get from our map if (langMap[code]) { return langMap[code]; } // Try to get from the select option text if (sourceLangText) { const option = Array.from(sourceLangText.options).find(opt => opt.value === code); if (option) { return option.text; } } // Fallback to code return code; } // Display notification function showNotification(message) { if (notificationElement) { notificationElement.textContent = message; notificationElement.style.display = 'block'; notificationElement.classList.add('show'); // Hide after 3 seconds setTimeout(() => { notificationElement.classList.remove('show'); setTimeout(() => { notificationElement.style.display = 'none'; }, 300); }, 3000); } } // Display error message function showError(message) { if (errorMessageElement) { errorMessageElement.textContent = message; errorMessageElement.style.display = 'block'; // Hide after 5 seconds setTimeout(() => { errorMessageElement.style.display = 'none'; }, 5000); } } // Initialize by applying RTL styling based on initial language selection handleLanguageChange(); };