Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Greasyfork – Auto-Translator (v16.2) - TEST for CITRUS - Draggable, Wrape line and Decription at once - IN USE -Stay pb retranslate when show original
- // @namespace http://tampermonkey.net/
- // @version 16.2
- // @description Auto-translates non-Latin-language text on Greasyfork listing, user profile, and script info pages while preserving HTML structure in #additional-info.
- // @match https://greasyfork.org/*/users/*
- // @match https://greasyfork.org/*/scripts*
- // @match https://greasyfork.org/*/users/*?sort=*
- // @match https://greasyfork.org/*/users/*-*?sort=*
- // @grant GM_xmlhttpRequest
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @connect translate.googleapis.com
- // @downloadURL https://update.greasyfork.org/scripts/555224/Greasyfork%20%E2%80%93%20Auto-Translator%20%28v16%29.user.js
- // @updateURL https://update.greasyfork.org/scripts/555224/Greasyfork%20%E2%80%93%20Auto-Translator%20%28v16%29.meta.js
- // ==/UserScript==
- (() => {
- 'use strict';
- const SELECTORS = {
- listingTitle: '#script-table tbody td:nth-child(2) .thetitle > a > b',
- listingDesc: 'body[pagetype="ListingPage"] #script-table td .thetitle + div',
- profileDesc: 'body[pagetype="UserProfile"] #script-table td .thetitle + div',
- infoTitle: '.width-constraint #script-info > #script-content .gf-formatted-text span.gf-item',
- infoDesc: '.width-constraint #script-info > #script-content #additional-info',
- scriptDescription: '.width-constraint:has(#additional-info) #script-info header p#script-description.script-description',
- additionalInfoP: '#additional-info p',
- headerTitle: 'header h2',
- scriptInfoTitle: '#script-info h2'
- };
- const TRANSLATED_CLASS = 'gf-translated-text';
- const state = {
- queue: [],
- processing: false,
- processed: new WeakSet(),
- count: 0,
- autoTranslate: true
- };
- const EN_SKIP = /^[a-zA-Z\s\d\.,;:!?()\-"']+$/;
- const LATIN_SKIP = /^[a-zA-ZÀ-ÿ\s\d\.,;:!?¿¡()\-"']+$/;
- const NON_LATIN = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u0400-\u04ff\u0600-\u06ff\u0e00-\u0e7f]/;
- GM_addStyle(`
- #gf-panel {
- position: fixed;
- top: 20px;
- right: 20px;
- z-index: 999999;
- background: #111;
- color: #fff;
- padding: 10px;
- border-radius: 8px;
- font-size: 13px;
- max-width: 280px;
- box-shadow: 0 4px 14px rgba(0,0,0,.35);
- cursor: move;
- user-select: none;
- }
- #gf-panel button {
- margin-top: 6px;
- margin-right: 6px;
- cursor: pointer;
- }
- .${TRANSLATED_CLASS} {
- direction: ltr !important;
- unicode-bidi: plaintext !important;
- }
- #additional-info, #additional-info p, #additional-info br {
- white-space: pre-wrap !important;
- word-break: break-word !important;
- overflow-wrap: anywhere !important;
- }
- `);
- function shouldTranslate(text) {
- if (!text) return false;
- const t = text.trim();
- if (!t) return false;
- if (EN_SKIP.test(t) || LATIN_SKIP.test(t)) return false;
- return NON_LATIN.test(t);
- }
- function markTranslated(el) {
- if (el && el.classList) el.classList.add(TRANSLATED_CLASS);
- }
- function unmarkTranslated(el) {
- if (el && el.classList) el.classList.remove(TRANSLATED_CLASS);
- }
- function translateText(text) {
- return new Promise((resolve) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=' + encodeURIComponent(text),
- onload: (res) => {
- try {
- const data = JSON.parse(res.responseText);
- resolve(data?.[0]?.map(part => part?.[0]).join('') || text);
- } catch {
- resolve(text);
- }
- },
- onerror: () => resolve(text)
- });
- });
- }
- function setTranslatedText(el, text) {
- el.textContent = text;
- markTranslated(el);
- }
- function setTranslatedHtml(el, text) {
- el.innerHTML = text.replace(/\n/g, '<br>');
- markTranslated(el);
- }
- async function translateAdditionalInfo(el) {
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
- const nodes = [];
- while (walker.nextNode()) {
- const node = walker.currentNode;
- const text = (node.nodeValue || '').trim();
- if (text && shouldTranslate(text)) nodes.push(node);
- }
- for (const node of nodes) {
- const original = node.nodeValue;
- const translated = await translateText(original);
- if (translated && translated !== original) {
- node.nodeValue = translated;
- if (node.parentElement) markTranslated(node.parentElement);
- }
- await new Promise(r => setTimeout(r, 200));
- }
- }
- async function translateScriptInfoPage() {
- const info = document.querySelector('#script-info');
- if (!info) return;
- const scriptDesc = info.querySelector('header p#script-description.script-description');
- if (scriptDesc && !state.processed.has(scriptDesc)) {
- state.processed.add(scriptDesc);
- const text = (scriptDesc.textContent || '').trim();
- if (text && shouldTranslate(text)) {
- const translated = await translateText(text);
- if (translated && translated !== text) setTranslatedText(scriptDesc, translated);
- }
- }
- const titleNodes = info.querySelectorAll('.gf-formatted-text .gf-item, header h2, #script-info h2');
- for (const el of titleNodes) {
- if (state.processed.has(el)) continue;
- const text = (el.textContent || '').trim();
- if (!text || !shouldTranslate(text)) continue;
- state.processed.add(el);
- const translated = await translateText(text);
- if (translated && translated !== text) setTranslatedText(el, translated);
- }
- const additionalInfo = info.querySelector('#additional-info');
- if (additionalInfo && !state.processed.has(additionalInfo)) {
- state.processed.add(additionalInfo);
- await translateAdditionalInfo(additionalInfo);
- markTranslated(additionalInfo);
- }
- }
- async function processElement(el) {
- if (state.processed.has(el)) return;
- if (!state.autoTranslate) return;
- if (el.matches('#script-info') || el.closest('#script-info')) {
- await translateScriptInfoPage();
- return;
- }
- const text = (el.textContent || '').trim();
- if (!text) return;
- if (!shouldTranslate(text) && text.length > 20) return;
- state.processed.add(el);
- if (el.matches(SELECTORS.infoDesc)) {
- await translateAdditionalInfo(el);
- markTranslated(el);
- return;
- }
- const translated = await translateText(text);
- if (!translated || translated === text) return;
- const htmlTargets = [
- SELECTORS.listingDesc,
- SELECTORS.profileDesc,
- SELECTORS.scriptDescription,
- SELECTORS.additionalInfoP,
- SELECTORS.infoTitle,
- SELECTORS.infoDesc
- ].some(sel => el.matches(sel));
- if (htmlTargets) setTranslatedHtml(el, translated);
- else setTranslatedText(el, translated);
- }
- async function runQueue() {
- if (state.processing || !state.autoTranslate) return;
- state.processing = true;
- while (state.queue.length && state.autoTranslate) {
- const el = state.queue.shift();
- try {
- await processElement(el);
- state.count++;
- updatePanel();
- } catch {}
- await new Promise(r => setTimeout(r, 200));
- }
- state.processing = false;
- }
- function enqueueMatches() {
- if (!state.autoTranslate) return;
- const found = new Set();
- for (const sel of Object.values(SELECTORS)) {
- document.querySelectorAll(sel).forEach(el => found.add(el));
- }
- found.forEach(el => {
- if (!state.processed.has(el)) state.queue.push(el);
- });
- const infoPage = document.querySelector('#script-info');
- if (infoPage && !state.queue.includes(infoPage) && state.autoTranslate) {
- state.queue.unshift(infoPage);
- }
- runQueue();
- }
- function updatePanel() {
- const el = document.getElementById('gf-panel');
- if (!el) return;
- const countEl = el.querySelector('.count');
- if (countEl) countEl.textContent = `Translated: ${state.count}`;
- const btn = el.querySelector('#gf-toggle');
- if (btn) btn.textContent = state.autoTranslate ? 'Pause Translate' : 'Translate';
- }
- function createPanel() {
- const panel = document.createElement('div');
- panel.id = 'gf-panel';
- panel.innerHTML = `
- <div>Greasyfork Auto-Translator v.16</div>
- <div class="count">Translated: 0</div>
- <button id="gf-toggle">Pause Translate</button>
- <button id="gf-hide">Hide</button>
- <button id="gf-reload">Restore Page</button>
- `;
- document.body.appendChild(panel);
- panel.querySelector('#gf-toggle').addEventListener('click', () => {
- state.autoTranslate = !state.autoTranslate;
- updatePanel();
- if (state.autoTranslate) enqueueMatches();
- });
- panel.querySelector('#gf-hide').addEventListener('click', () => {
- panel.remove();
- });
- panel.querySelector('#gf-reload').addEventListener('click', () => {
- state.autoTranslate = false;
- updatePanel();
- state.queue = [];
- state.processed = new WeakSet();
- location.reload();
- });
- let dragging = false;
- let offsetX = 0;
- let offsetY = 0;
- panel.addEventListener('mousedown', (e) => {
- if (e.target.tagName === 'BUTTON') return;
- dragging = true;
- offsetX = e.clientX - panel.getBoundingClientRect().left;
- offsetY = e.clientY - panel.getBoundingClientRect().top;
- });
- document.addEventListener('mousemove', (e) => {
- if (!dragging) return;
- panel.style.left = `${e.clientX - offsetX}px`;
- panel.style.top = `${e.clientY - offsetY}px`;
- panel.style.right = 'auto';
- panel.style.bottom = 'auto';
- });
- document.addEventListener('mouseup', () => {
- dragging = false;
- });
- }
- const observer = new MutationObserver(() => enqueueMatches());
- function init() {
- if (!document.body) return;
- createPanel();
- enqueueMatches();
- observer.observe(document.body, { childList: true, subtree: true });
- }
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', init);
- } else {
- init();
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment