Translator / index.html
Rizwan1010's picture
Update index.html
4106b79 verified
Raw
History Blame Contribute Delete
24.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<meta
name="description"
content="Free Urdu, Hindi, Roman Urdu and Hinglish to English translator."
/>
<title>Urdu & Hindi to English Translator</title>
<style>
:root {
--primary: #3157f6;
--primary-dark: #2446cc;
--secondary: #7b4dff;
--background: #f4f7fb;
--card: #ffffff;
--text: #172033;
--muted: #667085;
--border: #dce3ef;
--success: #14804a;
--success-bg: #ecfdf3;
--error: #b42318;
--error-bg: #fff4f2;
--warning-bg: #fffaeb;
--warning-border: #fedf89;
--warning-text: #7a4d00;
--shadow: 0 18px 60px rgba(35, 55, 95, 0.12);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 10% 0%, #dfe8ff, transparent 34%),
var(--background);
color: var(--text);
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
button,
textarea,
select,
input {
font: inherit;
}
main {
width: min(1120px, calc(100% - 28px));
margin: 28px auto;
}
.header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
}
.logo {
width: 52px;
height: 52px;
display: grid;
place-items: center;
flex-shrink: 0;
border-radius: 16px;
background: linear-gradient(
135deg,
var(--primary),
var(--secondary)
);
color: #ffffff;
font-size: 25px;
font-weight: 800;
box-shadow: 0 12px 30px rgba(49, 87, 246, 0.25);
}
h1 {
margin: 0;
font-size: clamp(1.55rem, 3vw, 2.3rem);
line-height: 1.15;
}
.subtitle {
margin-top: 5px;
color: var(--muted);
}
.card {
padding: 22px;
background: var(--card);
border: 1px solid var(--border);
border-radius: 24px;
box-shadow: var(--shadow);
}
.translator-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
}
.field {
display: flex;
flex-direction: column;
gap: 9px;
}
.field-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
label {
font-weight: 800;
}
.small-label {
color: var(--muted);
font-size: 0.85rem;
font-weight: 600;
}
textarea,
select {
width: 100%;
color: var(--text);
background: #ffffff;
border: 1px solid var(--border);
outline: none;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
}
textarea {
min-height: 320px;
padding: 16px;
border-radius: 16px;
resize: vertical;
line-height: 1.65;
}
select {
width: auto;
min-width: 155px;
padding: 11px 13px;
border-radius: 12px;
}
textarea:focus,
select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(49, 87, 246, 0.1);
}
textarea[readonly] {
background: #fbfcff;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 9px;
margin-top: 12px;
}
button,
.file-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
min-height: 42px;
padding: 10px 15px;
border: 0;
border-radius: 12px;
font-weight: 800;
text-decoration: none;
cursor: pointer;
transition:
transform 0.15s ease,
background-color 0.15s ease,
opacity 0.15s ease;
}
button:hover,
.file-button:hover {
transform: translateY(-1px);
}
button:disabled {
opacity: 0.55;
cursor: not-allowed;
transform: none;
}
.primary-button {
color: #ffffff;
background: var(--primary);
}
.primary-button:hover {
background: var(--primary-dark);
}
.secondary-button,
.file-button {
color: #344054;
background: #eef2f8;
}
.danger-button {
color: var(--error);
background: #fff0ee;
}
input[type="file"] {
display: none;
}
.metadata {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 15px;
}
.pill {
padding: 7px 11px;
color: #475467;
background: #f2f4f7;
border-radius: 999px;
font-size: 0.84rem;
font-weight: 700;
}
.status {
margin-top: 16px;
padding: 12px 14px;
color: var(--muted);
background: #f8fafc;
border: 1px solid var(--border);
border-radius: 12px;
line-height: 1.5;
}
.status.success {
color: var(--success);
background: var(--success-bg);
border-color: #abefc6;
}
.status.error {
color: var(--error);
background: var(--error-bg);
border-color: #fecdca;
}
.microphone-section {
margin-top: 19px;
padding-top: 19px;
border-top: 1px solid var(--border);
}
.microphone-section h2 {
margin: 0 0 6px;
font-size: 1.05rem;
}
.microphone-section p {
margin: 0;
color: var(--muted);
line-height: 1.5;
}
.notice {
margin-top: 18px;
padding: 14px;
color: var(--warning-text);
background: var(--warning-bg);
border: 1px solid var(--warning-border);
border-radius: 13px;
line-height: 1.55;
}
.footer {
margin-top: 18px;
color: var(--muted);
text-align: center;
font-size: 0.88rem;
}
@media (max-width: 800px) {
.translator-grid {
grid-template-columns: 1fr;
}
textarea {
min-height: 230px;
}
.card {
padding: 15px;
border-radius: 18px;
}
.field-header {
align-items: flex-start;
flex-direction: column;
}
select {
width: 100%;
}
}
@media (max-width: 480px) {
main {
width: min(100% - 18px, 1120px);
margin: 16px auto;
}
.header {
align-items: flex-start;
}
.logo {
width: 46px;
height: 46px;
}
button,
.file-button {
width: 100%;
}
}
</style>
</head>
<body>
<main>
<header class="header">
<div class="logo" aria-hidden="true"></div>
<div>
<h1>AI English Translator</h1>
<div class="subtitle">
Urdu · Hindi · Roman Urdu · Hinglish → English
</div>
</div>
</header>
<section class="card">
<div class="translator-grid">
<div class="field">
<div class="field-header">
<label for="sourceText">Original text</label>
<span class="small-label">
Enter or dictate your message
</span>
</div>
<textarea
id="sourceText"
dir="auto"
maxlength="5000"
placeholder="اردو، हिंदी, Roman Urdu ya Hinglish yahan likhein..."
></textarea>
<div class="controls">
<select
id="sourceLanguage"
aria-label="Select source language"
>
<option value="auto">Auto detect</option>
<option value="ur">Urdu</option>
<option value="hi">Hindi</option>
</select>
<label class="file-button" for="textFile">
Import TXT
</label>
<input
id="textFile"
type="file"
accept=".txt,text/plain"
/>
</div>
</div>
<div class="field">
<div class="field-header">
<label for="translatedText">
English translation
</label>
<span class="small-label">
Generated translation
</span>
</div>
<textarea
id="translatedText"
readonly
placeholder="Your English translation will appear here..."
></textarea>
<div class="controls">
<button
id="translateButton"
class="primary-button"
type="button"
>
Translate
</button>
<button
id="copyButton"
class="secondary-button"
type="button"
>
Copy
</button>
<button
id="downloadButton"
class="secondary-button"
type="button"
>
Download
</button>
<button
id="clearButton"
class="danger-button"
type="button"
>
Clear
</button>
</div>
</div>
</div>
<div class="metadata">
<span id="detectedLanguage" class="pill">
Detected: —
</span>
<span id="characterCount" class="pill">
0 characters
</span>
</div>
<div
id="statusMessage"
class="status"
role="status"
aria-live="polite"
>
Ready.
</div>
<section class="microphone-section">
<h2>Browser microphone dictation</h2>
<p>
Speak into your microphone and the browser will add the
transcript to the original text box.
</p>
<div class="controls">
<select
id="speechLanguage"
aria-label="Select microphone language"
>
<option value="ur-PK">Urdu — Pakistan</option>
<option value="hi-IN">Hindi — India</option>
<option value="en-IN">Hinglish — India</option>
</select>
<button
id="startMicrophoneButton"
class="primary-button"
type="button"
>
Start microphone
</button>
<button
id="stopMicrophoneButton"
class="danger-button"
type="button"
disabled
>
Stop microphone
</button>
</div>
</section>
<div class="notice">
<strong>Static Space edition:</strong>
this version supports browser-based text translation,
microphone dictation and TXT imports. Audio files, videos,
PDFs, DOCX files and image OCR require the full Python
application. Translation is sent to a third-party public
translation service, so do not enter confidential information.
</div>
</section>
<footer class="footer">
Free browser-based Urdu, Hindi, Roman Urdu and Hinglish
translator.
</footer>
</main>
<script>
"use strict";
const sourceText = document.getElementById("sourceText");
const translatedText =
document.getElementById("translatedText");
const sourceLanguage =
document.getElementById("sourceLanguage");
const speechLanguage =
document.getElementById("speechLanguage");
const textFile = document.getElementById("textFile");
const translateButton =
document.getElementById("translateButton");
const copyButton =
document.getElementById("copyButton");
const downloadButton =
document.getElementById("downloadButton");
const clearButton =
document.getElementById("clearButton");
const startMicrophoneButton =
document.getElementById("startMicrophoneButton");
const stopMicrophoneButton =
document.getElementById("stopMicrophoneButton");
const detectedLanguage =
document.getElementById("detectedLanguage");
const characterCount =
document.getElementById("characterCount");
const statusMessage =
document.getElementById("statusMessage");
const MAX_FILE_SIZE = 1024 * 1024;
const MAX_TRANSLATION_LENGTH = 5000;
const CHUNK_LENGTH = 450;
function updateStatus(message, type = "") {
statusMessage.textContent = message;
statusMessage.className = "status";
if (type === "success") {
statusMessage.classList.add("success");
}
if (type === "error") {
statusMessage.classList.add("error");
}
}
function updateCharacterCount() {
const count = sourceText.value.length;
characterCount.textContent =
`${count.toLocaleString()} characters`;
}
function detectInputLanguage(text) {
const trimmedText = text.trim();
if (!trimmedText) {
return {
code: "ur",
label: "Unknown"
};
}
const urduCharacters =
trimmedText.match(/[\u0600-\u06FF]/g) || [];
const hindiCharacters =
trimmedText.match(/[\u0900-\u097F]/g) || [];
if (urduCharacters.length > hindiCharacters.length) {
return {
code: "ur",
label: "Urdu"
};
}
if (hindiCharacters.length > urduCharacters.length) {
return {
code: "hi",
label: "Hindi"
};
}
const romanUrduWords = [
"aap",
"ap",
"main",
"mein",
"mujhe",
"mera",
"meri",
"mere",
"hum",
"ham",
"hai",
"hain",
"ho",
"tha",
"thi",
"nahi",
"nahin",
"kya",
"kyun",
"kaise",
"kab",
"kahan",
"karna",
"karo",
"karen",
"chahiye",
"acha",
"achha",
"theek",
"lekin",
"aur",
"wala",
"wali",
"liye",
"bohat",
"bahut"
];
const normalizedWords = trimmedText
.toLowerCase()
.replace(/[^\p{L}\p{N}\s]/gu, " ")
.split(/\s+/)
.filter(Boolean);
const romanMatchCount = normalizedWords.filter(
(word) => romanUrduWords.includes(word)
).length;
if (romanMatchCount >= 1) {
return {
code: "ur",
label: "Roman Urdu / Hinglish"
};
}
return {
code: "ur",
label: "Roman or unknown"
};
}
function splitTextIntoChunks(text, maximumLength) {
const chunks = [];
let remainingText = text.trim();
while (remainingText.length > maximumLength) {
let splitPosition =
remainingText.lastIndexOf(" ", maximumLength);
if (splitPosition < maximumLength * 0.5) {
splitPosition = maximumLength;
}
chunks.push(
remainingText.slice(0, splitPosition).trim()
);
remainingText =
remainingText.slice(splitPosition).trim();
}
if (remainingText) {
chunks.push(remainingText);
}
return chunks;
}
function decodeHtmlEntities(value) {
const textArea = document.createElement("textarea");
textArea.innerHTML = value;
return textArea.value;
}
async function translateChunk(text, languageCode) {
const endpoint =
"https://api.mymemory.translated.net/get";
const parameters = new URLSearchParams({
q: text,
langpair: `${languageCode}|en`
});
const response = await fetch(
`${endpoint}?${parameters.toString()}`
);
if (!response.ok) {
throw new Error(
`Translation service returned status ${response.status}.`
);
}
const data = await response.json();
const translation =
data?.responseData?.translatedText;
if (!translation) {
throw new Error(
data?.responseDetails ||
"The translation service returned no text."
);
}
return decodeHtmlEntities(translation);
}
async function translateText() {
const originalText = sourceText.value.trim();
if (!originalText) {
updateStatus(
"Please enter or dictate some text first.",
"error"
);
sourceText.focus();
return;
}
if (originalText.length > MAX_TRANSLATION_LENGTH) {
updateStatus(
`Please limit the text to ${MAX_TRANSLATION_LENGTH.toLocaleString()} characters.`,
"error"
);
return;
}
const detected = detectInputLanguage(originalText);
const selectedLanguage =
sourceLanguage.value === "auto"
? detected.code
: sourceLanguage.value;
detectedLanguage.textContent =
`Detected: ${detected.label}`;
translateButton.disabled = true;
translatedText.value = "";
updateStatus("Translating your text...");
try {
const chunks = splitTextIntoChunks(
originalText,
CHUNK_LENGTH
);
const completedTranslations = [];
for (let index = 0; index < chunks.length; index += 1) {
updateStatus(
`Translating section ${index + 1} of ${chunks.length}...`
);
const translation = await translateChunk(
chunks[index],
selectedLanguage
);
completedTranslations.push(translation);
}
translatedText.value =
completedTranslations
.join(" ")
.replace(/\s+/g, " ")
.trim();
updateStatus(
"Translation completed successfully.",
"success"
);
} catch (error) {
console.error(error);
updateStatus(
`Translation failed: ${error.message}`,
"error"
);
} finally {
translateButton.disabled = false;
}
}
async function copyTranslation() {
const value = translatedText.value.trim();
if (!value) {
updateStatus(
"There is no translation to copy.",
"error"
);
return;
}
try {
await navigator.clipboard.writeText(value);
updateStatus(
"Translation copied to the clipboard.",
"success"
);
} catch (error) {
translatedText.select();
const copied = document.execCommand("copy");
if (copied) {
updateStatus(
"Translation copied to the clipboard.",
"success"
);
} else {
updateStatus(
"The browser could not copy the translation.",
"error"
);
}
}
}
function downloadTranslation() {
const value = translatedText.value.trim();
if (!value) {
updateStatus(
"There is no translation to download.",
"error"
);
return;
}
const blob = new Blob(
[value],
{
type: "text/plain;charset=utf-8"
}
);
const objectUrl = URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = objectUrl;
downloadLink.download = "english_translation.txt";
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
URL.revokeObjectURL(objectUrl);
updateStatus(
"Translation downloaded.",
"success"
);
}
function clearApplication() {
sourceText.value = "";
translatedText.value = "";
textFile.value = "";
detectedLanguage.textContent = "Detected: —";
updateCharacterCount();
updateStatus("Cleared.");
sourceText.focus();
}
async function importTextFile(event) {
const file = event.target.files?.[0];
if (!file) {
return;
}
if (file.size > MAX_FILE_SIZE) {
updateStatus(
"The TXT file is too large. Maximum size is 1 MB.",
"error"
);
textFile.value = "";
return;
}
const fileName = file.name.toLowerCase();
if (
file.type !== "text/plain" &&
!fileName.endsWith(".txt")
) {
updateStatus(
"Please upload a valid TXT file.",
"error"
);
textFile.value = "";
return;
}
try {
const content = await file.text();
if (!content.trim()) {
throw new Error("The selected TXT file is empty.");
}
sourceText.value =
content.slice(0, MAX_TRANSLATION_LENGTH);
updateCharacterCount();
const detected = detectInputLanguage(
sourceText.value
);
detectedLanguage.textContent =
`Detected: ${detected.label}`;
updateStatus(
"TXT file loaded successfully.",
"success"
);
} catch (error) {
updateStatus(
`Could not read the file: ${error.message}`,
"error"
);
}
}
sourceText.addEventListener(
"input",
updateCharacterCount
);
sourceText.addEventListener(
"keydown",
(event) => {
if (
(event.ctrlKey || event.metaKey) &&
event.key === "Enter"
) {
translateText();
}
}
);
translateButton.addEventListener(
"click",
translateText
);
copyButton.addEventListener(
"click",
copyTranslation
);
downloadButton.addEventListener(
"click",
downloadTranslation
);
clearButton.addEventListener(
"click",
clearApplication
);
textFile.addEventListener(
"change",
importTextFile
);
const SpeechRecognition =
window.SpeechRecognition ||
window.webkitSpeechRecognition;
let speechRecognition = null;
if (SpeechRecognition) {
speechRecognition = new SpeechRecognition();
speechRecognition.continuous = true;
speechRecognition.interimResults = true;
let finalTranscript = "";
speechRecognition.addEventListener(
"start",
() => {
finalTranscript = "";
startMicrophoneButton.disabled = true;
stopMicrophoneButton.disabled = false;
updateStatus(
"Listening through the microphone..."
);
}
);
speechRecognition.addEventListener(
"result",
(event) => {
let interimTranscript = "";
for (
let index = event.resultIndex;
index < event.results.length;
index += 1
) {
const transcript =
event.results[index][0].transcript;
if (event.results[index].isFinal) {
finalTranscript += `${transcript} `;
} else {
interimTranscript += transcript;
}
}
if (finalTranscript.trim()) {
const existingText =
sourceText.value.trim();
sourceText.value =
`${existingText}${existingText ? " " : ""}${finalTranscript.trim()}`;
finalTranscript = "";
updateCharacterCount();
}
if (interimTranscript) {
updateStatus(
`Listening: ${interimTranscript}`
);
}
}
);
speechRecognition.addEventListener(
"error",
(event) => {
updateStatus(
`Microphone error: ${event.error}`,
"error"
);
}
);
speechRecognition.addEventListener(
"end",
() => {
startMicrophoneButton.disabled = false;
stopMicrophoneButton.disabled = true;
updateStatus("Microphone stopped.");
}
);
startMicrophoneButton.addEventListener(
"click",
() => {
try {
speechRecognition.lang =
speechLanguage.value;
speechRecognition.start();
} catch (error) {
updateStatus(
`Could not start microphone: ${error.message}`,
"error"
);
}
}
);
stopMicrophoneButton.addEventListener(
"click",
() => {
speechRecognition.stop();
}
);
} else {
startMicrophoneButton.disabled = true;
stopMicrophoneButton.disabled = true;
updateStatus(
"Microphone dictation is not supported by this browser. Text translation is still available."
);
}
updateCharacterCount();
</script>
</body>
</html>