OptiTranslate

Three engines: Google, Bing, Yandex. Total coverage.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         OptiTranslate
// @namespace    com.vonkleist.optitranslate
// @version      4.0
// @description  Three engines: Google, Bing, Yandex. Total coverage.
// @author       VonKleistL
// @match        *://*/*
// @run-at       document-end
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const TARGET_LANG = 'en'; 

    // Safety: Don't run on translation sites
    if (window.location.hostname.match(/(translate|googleusercontent|yandex|bing|microsoft)/)) return;
    
    const pageLang = document.documentElement.lang || navigator.language;
    if (pageLang && pageLang.toLowerCase().startsWith(TARGET_LANG)) return;
    if (document.body.innerText.length < 50) return;

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

    // --- STYLES ---
    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(18, 18, 20, 0.96);
            backdrop-filter: blur(30px) saturate(180%);
            -webkit-backdrop-filter: blur(30px) saturate(180%);
            border: 1px solid rgba(255, 255, 255, 0.15);
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
            border-radius: 26px;
            padding: 6px;
            display: flex;
            gap: 6px;
            transform: translateY(120%);
            opacity: 0;
            transition: transform 0.45s cubic-bezier(0.19, 1, 0.22, 1), opacity 0.35s;
            pointer-events: auto;
        }

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

        .opti-btn {
            border: none;
            padding: 0 14px;
            height: 42px;
            border-radius: 20px;
            color: #fff;
            font-size: 13px;
            font-weight: 700;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: transform 0.1s;
        }
        
        .opti-btn:active {
            transform: scale(0.95);
        }

        /* Google: Blue */
        #btn-google {
            background: linear-gradient(135deg, #4285F4, #357AE8);
            box-shadow: 0 3px 12px rgba(66, 133, 244, 0.35);
        }

        /* Bing: Teal/Green */
        #btn-bing {
            background: linear-gradient(135deg, #008272, #00695C);
            box-shadow: 0 3px 12px rgba(0, 130, 114, 0.35);
        }

        /* Yandex: Red */
        #btn-yandex {
            background: linear-gradient(135deg, #FC3F1D, #D81E05);
            box-shadow: 0 3px 12px rgba(252, 63, 29, 0.35);
        }

        .opti-close {
            width: 42px;
            height: 42px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.08);
            border: none;
            color: rgba(255, 255, 255, 0.5);
            font-size: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .opti-close:active {
            background: rgba(255, 255, 255, 0.15);
        }
    `;
    shadow.appendChild(style);

    // --- BODY ---
    const panel = document.createElement('div');
    panel.className = 'opti-panel';
    panel.innerHTML = `
        <button class="opti-btn" id="btn-google">Google</button>
        <button class="opti-btn" id="btn-bing">Bing</button>
        <button class="opti-btn" id="btn-yandex">Yandex</button>
        <button class="opti-close" id="do-close">×</button>
    `;
    shadow.appendChild(panel);

    // --- THE GHOST ANCHOR METHOD ---
    function forceOpen(url) {
        const a = document.createElement('a');
        a.href = url;
        a.target = '_blank';
        a.rel = 'noopener noreferrer';
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
        setTimeout(() => a.remove(), 100);
        closePanel();
    }

    // --- TRIGGERS ---
    
    // 1. Google Translate
    shadow.getElementById('btn-google').onclick = (e) => {
        e.preventDefault();
        const url = encodeURIComponent(window.location.href);
        forceOpen(`https://translate.google.com/translate?sl=auto&tl=${TARGET_LANG}&u=${url}`);
    };

    // 2. Microsoft Bing Translator
    shadow.getElementById('btn-bing').onclick = (e) => {
        e.preventDefault();
        const url = encodeURIComponent(window.location.href);
        forceOpen(`https://www.translatetheweb.com/?from=&to=${TARGET_LANG}&dl=${TARGET_LANG}&a=${url}`);
    };

    // 3. Yandex Translate
    shadow.getElementById('btn-yandex').onclick = (e) => {
        e.preventDefault();
        const url = encodeURIComponent(window.location.href);
        forceOpen(`https://translate.yandex.com/translate?url=${url}&lang=auto-en`);
    };

    // 4. Close
    shadow.getElementById('do-close').onclick = () => closePanel();

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

    // --- ENTRANCE ---
    setTimeout(() => panel.classList.add('visible'), 600);

})();