// ==UserScript== // @name SN translator // @namespace http://tampermonkey.net/ // @version 0.1 // @description Translate posts on SN // @author ekzyis // @source https://gitlab.com/ekzyis/sn-translator // @match https://stacker.news/* // @icon https://img.freepik.com/free-vector/lightning-bolt-coloured-outline_78370-517.jpg?w=826&t=st=1671940131~exp=1671940731~hmac=d04f3bd1468f1d362a864b4a25e367aef4d1941991161aa008684c5a4089cf0b // @grant GM_xmlhttpRequest // @grant GM_addStyle // ==/UserScript== const headers = { origin: 'https://libretranslate.com', accept: '*/*', 'accept-language': 'de-DE,de;q=0.9,ru-DE;q=0.8,ru;q=0.7,en-US;q=0.6,en;q=0.5', 'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Linux"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', }; function translate(text, source, target) { const formData = new FormData(); formData.append('q', text); formData.append('source', source); formData.append('target', target); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://libretranslate.com/translate', data: formData, headers, synchronous: true, onload: function (res) { const body = JSON.parse(res.responseText); if (res.status !== 200) return reject(body); return resolve(body.translatedText); }, }); }); } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const log = (msg) => console.log(`sn-translator:`, msg); function createButton(textNode) { const btn = document.createElement('button'); btn.innerText = 'translate'; btn.onclick = async (e) => { const hasTranslation = !!textNode.querySelector('hr'); if (hasTranslation) return; // TODO: let user choose target language const t = await translate(textNode.innerText, 'auto', 'en').catch(console.error); if (t) { const divider = document.createElement('hr'); textNode.appendChild(divider); const em = document.createElement('em'); const p = document.createElement('p'); p.innerText = 'Translation:'; const p2 = document.createElement('p'); p2.innerText = t; em.appendChild(p); em.appendChild(p2); textNode.appendChild(em); } }; btn.classList.add('translate'); return btn; } function addButtons() { const commentSection = document.querySelector('.item_comments__cN57K'); if (!commentSection) return; const comments = commentSection.querySelectorAll('.comment_comment__5uvl3'); log(`Found ${comments.length} comment(s)`); log(`Adding translate button to every comment ...`); for (const comment of comments) { const topBar = comment.querySelector('.item_other__qNlji'); const textNode = comment.querySelector('.comment_text__nHI0E > .text_text__fuZIZ'); const btn = createButton(textNode); topBar.appendChild(btn); } log(`Done`); } (async function () { // Sleep before running script on page load // since else we might get overwritten by loading content. const initialSleep = 1000; await sleep(initialSleep); GM_addStyle(` .translate { color: var(--theme-grey); border: none; cursor: pointer; background: rgba(0,0,0,0); } `); let pathname = window.location.pathname; log(`Current location: ${pathname}`); addButtons(); // Check if URL changed and rerun script const scriptInterval = 1000; setInterval(() => { if (window.location.pathname !== pathname) { pathname = window.location.pathname; log(`New location detected: ${pathname}`); addButtons(); } }, scriptInterval); })();