OptiTranslate

Dual-engine translation (Google + Yandex). Anti-crash.

目前為 2025-12-28 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         OptiTranslate
// @namespace    com.vonkleist.optitranslate
// @version      3.0
// @description  Dual-engine translation (Google + Yandex). Anti-crash.
// @author       VonKleistL
// @match        *://*/*
// @run-at       document-end
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- CONFIGURATION ---
    const TARGET_LANG = 'en'; 

    // --- SAFETY CHECKS ---
    // Prevent recursive loading on translation sites
    if (window.location.hostname.match(/(translate|googleusercontent|yandex)/)) return;

    // Language & Content Check
    const pageLang = document.documentElement.lang || navigator.language;
    if (pageLang && pageLang.toLowerCase().startsWith(TARGET_LANG)) return;
    if (document.body.innerText.length < 50) return;

    // --- THE HOST ---
    const host = document.createElement('div');
    host.id = 'opti-translate-host';
    document.documentElement.appendChild(host);
    const shadow = host.attachShadow({mode: 'open'});

    // --- THE AESTHETIC ---
    const style = document.createElement('style');
    style.textContent = `
        :host {
            all: initial; 
            z-index: 2147483647;
            position: fixed;
            bottom: 30px;
            right: 20px;
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
            pointer-events: none;
        }
        
        .opti-panel {
            background: rgba(22, 22, 24, 0.92);
            backdrop-filter: blur(25px) saturate(180%);
            -webkit-backdrop-filter: blur(25px) saturate(180%);
            border: 1px solid rgba(255, 255, 255, 0.12);
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
            border-radius: 22px;
            padding: 10px 12px;
            display: flex;
            align-items: center;
            gap: 10px;
            transform: translateY(120%);
            opacity: 0;
            transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1), opacity 0.4s ease-out;
            pointer-events: auto;
        }

        .opti-panel.visible {
            transform: translateY(0);
            opacity: 1;
        }

        /* The Engines Container */
        .opti-engines {
            display: flex;
            gap: 6px;
        }

        .opti-btn {
            border: none;
            padding: 0 14px;
            height: 34px;
            border-radius: 10px;
            color: #fff;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: transform 0.1s, opacity 0.2s;
        }
        
        .opti-btn:active {
            transform: scale(0.96);
        }

        /* Google Brand Color */
        #btn-google {
            background: rgba(66, 133, 244, 0.2);
            color: #8ab4f8;
            border: 1px solid rgba(66, 133, 244, 0.3);
        }
        #btn-google:active { background: rgba(66, 133, 244, 0.4); }

        /* Yandex Brand Color (Red/Orange) */
        #btn-yandex {
            background: rgba(252, 63, 29, 0.2);
            color: #ff8a80;
            border: 1px solid rgba(252, 63, 29, 0.3);
        }
        #btn-yandex:active { background: rgba(252, 63, 29, 0.4); }

        .opti-divider {
            width: 1px;
            height: 20px;
            background: rgba(255,255,255,0.1);
        }

        .opti-close {
            background: transparent;
            border: none;
            color: rgba(255, 255, 255, 0.3);
            font-size: 20px;
            width: 28px;
            height: 34px;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-left: 2px;
            cursor: pointer;
        }
        .opti-close:active { color: white; }
    `;
    shadow.appendChild(style);

    // --- THE BODY ---
    const panel = document.createElement('div');
    panel.className = 'opti-panel';
    const detected = pageLang.toUpperCase().split('-')[0];
    
    // Minimalist UI: [ Google ] [ Yandex ] | [ x ]
    panel.innerHTML = `
        <div class="opti-engines">
            <button class="opti-btn" id="btn-google">Google</button>
            <button class="opti-btn" id="btn-yandex">Yandex</button>
        </div>
        <div class="opti-divider"></div>
        <button class="opti-close" id="do-close">×</button>
    `;
    
    shadow.appendChild(panel);

    // --- THE NERVOUS SYSTEM ---
    
    // 1. Google (Mobile Endpoint - Anti-Captcha)
    shadow.getElementById('btn-google').addEventListener('click', (e) => {
        e.stopPropagation();
        const url = encodeURIComponent(window.location.href);
        // Using /m? endpoint for lighter, safer translation
        window.open(`https://translate.google.com/m?sl=auto&tl=${TARGET_LANG}&u=${url}`, '_blank');
        closePanel();
    });

    // 2. Yandex (The Heavy Hitter)
    shadow.getElementById('btn-yandex').addEventListener('click', (e) => {
        e.stopPropagation();
        const url = encodeURIComponent(window.location.href);
        window.open(`https://translate.yandex.com/translate?lang=auto-${TARGET_LANG}&url=${url}`, '_blank');
        closePanel();
    });

    // 3. Dismiss
    shadow.getElementById('do-close').addEventListener('click', (e) => {
        e.stopPropagation();
        closePanel();
    });

    function closePanel() {
        panel.classList.remove('visible');
        setTimeout(() => host.remove(), 600);
    }

    // 4. Entrance
    setTimeout(() => {
        panel.classList.add('visible');
    }, 1000);

})();