You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, sourceLang = 'eng', targetLang = 'rus') {
/**
* Dynamically loads a UMD script from a URL and resolves with its global variable.
* Caches the result to avoid re-loading.
* @param {string} url The URL of the script to load.
* @param {string} globalName The name of the global variable the script exposes.
* @returns {Promise<any>} A promise that resolves with the library's global object.
*/
const loadScript = (url, globalName) => {
return new Promise((resolve, reject) => {
if (window[globalName]) {
return resolve(window[globalName]);
}
const script = document.createElement('script');
script.src = url;
script.onload = () => {
if (window[globalName]) {
resolve(window[globalName]);
} else {
reject(new Error(`Global ${globalName} not found after loading ${url}`));
}
};
script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
document.head.appendChild(script);
});
};
// 1. DYNAMICALLY IMPORT LIBRARIES
// Load Tesseract.js for OCR
const Tesseract = await loadScript('https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js', 'Tesseract');
// Load Transformers.js for translation (as an ES module)
const { pipeline, env } = await import('https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.1');
// Configure Transformers.js environment
env.allowLocalModels = false; // For security and to ensure models are fetched from HuggingFace
// 2. LANGUAGE MAPPING
// Maps simple language codes to the specific codes required by Tesseract and the translation model.
const langMap = {
'eng': { tesseract: 'eng', nllb: 'eng_Latn' },
'rus': { tesseract: 'rus', nllb: 'rus_Cyrl' },
'deu': { tesseract: 'deu', nllb: 'deu_Latn' }, // German
'fra': { tesseract: 'fra', nllb: 'fra_Latn' }, // French
'spa': { tesseract: 'spa', nllb: 'spa_Latn' }, // Spanish
'jpn': { tesseract: 'jpn', nllb: 'jpn_Jpan' }, // Japanese
'chi_sim': { tesseract: 'chi_sim', nllb: 'zho_Hans' }, // Chinese (Simplified)
};
if (!langMap[sourceLang] || !langMap[targetLang]) {
throw new Error(`Unsupported language. Supported codes: ${Object.keys(langMap).join(', ')}`);
}
const tesseractLang = langMap[sourceLang].tesseract;
const nllbSourceLang = langMap[sourceLang].nllb;
const nllbTargetLang = langMap[targetLang].nllb;
// 3. SETUP CANVAS AND UI CONTAINER
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0);
// Create a container to hold the canvas and a status overlay
const container = document.createElement('div');
container.style.position = 'relative';
container.style.display = 'inline-block';
const statusDiv = document.createElement('div');
statusDiv.style.position = 'absolute';
statusDiv.style.top = '0';
statusDiv.style.left = '0';
statusDiv.style.width = '100%';
statusDiv.style.height = '100%';
statusDiv.style.display = 'flex';
statusDiv.style.alignItems = 'center';
statusDiv.style.justifyContent = 'center';
statusDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
statusDiv.style.color = 'white';
statusDiv.style.fontFamily = 'sans-serif';
statusDiv.style.fontSize = '16px';
statusDiv.style.textAlign = 'center';
statusDiv.style.padding = '20px';
statusDiv.style.boxSizing = 'border-box';
statusDiv.textContent = 'Initializing...';
container.appendChild(canvas);
container.appendChild(statusDiv);
const updateStatus = (text) => {
console.log(text);
statusDiv.innerHTML = text.replace(/\n/g, '<br>');
};
try {
// 4. PERFORM OCR
updateStatus('Initializing OCR engine...');
const worker = await Tesseract.createWorker(tesseractLang, 1, {
logger: m => updateStatus(`OCR: ${m.status}\n(${(m.progress * 100).toFixed(0)}%)`)
});
const { data: { paragraphs } } = await worker.recognize(canvas);
await worker.terminate();
const validParagraphs = paragraphs.filter(p => p.text.trim().length > 1);
if (validParagraphs.length === 0) {
updateStatus("No readable text detected.");
setTimeout(() => { statusDiv.style.display = 'none'; }, 3000);
return container;
}
const textsToTranslate = validParagraphs.map(p => p.text.replace(/\n/g, ' ').trim());
// 5. PERFORM TRANSLATION
updateStatus('Loading translation model...\n(This may take a moment on first use)');
const translator = await pipeline('translation', 'Xenova/nllb-200-distilled-600M', {
progress_callback: p => {
if (p.status === 'progress') {
const progress = (p.progress || 0).toFixed(2);
updateStatus(`Downloading model...\n${p.file}\n${progress}%`);
} else {
updateStatus(`Translation model: ${p.status}`);
}
}
});
updateStatus('Translating detected text...');
const translatedTexts = await translator(textsToTranslate, {
src_lang: nllbSourceLang,
tgt_lang: nllbTargetLang,
});
// 6. RENDER RESULTS
updateStatus('Rendering translation...');
validParagraphs.forEach((p, i) => {
const translatedText = translatedTexts[i].translation_text;
const bbox = p.bbox;
// Erase the original text by drawing a white box over it
ctx.fillStyle = 'white';
ctx.fillRect(bbox.x0, bbox.y0, bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
// Prepare to draw the translated text
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
const boxWidth = bbox.x1 - bbox.x0;
const boxHeight = bbox.y1 - bbox.y0;
// Auto-adjust font size to fit the bounding box
let fontSize = boxHeight * 0.9;
do {
ctx.font = `bold ${fontSize--}px sans-serif`;
} while (ctx.measureText(translatedText).width > boxWidth && fontSize > 8);
// Draw the text vertically centered in the box
ctx.fillText(translatedText, bbox.x0, bbox.y0 + boxHeight / 2);
});
updateStatus('Done!');
setTimeout(() => { statusDiv.style.display = 'none'; }, 2000);
} catch (error) {
console.error('An error occurred during image processing:', error);
updateStatus(`Error: ${error.message}\nCheck console for details.`);
// Don't hide the status div on error
}
return container;
}
Apply Changes