Greasy Fork

来自缓存

Greasy Fork is available in English.

一键中英翻译切换器(网页整页翻译)- 高速无残留版

点击按钮将当前网页所有英文翻译成简体中文(高速、无残留分隔符)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         一键中英翻译切换器(网页整页翻译)- 高速无残留版
// @namespace    http://tampermonkey.net/1554493
// @version      1.2
// @description  点击按钮将当前网页所有英文翻译成简体中文(高速、无残留分隔符)
// @author       飞翔的荷兰人269
// @match        *://*/*
// @grant        none
// @run-at       document-end
// @license      MIT  
// ==/UserScript==

(function() {
    'use strict';
    let isTranslated = false;
    let originalTexts = new Map();

    // 创建按钮(左下角)
    let button = document.createElement('button');
    button.innerText = '翻译为中文';
    button.style.position = 'fixed';
    button.style.bottom = '20px';
    button.style.left = '20px';
    button.style.zIndex = '999999';
    button.style.padding = '12px 18px';
    button.style.backgroundColor = '#4285f4';
    button.style.color = 'white';
    button.style.border = 'none';
    button.style.borderRadius = '8px';
    button.style.cursor = 'pointer';
    button.style.fontSize = '15px';
    button.style.fontWeight = 'bold';
    button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
    button.style.transition = 'all 0.3s';
    document.body.appendChild(button);

    // Google 翻译 API
    async function translateText(text) {
        if (!text || !/[a-zA-Z]/.test(text)) return text;
        try {
            const response = await fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=zh-CN&dt=t&q=${encodeURIComponent(text)}`);
            const data = await response.json();
            return data[0].map(item => item[0]).join('');
        } catch (e) {
            console.error('翻译失败:', e);
            return text;
        }
    }

    // 判断节点是否需要翻译
    function shouldTranslate(node) {
        if (!node.parentElement) return false;
        const tag = node.parentElement.tagName;
        if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'CODE', 'PRE', 'HEADER', 'NAV', 'FOOTER'].includes(tag)) return false;
        const className = node.parentElement.className.toLowerCase();
        if (className.includes('menu') || className.includes('nav') || className.includes('header') || className.includes('footer')) return false;
        return true;
    }

    // 翻译整个页面(修复分隔符问题)
    async function translatePage() {
        const textsToTranslate = []; // {node, original: text, trimmed}

        function walk(node) {
            if (node.nodeType === Node.TEXT_NODE) {
                const text = node.textContent;
                const trimmed = text.trim();
                if (trimmed && /[a-zA-Z]/.test(trimmed) && shouldTranslate(node)) {
                    textsToTranslate.push({node, original: text, trimmed});
                    if (!originalTexts.has(node)) {
                        originalTexts.set(node, text);
                    }
                }
            } else {
                for (let child of node.childNodes) walk(child);
            }
        }
        walk(document.body);

        if (textsToTranslate.length === 0) {
            alert('页面没有需要翻译的英文内容');
            return;
        }

        button.innerText = `翻译中... (0/${textsToTranslate.length})`;
        button.disabled = true;

        // 分批翻译(每批50条,用 \n\n 分隔,Google 会保留并正确处理)
        const batchSize = 50;
        for (let i = 0; i < textsToTranslate.length; i += batchSize) {
            const batch = textsToTranslate.slice(i, i + batchSize);
            const combinedText = batch.map(item => item.trimmed).join('\n\n');

            const translatedCombined = await translateText(combinedText);
            // 按 \n\n 拆分,确保顺序一致
            const translatedParts = translatedCombined.split('\n\n');

            // 写回节点(如果拆分数量不匹配,用最后一个或原文兜底)
            batch.forEach((item, idx) => {
                let translated = translatedParts[idx] || translatedParts[translatedParts.length - 1] || item.trimmed;
                translated = translated.trim(); // 去除多余空格
                if (translated && translated !== item.trimmed) {
                    const leading = item.original.match(/^(\s*)/)[0];
                    const trailing = item.original.match(/(\s*)$/)[0];
                    item.node.textContent = leading + translated + trailing;
                }
            });

            button.innerText = `翻译中... (${Math.min(i + batchSize, textsToTranslate.length)}/${textsToTranslate.length})`;
        }

        button.innerText = '恢复原文';
        button.style.backgroundColor = '#db4437';
        button.disabled = false;
        isTranslated = true;
    }

    // 恢复原文
    function restorePage() {
        originalTexts.forEach((originalText, node) => {
            if (node.parentNode) node.textContent = originalText;
        });
        originalTexts.clear();
        button.innerText = '翻译为中文';
        button.style.backgroundColor = '#4285f4';
        isTranslated = false;
    }

    button.addEventListener('click', () => {
        if (isTranslated) {
            restorePage();
        } else {
            translatePage();
        }
    });

    console.log('高速无残留网页翻译器已加载(左下角按钮)');
})();