Toggle translation without losing original formatting, resilient button injection via Edge Microsoft API
// ==UserScript==
// @name Translator bilingual (Microsoft Engine)
// @namespace http://tampermonkey.net/
// @version 21.03.2026
// @description Toggle translation without losing original formatting, resilient button injection via Edge Microsoft API
// @author Sspuramcopigemi
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @run-at document-start
// @connect edge.microsoft.com
// ==/UserScript==
(function() {
'use strict';
let modeActive = false;
const isMarathi = (text) => /[\u0900-\u097F]/.test(text);
async function translateText(text, targetLang) {
return new Promise((resolve) => {
// Replaced with the Microsoft Edge Free Translation Endpoint
const url = `https://edge.microsoft.com/translate/auth`;
// Step 1: Microsoft requires a quick handshake request to issue a temporary token
GM_xmlhttpRequest({
method: "GET",
url: url,
timeout: 3000,
onload: (tokenRes) => {
const token = tokenRes.responseText;
if (!token) { resolve(null); return; }
// Step 2: Use the token to fetch the translation from Microsoft engine
const translateUrl = `https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=${targetLang}`;
GM_xmlhttpRequest({
method: "POST",
url: translateUrl,
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
data: JSON.stringify([{ "Text": text }]),
timeout: 5000,
onload: (res) => {
try {
const result = JSON.parse(res.responseText);
// Microsoft API format returns data as: [{ translations: [{ text: "..." }] }]
resolve(result[0].translations[0].text || null);
} catch (e) { resolve(null); }
},
onerror: () => resolve(null)
});
},
onerror: () => resolve(null)
});
});
}
// ✅ Menu command added
GM_registerMenuCommand("Toggle En-Mr Mode", () => {
modeActive = !modeActive;
const btn = document.querySelector('#translator-toggle button');
if (btn) btn.textContent = modeActive ? 'ON' : 'OFF';
if (modeActive) { enableSentenceMode(); } else { disableSentenceMode(); }
});
function injectTranslatorButton() {
if (document.querySelector('#translator-toggle')) return;
const toggleBox = document.createElement('div');
toggleBox.id = 'translator-toggle';
const toggleBtn = document.createElement('button');
toggleBtn.textContent = 'OFF';
toggleBox.appendChild(toggleBtn);
// Strong CSS
const style = document.createElement('style');
style.textContent = `
#translator-toggle {
position: fixed !important;
bottom: 10px !important;
left: 60px !important;
padding: 2px 6px !important;
background: transparent !important;
border: 1px solid transparent !important;
border-radius: 6px !important;
z-index: 2147483647 !important;
}
#translator-toggle button {
border: none !important;
background: transparent !important;
color: #696969 !important;
font-size: 12px !important;
cursor: pointer !important;
font-family: system-ui, sans-serif !important;
}
#translator-toggle button:hover { text-decoration: underline !important; }
`;
document.head.appendChild(style);
// Append to both html and body
if (document.body) document.body.appendChild(toggleBox);
document.documentElement.appendChild(toggleBox);
toggleBtn.addEventListener('click', () => {
modeActive = !modeActive;
toggleBtn.textContent = modeActive ? 'ON' : 'OFF';
if (modeActive) { enableSentenceMode(); } else { disableSentenceMode(); }
});
}
// Inject after load
window.addEventListener('load', injectTranslatorButton);
// Reinjection checks
const observer = new MutationObserver(() => {
injectTranslatorButton();
if (modeActive) enableSentenceMode(); // Keeps checking for newly loaded articles on page switches
});
observer.observe(document.documentElement, { childList: true, subtree: true });
setInterval(injectTranslatorButton, 2000);
function enableSentenceMode() {
// FIXED: Expanded the element matcher to intercept general text div elements used by News on Air
const elements = Array.from(document.querySelectorAll('p, li, blockquote, h1, h2, h3, div[class*="content"], div[class*="story"], div[class*="article"]'))
.filter(el => {
if (el.closest('#translator-toggle')) return false;
// Ensure we don't accidentally split giant parent blocks that have structured children wrappers
if (el.tagName === 'DIV' && el.querySelector('p, li')) return false;
return el.innerText && el.innerText.trim().length > 5;
});
for (let el of elements) {
if (el.getAttribute('data-sentence-mode')) continue;
el.setAttribute('data-original-html', el.innerHTML);
const segments = el.innerText.trim().match(/[^.!?।।]+[.!?।।]?/g) || [];
el.innerHTML = '';
el.setAttribute('data-sentence-mode', 'true');
for (const fullSentence of segments) {
if (!fullSentence || fullSentence.trim().length < 2) continue;
const spanSentence = document.createElement('span');
spanSentence.innerText = `${fullSentence.trim()} `;
spanSentence.style.cursor = 'pointer';
spanSentence.style.color = '#444';
spanSentence.style.fontWeight = '500';
spanSentence.addEventListener('click', async () => {
const existing = spanSentence.querySelector('.translation-span');
if (existing) { existing.remove(); return; }
const targetLang = isMarathi(fullSentence) ? 'en' : 'mr';
const translation = await translateText(fullSentence, targetLang);
if (translation) {
const spanTrans = document.createElement('span');
spanTrans.innerText = ` [${translation}] `;
spanTrans.style = `color: ${targetLang === 'en' ? '#007bff' : '#d81b60'}; font-weight: 500; background: rgba(0,0,0,0.04); padding: 0 2px; margin-left: 5px; border-radius: 3px; font-size: 0.95em;`;
spanTrans.className = 'translation-span';
spanSentence.appendChild(spanTrans);
}
});
el.appendChild(spanSentence);
}
}
}
function disableSentenceMode() {
const elements = Array.from(document.querySelectorAll('[data-sentence-mode]'));
for (let el of elements) {
const originalHTML = el.getAttribute('data-original-html');
if (originalHTML) {
el.innerHTML = originalHTML;
}
el.removeAttribute('data-sentence-mode');
el.removeAttribute('data-original-html');
}
}
})();