Background Translator

test

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Background Translator
// @match        https://wtr-lab.com/*/chapter-*?service=web*
// @match        https://translate.google.com/?sl=auto&tl=en&op=translate
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @description  test
// @version 0.0.1.20260510071109
// @namespace https://greasyfork.org/users/1527990
// ==/UserScript==

(function() {
    'use strict';

    let lastUrl = location.href;
    const TARGET = '.chapter-body';

    const isChineseChapter = (t) => /第\s*\d+\s*章/.test(t) || /[\u4e00-\u9fa5]/.test(t);

// --- GOOGLE TRANSLATE SIDE ---
    if (location.href.includes('translate.google.com')) {
        GM_addValueChangeListener("request_translate", function(name, old_val, new_val) {
            if (!new_val) return;

            // 1. Extract the specific number from the Chinese source (e.g., 86)
            const sourceMatch = new_val.match(/第\s*(\d+)\s*章/);
            const targetNum = sourceMatch ? sourceMatch[1] : null;

            if (!targetNum) {
                console.error("Could not find Chapter Number in source text.");
                return;
            }

            let inputField = document.querySelector('textarea[aria-label="Source text"]');
            if (!inputField) return;

            inputField.focus();
            inputField.value = new_val;
            inputField.dispatchEvent(new Event('input', { bubbles: true }));

            const startTime = Date.now();
            let stabilityCounter = 0;
            let lastFoundText = "";

            let checkResult = setInterval(() => {
                // Get all translation parts
                let parts = document.querySelectorAll('.ryNqvb, span[jsname="W297wb"]');
                if (parts.length === 0) return;

                // The FIRST part should be our Chapter header
                let firstLine = parts[0].innerText.trim();

                // 2. THE FORMAT CHECK: Must be "Chapter [Number]"
                // We use a Regex to ensure "Chapter" is there and the number matches exactly
                const chapterPattern = new RegExp(`^Chapter\\s*${targetNum}`, 'i');
                const isCorrectChapter = chapterPattern.test(firstLine);

                let currentFullText = Array.from(parts).map(p => p.innerText.trim()).filter(t => t.length > 0).join('\n\n');

                // DEBUG LOGGING
                console.log(`Waiting for: Chapter ${targetNum} | Currently seeing: "${firstLine}"`);

                // 3. VALIDATION GATES
                if (isCorrectChapter && currentFullText.length > (new_val.length * 0.3) && currentFullText === lastFoundText) {
                    stabilityCounter++;
                } else {
                    stabilityCounter = 0; // Reset if the number disappears or text flickers
                }

                lastFoundText = currentFullText;

                // Return only after passing all checks for 2 cycles (10 seconds total)
                if (stabilityCounter >= 2) {
                    console.log("MATCH CONFIRMED. Returning Chapter " + targetNum);
                    GM_setValue("receive_translate", currentFullText);
                    GM_setValue("request_translate", "");
                    clearInterval(checkResult);
                } else if (Date.now() - startTime > 60000) {
                    GM_setValue("receive_translate", "Error: Timeout waiting for Chapter " + targetNum);
                    GM_setValue("request_translate", "");
                    clearInterval(checkResult);
                }
            }, 5000);
        });
        return;
    }

// --- HELPER: Get Chapter from URL ---
    const getUrlChapter = () => {
        const match = location.href.match(/chapter-(\d+)/);
        return match ? match[1] : null;
    };

    // --- NOVEL SITE SIDE ---

    function runTranslation() {
        const urlNum = getUrlChapter();
        if (!urlNum) return;

        // 1. Target the specific container for the current chapter
        const container = document.getElementById(`chapter-${urlNum}`) || document.querySelector(`.chapter-container#chapter-${urlNum}`);
        const div = container ? container.querySelector(TARGET) : document.querySelector(TARGET);

        if (!div || div.dataset.status === "processing" || div.dataset.status === "done" || !isChineseChapter(div.innerText)) return;

        div.dataset.status = "processing";

        setTimeout(() => {
            // Verify we haven't clicked 'next' again during the 3s wait
            if (getUrlChapter() !== urlNum) return;

            const lines = div.querySelectorAll('.wtr-line');
            let textArray = [];
            lines.forEach(line => {
                let lineClone = line.cloneNode(true);
                lineClone.querySelectorAll('.para-number-overlay').forEach(e => e.remove());
                let t = lineClone.innerText.trim();
                if (t) textArray.push(t);
            });

            const textToSend = textArray.join('\n\n');
            if (textToSend.length < 10) {
                div.dataset.status = "";
                return;
            }

            div.style.opacity = "0.5";
            GM_setValue("receive_translate", "");
            GM_setValue("request_translate", textToSend);
        }, 3000);
    }

    // --- SPA ENGINE (PRE-LOAD AWARE) ---
    setInterval(() => {
        const currentUrl = location.href;
        const urlNum = getUrlChapter();

        if (currentUrl !== lastUrl) {
            lastUrl = currentUrl;
            console.log("SPA Navigated to chapter:", urlNum);

            // Cleanup: The SPA keeps old chapters, so we remove the OLD overlay
            // but keep the current one if it exists.
            document.querySelectorAll('#ai-overlay').forEach(el => {
                // If the overlay text doesn't match the new chapter number, kill it
                if (!el.innerText.includes(urlNum)) el.remove();
            });
        }

        // Trigger translation
        runTranslation();
    }, 1500);

    // --- HANDLE TRIP BACK ---
    GM_addValueChangeListener("receive_translate", function(n, o, val) {
        if (!val) return;

        // Find the div matching the current URL
        const urlNum = getUrlChapter();
        const container = document.getElementById(`chapter-${urlNum}`);
        const div = container ? container.querySelector(TARGET) : document.querySelector(TARGET);

        if (!div) return;

        // Check if overlay already exists for this specific container
        let overlay = div.parentNode.querySelector('#ai-overlay');
        if (!overlay) {
            overlay = document.createElement('div');
            overlay.id = 'ai-overlay';
            overlay.className = div.className;
            overlay.style.cssText = `
                display: block !important;
                opacity: 1 !important;
                line-height: 1.4 !important;
                word-break: break-word !important;
            `;
            div.parentNode.insertBefore(overlay, div);
        }

        overlay.innerHTML = '';
        const rawLines = val.replace(/waerdygjfkstlhujhgfd/g, 'awdrsgyjekfthuhgf').split('\n');
        let lastLineWasEmpty = false;

        rawLines.forEach(lineText => {
            const trimmed = lineText.trim();
            if (trimmed === '' && lastLineWasEmpty) return;

            const p = document.createElement('div');
            if (trimmed === '') {
                p.style.height = "0.4em";
                lastLineWasEmpty = true;
            } else {
                p.innerText = trimmed;
                p.style.marginBottom = "0.4em";
                lastLineWasEmpty = false;
            }
            overlay.appendChild(p);
        });

        div.style.display = "none";
        div.setAttribute('data-status', 'done');
        GM_setValue("receive_translate", "");
    });
})();