|
|
<!DOCTYPE html> |
|
|
<html lang="fr"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Traducteur Universel</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script> |
|
|
tailwind.config = { |
|
|
darkMode: 'class', |
|
|
theme: { |
|
|
extend: { |
|
|
colors: { |
|
|
dark: { |
|
|
100: '#E5E7EB', |
|
|
200: '#D1D5DB', |
|
|
300: '#9CA3AF', |
|
|
400: '#6B7280', |
|
|
500: '#4B5563', |
|
|
600: '#374151', |
|
|
700: '#1F2937', |
|
|
800: '#111827', |
|
|
900: '#0F172A', |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
</script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.language-flag { |
|
|
width: 24px; |
|
|
height: 16px; |
|
|
margin-right: 8px; |
|
|
border-radius: 2px; |
|
|
} |
|
|
.text-input { |
|
|
min-height: 150px; |
|
|
resize: none; |
|
|
} |
|
|
.swap-btn { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.swap-btn:hover { |
|
|
transform: rotate(180deg); |
|
|
} |
|
|
.pulse { |
|
|
animation: pulse 1.5s infinite; |
|
|
} |
|
|
@keyframes pulse { |
|
|
0% { opacity: 1; } |
|
|
50% { opacity: 0.5; } |
|
|
100% { opacity: 1; } |
|
|
} |
|
|
body { |
|
|
transition: background-color 0.3s ease; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gradient-to-br from-dark-800 to-dark-900 min-h-screen text-dark-100"> |
|
|
<div class="container mx-auto px-4 py-12 max-w-4xl"> |
|
|
|
|
|
<div class="flex justify-end mb-4"> |
|
|
<button id="themeToggle" class="p-2 rounded-full bg-dark-700 hover:bg-dark-600 transition"> |
|
|
<i class="fas fa-moon" id="themeIcon"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="text-center mb-10"> |
|
|
<h1 class="text-4xl font-bold text-indigo-400 mb-2">Traducteur Universel</h1> |
|
|
<p class="text-lg text-indigo-300">Traduisez instantanément entre toutes les langues</p> |
|
|
</div> |
|
|
|
|
|
<div class="bg-dark-700 rounded-xl shadow-xl overflow-hidden border border-dark-600"> |
|
|
<div class="flex flex-col md:flex-row"> |
|
|
|
|
|
<div class="w-full md:w-1/2 p-6 border-b md:border-b-0 md:border-r border-dark-600"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<div class="flex items-center"> |
|
|
<div class="relative w-full"> |
|
|
<select id="sourceLanguage" class="w-full p-3 border border-dark-500 rounded-lg appearance-none bg-dark-800 text-dark-100 pr-10 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<option value="auto">Détection automatique</option> |
|
|
<option value="fr">Français</option> |
|
|
<option value="en">Anglais</option> |
|
|
<option value="es">Espagnol</option> |
|
|
<option value="de">Allemand</option> |
|
|
<option value="it">Italien</option> |
|
|
<option value="pt">Portugais</option> |
|
|
<option value="ru">Russe</option> |
|
|
<option value="zh">Chinois</option> |
|
|
<option value="ja">Japonais</option> |
|
|
<option value="ar">Arabe</option> |
|
|
</select> |
|
|
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none"> |
|
|
<i class="fas fa-chevron-down text-dark-400"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<textarea id="sourceText" class="w-full text-input p-4 border border-dark-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-dark-800 text-dark-100 placeholder-dark-400" placeholder="Entrez votre texte ici..."></textarea> |
|
|
<div class="flex justify-between items-center mt-3"> |
|
|
<div class="text-sm text-dark-400" id="sourceCharCount">0/5000</div> |
|
|
<button id="clearSource" class="text-dark-400 hover:text-indigo-400"> |
|
|
<i class="fas fa-times"></i> Effacer |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="w-full md:w-1/2 p-6"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<div class="flex items-center"> |
|
|
<div class="relative w-full"> |
|
|
<select id="targetLanguage" class="w-full p-3 border border-dark-500 rounded-lg appearance-none bg-dark-800 text-dark-100 pr-10 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<option value="fr">Français</option> |
|
|
<option value="en" selected>Anglais</option> |
|
|
<option value="es">Espagnol</option> |
|
|
<option value="de">Allemand</option> |
|
|
<option value="it">Italien</option> |
|
|
<option value="pt">Portugais</option> |
|
|
<option value="ru">Russe</option> |
|
|
<option value="zh">Chinois</option> |
|
|
<option value="ja">Japonais</option> |
|
|
<option value="ar">Arabe</option> |
|
|
</select> |
|
|
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none"> |
|
|
<i class="fas fa-chevron-down text-dark-400"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div id="targetText" class="w-full text-input p-4 border border-dark-600 rounded-lg bg-dark-800 min-h-[150px] text-dark-100"> |
|
|
<div class="text-dark-400 italic">La traduction apparaîtra ici...</div> |
|
|
</div> |
|
|
<div class="flex justify-between items-center mt-3"> |
|
|
<div class="text-sm text-dark-400" id="targetCharCount">0/5000</div> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="copyTranslation" class="text-dark-400 hover:text-indigo-400" title="Copier"> |
|
|
<i class="far fa-copy"></i> |
|
|
</button> |
|
|
<button id="speakTranslation" class="text-dark-400 hover:text-indigo-400" title="Écouter"> |
|
|
<i class="fas fa-volume-up"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-dark-800 px-6 py-4 border-t border-dark-600 flex justify-between items-center"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<button id="swapLanguages" class="swap-btn bg-indigo-600 text-white p-3 rounded-full hover:bg-indigo-500"> |
|
|
<i class="fas fa-exchange-alt"></i> |
|
|
</button> |
|
|
<span class="text-sm text-dark-300">Échanger les langues</span> |
|
|
</div> |
|
|
<button id="translateBtn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-medium py-3 px-6 rounded-lg flex items-center"> |
|
|
<i class="fas fa-language mr-2"></i> Traduire |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mt-10 bg-dark-700 rounded-xl shadow-lg overflow-hidden border border-dark-600"> |
|
|
<div class="p-6"> |
|
|
<h2 class="text-xl font-semibold text-indigo-400 mb-4 flex items-center"> |
|
|
<i class="fas fa-history mr-2"></i> Historique des traductions |
|
|
</h2> |
|
|
<div id="historyList" class="space-y-3 max-h-60 overflow-y-auto"> |
|
|
|
|
|
<div class="text-center text-dark-400 py-4 italic">Aucun historique pour le moment</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const sourceText = document.getElementById('sourceText'); |
|
|
const targetText = document.getElementById('targetText'); |
|
|
const sourceLanguage = document.getElementById('sourceLanguage'); |
|
|
const targetLanguage = document.getElementById('targetLanguage'); |
|
|
const translateBtn = document.getElementById('translateBtn'); |
|
|
const swapLanguages = document.getElementById('swapLanguages'); |
|
|
const clearSource = document.getElementById('clearSource'); |
|
|
const copyTranslation = document.getElementById('copyTranslation'); |
|
|
const speakTranslation = document.getElementById('speakTranslation'); |
|
|
const sourceCharCount = document.getElementById('sourceCharCount'); |
|
|
const targetCharCount = document.getElementById('targetCharCount'); |
|
|
const historyList = document.getElementById('historyList'); |
|
|
const themeToggle = document.getElementById('themeToggle'); |
|
|
const themeIcon = document.getElementById('themeIcon'); |
|
|
let isDarkMode = true; |
|
|
|
|
|
|
|
|
themeToggle.addEventListener('click', function() { |
|
|
isDarkMode = !isDarkMode; |
|
|
if (isDarkMode) { |
|
|
document.documentElement.classList.add('dark'); |
|
|
themeIcon.classList.remove('fa-sun'); |
|
|
themeIcon.classList.add('fa-moon'); |
|
|
} else { |
|
|
document.documentElement.classList.remove('dark'); |
|
|
themeIcon.classList.remove('fa-moon'); |
|
|
themeIcon.classList.add('fa-sun'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
sourceText.addEventListener('input', function() { |
|
|
const count = sourceText.value.length; |
|
|
sourceCharCount.textContent = `${count}/5000`; |
|
|
if (count > 4500) { |
|
|
sourceCharCount.classList.add('text-red-400'); |
|
|
} else { |
|
|
sourceCharCount.classList.remove('text-red-400'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
clearSource.addEventListener('click', function() { |
|
|
sourceText.value = ''; |
|
|
sourceCharCount.textContent = '0/5000'; |
|
|
targetText.innerHTML = '<div class="text-dark-400 italic">La traduction apparaîtra ici...</div>'; |
|
|
targetCharCount.textContent = '0/5000'; |
|
|
}); |
|
|
|
|
|
|
|
|
swapLanguages.addEventListener('click', function() { |
|
|
const tempLang = sourceLanguage.value; |
|
|
sourceLanguage.value = targetLanguage.value; |
|
|
targetLanguage.value = tempLang; |
|
|
|
|
|
|
|
|
if (sourceText.value.trim()) { |
|
|
translateText(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
copyTranslation.addEventListener('click', function() { |
|
|
if (targetText.textContent.trim() && !targetText.querySelector('.italic')) { |
|
|
navigator.clipboard.writeText(targetText.textContent) |
|
|
.then(() => { |
|
|
const originalIcon = copyTranslation.innerHTML; |
|
|
copyTranslation.innerHTML = '<i class="fas fa-check"></i>'; |
|
|
setTimeout(() => { |
|
|
copyTranslation.innerHTML = originalIcon; |
|
|
}, 2000); |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
speakTranslation.addEventListener('click', function() { |
|
|
if (targetText.textContent.trim() && !targetText.querySelector('.italic')) { |
|
|
const utterance = new SpeechSynthesisUtterance(targetText.textContent); |
|
|
utterance.lang = targetLanguage.value; |
|
|
speechSynthesis.speak(utterance); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
let translateTimeout; |
|
|
sourceText.addEventListener('input', function() { |
|
|
clearTimeout(translateTimeout); |
|
|
if (sourceText.value.trim()) { |
|
|
translateTimeout = setTimeout(translateText, 1000); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
translateBtn.addEventListener('click', translateText); |
|
|
|
|
|
|
|
|
function translateText() { |
|
|
const text = sourceText.value.trim(); |
|
|
if (!text) return; |
|
|
|
|
|
|
|
|
targetText.innerHTML = '<div class="flex justify-center items-center h-full"><div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-indigo-500"></div></div>'; |
|
|
translateBtn.disabled = true; |
|
|
translateBtn.classList.add('opacity-75', 'cursor-not-allowed'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
|
|
|
const translatedText = `[Traduction simulée de ${sourceLanguage.value} vers ${targetLanguage.value}]\n${text.split('').reverse().join('')}`; |
|
|
|
|
|
targetText.innerHTML = translatedText; |
|
|
targetCharCount.textContent = `${translatedText.length}/5000`; |
|
|
|
|
|
|
|
|
addToHistory(text, translatedText, sourceLanguage.value, targetLanguage.value); |
|
|
|
|
|
translateBtn.disabled = false; |
|
|
translateBtn.classList.remove('opacity-75', 'cursor-not-allowed'); |
|
|
}, 1500); |
|
|
} |
|
|
|
|
|
|
|
|
function addToHistory(source, translation, fromLang, toLang) { |
|
|
|
|
|
if (historyList.querySelector('.italic')) { |
|
|
historyList.innerHTML = ''; |
|
|
} |
|
|
|
|
|
const historyItem = document.createElement('div'); |
|
|
historyItem.className = 'bg-dark-800 p-3 rounded-lg border border-dark-600 hover:bg-dark-600 transition cursor-pointer'; |
|
|
historyItem.innerHTML = ` |
|
|
<div class="flex justify-between items-start"> |
|
|
<div class="flex-1"> |
|
|
<div class="text-sm font-medium text-dark-100 mb-1">${source.substring(0, 50)}${source.length > 50 ? '...' : ''}</div> |
|
|
<div class="text-sm text-indigo-400">${translation.substring(0, 50)}${translation.length > 50 ? '...' : ''}</div> |
|
|
</div> |
|
|
<div class="text-xs text-dark-400 ml-2">${fromLang} → ${toLang}</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
historyList.insertBefore(historyItem, historyList.firstChild); |
|
|
|
|
|
|
|
|
if (historyList.children.length > 10) { |
|
|
historyList.removeChild(historyList.lastChild); |
|
|
} |
|
|
|
|
|
|
|
|
historyItem.addEventListener('click', function() { |
|
|
sourceText.value = source; |
|
|
sourceCharCount.textContent = `${source.length}/5000`; |
|
|
targetText.innerHTML = translation; |
|
|
targetCharCount.textContent = `${translation.length}/5000`; |
|
|
sourceLanguage.value = fromLang; |
|
|
targetLanguage.value = toLang; |
|
|
}); |
|
|
} |
|
|
}); |
|
|
</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=Bruno2023/unitrad" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |