Spaces:
Running
Running
<html lang="es"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Traductor Funcional</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.gradient-bg { | |
background: linear-gradient(135deg, #6b73ff 0%, #000dff 100%); | |
} | |
.text-area { | |
min-height: 150px; | |
resize: none; | |
} | |
.swap-btn:hover { | |
transform: rotate(180deg); | |
transition: transform 0.3s ease; | |
} | |
.pulse { | |
animation: pulse 2s infinite; | |
} | |
@keyframes pulse { | |
0% { opacity: 1; } | |
50% { opacity: 0.5; } | |
100% { opacity: 1; } | |
} | |
</style> | |
</head> | |
<body class="gradient-bg min-h-screen flex items-center justify-center p-4"> | |
<div class="w-full max-w-4xl bg-white rounded-xl shadow-2xl overflow-hidden"> | |
<!-- Header --> | |
<div class="bg-indigo-700 text-white p-6"> | |
<h1 class="text-3xl font-bold flex items-center"> | |
<i class="fas fa-language mr-3"></i> | |
Traductor Funcional | |
</h1> | |
<p class="text-indigo-200 mt-1">Traduce texto usando la API de Google Translate</p> | |
</div> | |
<!-- Main Content --> | |
<div class="p-6"> | |
<!-- Language Selection --> | |
<div class="flex items-center justify-between mb-6"> | |
<!-- Source Language --> | |
<div class="flex-1 mr-4"> | |
<div class="relative"> | |
<select id="sourceLanguage" class="w-full p-3 border border-gray-300 rounded-lg appearance-none bg-white cursor-pointer"> | |
<option value="auto">Detectar idioma</option> | |
<option value="es">Español</option> | |
<option value="en">Inglés</option> | |
<option value="fr">Francés</option> | |
<option value="de">Alemán</option> | |
<option value="it">Italiano</option> | |
<option value="pt">Portugués</option> | |
<option value="ru">Ruso</option> | |
<option value="ja">Japonés</option> | |
<option value="zh">Chino</option> | |
</select> | |
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> | |
<i class="fas fa-chevron-down"></i> | |
</div> | |
</div> | |
</div> | |
<!-- Swap Button --> | |
<button id="swapLanguages" class="swap-btn bg-indigo-600 text-white p-3 rounded-full hover:bg-indigo-700 transition-colors duration-300"> | |
<i class="fas fa-exchange-alt"></i> | |
</button> | |
<!-- Target Language --> | |
<div class="flex-1 ml-4"> | |
<div class="relative"> | |
<select id="targetLanguage" class="w-full p-3 border border-gray-300 rounded-lg appearance-none bg-white cursor-pointer"> | |
<option value="es">Español</option> | |
<option value="en" selected>Inglés</option> | |
<option value="fr">Francés</option> | |
<option value="de">Alemán</option> | |
<option value="it">Italiano</option> | |
<option value="pt">Portugués</option> | |
<option value="ru">Ruso</option> | |
<option value="ja">Japonés</option> | |
<option value="zh">Chino</option> | |
</select> | |
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> | |
<i class="fas fa-chevron-down"></i> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Text Areas --> | |
<div class="flex flex-col md:flex-row gap-6"> | |
<!-- Source Text --> | |
<div class="flex-1"> | |
<div class="relative"> | |
<textarea id="sourceText" class="text-area w-full p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" placeholder="Escribe o pega el texto aquí..."></textarea> | |
<div class="absolute bottom-3 right-3 flex space-x-2"> | |
<button id="clearSource" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
<button id="listenSource" class="text-indigo-600 hover:text-indigo-800"> | |
<i class="fas fa-volume-up"></i> | |
</button> | |
</div> | |
</div> | |
<div class="mt-2 text-sm text-gray-500 flex justify-between"> | |
<span id="charCount">0 caracteres</span> | |
</div> | |
</div> | |
<!-- Target Text --> | |
<div class="flex-1"> | |
<div class="relative"> | |
<textarea id="targetText" readonly class="text-area w-full p-4 border border-gray-300 rounded-lg bg-gray-50" placeholder="Traducción aparecerá aquí..."></textarea> | |
<div class="absolute bottom-3 right-3 flex space-x-2"> | |
<button id="copyTranslation" class="text-indigo-600 hover:text-indigo-800" title="Copiar traducción"> | |
<i class="far fa-copy"></i> | |
</button> | |
<button id="listenTranslation" class="text-indigo-600 hover:text-indigo-800"> | |
<i class="fas fa-volume-up"></i> | |
</button> | |
</div> | |
</div> | |
<div class="mt-2 text-sm text-gray-500"> | |
<span id="translationStatus">Listo para traducir</span> | |
</div> | |
</div> | |
</div> | |
<!-- Translate Button --> | |
<div class="mt-6 text-center"> | |
<button id="translateBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-8 rounded-full shadow-lg transition-all duration-300 transform hover:scale-105"> | |
<i class="fas fa-exchange-alt mr-2"></i> Traducir | |
</button> | |
</div> | |
</div> | |
<!-- Footer --> | |
<div class="bg-gray-100 p-4 text-center text-gray-600 text-sm"> | |
<p>Traductor Funcional © 2023 | Usa la API de Google Translate</p> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Elements | |
const sourceLanguage = document.getElementById('sourceLanguage'); | |
const targetLanguage = document.getElementById('targetLanguage'); | |
const sourceText = document.getElementById('sourceText'); | |
const targetText = document.getElementById('targetText'); | |
const translateBtn = document.getElementById('translateBtn'); | |
const swapLanguages = document.getElementById('swapLanguages'); | |
const clearSource = document.getElementById('clearSource'); | |
const copyTranslation = document.getElementById('copyTranslation'); | |
const listenSource = document.getElementById('listenSource'); | |
const listenTranslation = document.getElementById('listenTranslation'); | |
const charCount = document.getElementById('charCount'); | |
const translationStatus = document.getElementById('translationStatus'); | |
// Character counter | |
sourceText.addEventListener('input', function() { | |
const count = sourceText.value.length; | |
charCount.textContent = `${count} caracter${count !== 1 ? 'es' : ''}`; | |
if (count > 5000) { | |
charCount.classList.add('text-red-500'); | |
charCount.classList.remove('text-gray-500'); | |
} else { | |
charCount.classList.add('text-gray-500'); | |
charCount.classList.remove('text-red-500'); | |
} | |
}); | |
// Clear source text | |
clearSource.addEventListener('click', function() { | |
sourceText.value = ''; | |
charCount.textContent = '0 caracteres'; | |
charCount.classList.add('text-gray-500'); | |
charCount.classList.remove('text-red-500'); | |
}); | |
// Copy translation | |
copyTranslation.addEventListener('click', function() { | |
if (targetText.value) { | |
navigator.clipboard.writeText(targetText.value) | |
.then(() => { | |
const originalIcon = copyTranslation.innerHTML; | |
copyTranslation.innerHTML = '<i class="fas fa-check"></i>'; | |
setTimeout(() => { | |
copyTranslation.innerHTML = originalIcon; | |
}, 2000); | |
}); | |
} | |
}); | |
// Swap languages | |
swapLanguages.addEventListener('click', function() { | |
const tempLang = sourceLanguage.value; | |
sourceLanguage.value = targetLanguage.value === 'auto' ? 'es' : targetLanguage.value; | |
targetLanguage.value = tempLang === 'auto' ? 'en' : tempLang; | |
if (sourceText.value && targetText.value) { | |
const tempText = sourceText.value; | |
sourceText.value = targetText.value; | |
targetText.value = tempText; | |
} | |
}); | |
// Text-to-speech | |
listenSource.addEventListener('click', function() { | |
if (sourceText.value.trim()) { | |
speak(sourceText.value, sourceLanguage.value === 'auto' ? 'es' : sourceLanguage.value); | |
} | |
}); | |
listenTranslation.addEventListener('click', function() { | |
if (targetText.value.trim()) { | |
speak(targetText.value, targetLanguage.value); | |
} | |
}); | |
// Translate button | |
translateBtn.addEventListener('click', function() { | |
if (!sourceText.value.trim()) { | |
showError("Por favor ingresa texto para traducir"); | |
return; | |
} | |
if (sourceText.value.length > 5000) { | |
showError("El texto es demasiado largo (máx. 5000 caracteres)"); | |
return; | |
} | |
translationStatus.textContent = "Traduciendo..."; | |
translateBtn.disabled = true; | |
translateBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Traduciendo'; | |
// Usamos la API de Google Translate (método no oficial) | |
translateText( | |
sourceText.value, | |
sourceLanguage.value, | |
targetLanguage.value | |
).then(translatedText => { | |
targetText.value = translatedText; | |
translationStatus.textContent = "Traducción completada"; | |
translateBtn.disabled = false; | |
translateBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> Traducir'; | |
}).catch(error => { | |
showError("Error al traducir: " + error.message); | |
translateBtn.disabled = false; | |
translateBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> Traducir'; | |
}); | |
}); | |
// Helper functions | |
function showError(message) { | |
translationStatus.textContent = message; | |
translationStatus.classList.add('text-red-500'); | |
setTimeout(() => { | |
translationStatus.textContent = "Listo para traducir"; | |
translationStatus.classList.remove('text-red-500'); | |
}, 3000); | |
} | |
function speak(text, lang) { | |
if ('speechSynthesis' in window) { | |
const utterance = new SpeechSynthesisUtterance(text); | |
utterance.lang = lang; | |
window.speechSynthesis.speak(utterance); | |
} else { | |
alert("Tu navegador no soporta la función de texto a voz."); | |
} | |
} | |
// Función principal de traducción usando Google Translate API | |
async function translateText(text, sourceLang, targetLang) { | |
// Si es "auto", no especificamos el idioma origen | |
const sourceParam = sourceLang === 'auto' ? '' : `&sl=${sourceLang}`; | |
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&dt=t${sourceParam}&tl=${targetLang}&q=${encodeURIComponent(text)}`; | |
try { | |
const response = await fetch(url); | |
if (!response.ok) { | |
throw new Error('Error en la API de traducción'); | |
} | |
const data = await response.json(); | |
// La respuesta viene en un formato complejo, extraemos la traducción | |
if (data && Array.isArray(data[0])) { | |
return data[0].map(item => item[0]).join(''); | |
} | |
throw new Error('Formato de respuesta no reconocido'); | |
} catch (error) { | |
console.error('Error al traducir:', error); | |
throw error; | |
} | |
} | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=javarribas/traductor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |