256 lines
8.5 KiB
JavaScript
256 lines
8.5 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function() {
|
|
// DOM elements
|
|
const sourceLanguage = document.getElementById('sourceLanguage');
|
|
const targetLanguage = document.getElementById('targetLanguage');
|
|
const swapButton = document.getElementById('swapLanguages');
|
|
const sourceText = document.getElementById('sourceText');
|
|
const translatedText = document.getElementById('translatedText');
|
|
const recordSourceButton = document.getElementById('recordSource');
|
|
const speakButton = document.getElementById('speak');
|
|
const clearSourceButton = document.getElementById('clearSource');
|
|
const copyTranslationButton = document.getElementById('copyTranslation');
|
|
const translateButton = document.getElementById('translateButton');
|
|
const statusMessage = document.getElementById('status');
|
|
|
|
// Audio recording variables
|
|
let mediaRecorder;
|
|
let audioChunks = [];
|
|
let isRecording = false;
|
|
|
|
// Speech recognition setup
|
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
const recognition = SpeechRecognition ? new SpeechRecognition() : null;
|
|
|
|
if (recognition) {
|
|
recognition.continuous = false;
|
|
recognition.interimResults = false;
|
|
}
|
|
|
|
// Event listeners
|
|
swapButton.addEventListener('click', swapLanguages);
|
|
translateButton.addEventListener('click', translateText);
|
|
clearSourceButton.addEventListener('click', clearSource);
|
|
copyTranslationButton.addEventListener('click', copyTranslation);
|
|
|
|
if (recognition) {
|
|
recordSourceButton.addEventListener('click', toggleRecording);
|
|
} else {
|
|
recordSourceButton.textContent = "Speech API not supported";
|
|
recordSourceButton.disabled = true;
|
|
}
|
|
|
|
speakButton.addEventListener('click', speakTranslation);
|
|
|
|
// Functions (continued)
|
|
function swapLanguages() {
|
|
const tempLang = sourceLanguage.value;
|
|
sourceLanguage.value = targetLanguage.value;
|
|
targetLanguage.value = tempLang;
|
|
|
|
// Also swap the text if both fields have content
|
|
if (sourceText.value && translatedText.value) {
|
|
const tempText = sourceText.value;
|
|
sourceText.value = translatedText.value;
|
|
translatedText.value = tempText;
|
|
}
|
|
}
|
|
|
|
function clearSource() {
|
|
sourceText.value = '';
|
|
updateStatus('');
|
|
}
|
|
|
|
function copyTranslation() {
|
|
if (!translatedText.value) {
|
|
updateStatus('Nothing to copy', 'error');
|
|
return;
|
|
}
|
|
|
|
navigator.clipboard.writeText(translatedText.value)
|
|
.then(() => {
|
|
updateStatus('Copied to clipboard!', 'success');
|
|
setTimeout(() => updateStatus(''), 2000);
|
|
})
|
|
.catch(err => {
|
|
updateStatus('Failed to copy: ' + err, 'error');
|
|
});
|
|
}
|
|
|
|
async function translateText() {
|
|
const source = sourceText.value.trim();
|
|
if (!source) {
|
|
updateStatus('Please enter or speak some text to translate', 'error');
|
|
return;
|
|
}
|
|
|
|
updateStatus('Translating...');
|
|
translatedText.value = '';
|
|
|
|
try {
|
|
const response = await fetch('/translate', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
sourceLanguage: sourceLanguage.value,
|
|
targetLanguage: targetLanguage.value,
|
|
text: source
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
translatedText.value = data.translation;
|
|
updateStatus('Translation complete', 'success');
|
|
setTimeout(() => updateStatus(''), 2000);
|
|
} else {
|
|
updateStatus(data.error || 'Translation failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
updateStatus('Network error: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function toggleRecording() {
|
|
if (!recognition) {
|
|
updateStatus('Speech recognition not supported in this browser', 'error');
|
|
return;
|
|
}
|
|
|
|
if (isRecording) {
|
|
stopRecording();
|
|
} else {
|
|
startRecording();
|
|
}
|
|
}
|
|
|
|
function startRecording() {
|
|
sourceText.value = '';
|
|
updateStatus('Listening...');
|
|
|
|
recognition.lang = getLanguageCode(sourceLanguage.value);
|
|
recognition.onresult = function(event) {
|
|
const transcript = event.results[0][0].transcript;
|
|
sourceText.value = transcript;
|
|
updateStatus('Recording completed', 'success');
|
|
setTimeout(() => updateStatus(''), 2000);
|
|
};
|
|
|
|
recognition.onerror = function(event) {
|
|
updateStatus('Error in speech recognition: ' + event.error, 'error');
|
|
stopRecording();
|
|
};
|
|
|
|
recognition.onend = function() {
|
|
stopRecording();
|
|
};
|
|
|
|
try {
|
|
recognition.start();
|
|
isRecording = true;
|
|
recordSourceButton.classList.add('recording');
|
|
recordSourceButton.querySelector('.button-text').textContent = 'Stop';
|
|
} catch (error) {
|
|
updateStatus('Failed to start recording: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function stopRecording() {
|
|
if (isRecording) {
|
|
try {
|
|
recognition.stop();
|
|
} catch (error) {
|
|
console.error('Error stopping recognition:', error);
|
|
}
|
|
|
|
isRecording = false;
|
|
recordSourceButton.classList.remove('recording');
|
|
recordSourceButton.querySelector('.button-text').textContent = 'Record';
|
|
}
|
|
}
|
|
|
|
function speakTranslation() {
|
|
const text = translatedText.value.trim();
|
|
if (!text) {
|
|
updateStatus('No translation to speak', 'error');
|
|
return;
|
|
}
|
|
|
|
// Use the browser's speech synthesis API
|
|
const speech = new SpeechSynthesisUtterance(text);
|
|
speech.lang = getLanguageCode(targetLanguage.value);
|
|
speech.volume = 1;
|
|
speech.rate = 1;
|
|
speech.pitch = 1;
|
|
|
|
speech.onstart = function() {
|
|
updateStatus('Speaking...');
|
|
speakButton.disabled = true;
|
|
};
|
|
|
|
speech.onend = function() {
|
|
updateStatus('');
|
|
speakButton.disabled = false;
|
|
};
|
|
|
|
speech.onerror = function(event) {
|
|
updateStatus('Speech synthesis error: ' + event.error, 'error');
|
|
speakButton.disabled = false;
|
|
};
|
|
|
|
window.speechSynthesis.speak(speech);
|
|
}
|
|
|
|
function getLanguageCode(language) {
|
|
// Map language names to BCP 47 language tags for speech recognition/synthesis
|
|
const languageMap = {
|
|
"arabic": "ar-SA",
|
|
"armenian": "hy-AM",
|
|
"azerbaijani": "az-AZ",
|
|
"english": "en-US",
|
|
"french": "fr-FR",
|
|
"georgian": "ka-GE",
|
|
"kazakh": "kk-KZ",
|
|
"mandarin": "zh-CN",
|
|
"persian": "fa-IR",
|
|
"portuguese": "pt-PT",
|
|
"russian": "ru-RU",
|
|
"turkish": "tr-TR",
|
|
"uzbek": "uz-UZ"
|
|
};
|
|
|
|
return languageMap[language] || 'en-US';
|
|
}
|
|
|
|
function updateStatus(message, type = '') {
|
|
statusMessage.textContent = message;
|
|
statusMessage.className = 'status-message';
|
|
|
|
if (type) {
|
|
statusMessage.classList.add(type);
|
|
}
|
|
}
|
|
|
|
// Check for microphone and speech support when page loads
|
|
function checkSupportedFeatures() {
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
updateStatus('Microphone access is not supported in this browser', 'error');
|
|
recordSourceButton.disabled = true;
|
|
}
|
|
|
|
if (!window.SpeechRecognition && !window.webkitSpeechRecognition) {
|
|
updateStatus('Speech recognition is not supported in this browser', 'error');
|
|
recordSourceButton.disabled = true;
|
|
}
|
|
|
|
if (!window.speechSynthesis) {
|
|
updateStatus('Speech synthesis is not supported in this browser', 'error');
|
|
speakButton.disabled = true;
|
|
}
|
|
}
|
|
|
|
checkSupportedFeatures();
|
|
});
|