Multi-Language Translator (Unofficial)

GitHub sayfalarındaki Latin olmayan karakterli metinleri resmi olmayan Google Translate API'sini kullanarak çevirir.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Multi-Language Translator (Unofficial)
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  GitHub sayfalarındaki Latin olmayan karakterli metinleri resmi olmayan Google Translate API'sini kullanarak çevirir.
// @author       ekstra26
// @license      MIT
// @match        https://github.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @connect      translate.googleapis.com
// ==/UserScript==

(function() {
    'use strict';

    // --- Kullanıcının Ayarları ---
    let TARGET_SOURCE_LANGS_CODES = GM_getValue('targetSourceLangCodes', 'zh,ja,ko');
    const TARGET_LANG = 'en'; // Hedef dil (İngilizce)

    // Unicode karakter aralıkları (basit dil/script tespiti için)
    const LANG_RANGES = {
        'zh': {
            name: 'Çince (Basit/Geleneksel), Japonca (Kanji), Korece (Hanja)',
            regex: /[\u4E00-\u9FFF\u3400-\u4DBF]/
        },
        'ja': {
            name: 'Japonca (Hiragana/Katakana)',
            regex: /[\u3040-\u30FF]/
        },
        'ko': {
            name: 'Korece (Hangul)',
            regex: /[\uAC00-\uD7AF]/
        },
        'ru': {
            name: 'Rusça (Kiril)',
            regex: /[\u0400-\u04FF]/
        },
        'ar': {
            name: 'Arapça',
            regex: /[\u0600-\u06FF\u0750-\u077F]/
        },
        'el': {
            name: 'Yunanca',
            regex: /[\u0370-\u03FF\u1F00-\u1FFF]/
        },
        'th': {
            name: 'Tayca',
            regex: /[\u0E00-\u0E7F]/
        },
    };

    GM_registerMenuCommand("Taranacak Kaynak Dilleri Ayarla", function() {
        const availableLangs = Object.keys(LANG_RANGES).map(code => `${code} (${LANG_RANGES[code].name})`).join('\n');
        let newLangs = prompt(
            `Lütfen taranacak dil kodlarını virgülle ayırarak girin (örn: zh,ja,ko).\n` +
            `Mevcut ve önerilen diller:\n` +
            `${availableLangs}\n\n` +
            `Not: Bu ayar, metinlerin hangi karakterleri içerdiğini tespit etmek için kullanılır. ` +
            `API'nin kendisi çoğu zaman doğru dilde çeviri yapar.`,
            TARGET_SOURCE_LANGS_CODES
        );
        if (newLangs !== null) {
            TARGET_SOURCE_LANGS_CODES = newLangs.trim().toLowerCase();
            GM_setValue('targetSourceLangCodes', TARGET_SOURCE_LANGS_CODES);
            alert(`Taranacak diller başarıyla kaydedildi: ${TARGET_SOURCE_LANGS_CODES}. Sayfayı yenileyerek çeviriyi başlatın.`);
        } else {
            alert('Dil ayarı iptal edildi.');
        }
    });

    GM_registerMenuCommand("Şimdi Çeviriyi Başlat", function() {
        if (!GM_getValue('targetSourceLangCodes')) {
            showNotification("Lütfen önce 'Taranacak Kaynak Dilleri Ayarla' seçeneğini kullanarak dilleri ayarlayın.", 'error');
            return;
        }
        console.log("[DEBUG] Manuel çeviri başlatılıyor...");
        translatePage();
    });


    // --- Yardımcı Fonksiyonlar ---

    function containsAnySelectedNonLatinScript(text) {
        const selectedCodes = TARGET_SOURCE_LANGS_CODES.split(',').map(c => c.trim());
        for (const code of selectedCodes) {
            const langInfo = LANG_RANGES[code];
            if (langInfo && langInfo.regex.test(text)) {
                return true;
            }
        }
        return false;
    }

    function translateText(text, targetLang) {
        return new Promise((resolve, reject) => {
            const encodedText = encodeURIComponent(text);
            const apiUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=auto&tl=${targetLang}&q=${encodedText}`;

            console.log(`[DEBUG] API çağrısı yapılıyor (ilk 100 karakter): ${apiUrl.substring(0, 100)}...`); // URL'yi kısalt
            console.log(`[DEBUG] Orijinal Metin gönderiliyor: "${text}"`);

            GM_xmlhttpRequest({
                method: "GET",
                url: apiUrl,
                onload: function(response) {
                    console.log(`[DEBUG] API Yanıt Durumu: ${response.status}`);
                    console.log(`[DEBUG] API Yanıt Metni (tamamını):`, response.responseText); // Tam yanıtı logla
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data && data[0] && data[0][0] && data[0][0][0]) {
                            const translated = data[0][0][0];
                            console.log(`[DEBUG] Çevrilen Metin (parsed): "${translated}"`);
                            resolve(translated);
                        } else {
                            console.warn("[DEBUG] Çeviri sonucu bulunamadı veya beklenmeyen yanıt yapısı:", data);
                            reject("Çeviri sonucu bulunamadı veya API yanıt yapısı değişmiş olabilir.");
                        }
                    } catch (e) {
                        console.error("[DEBUG] JSON parse hatası veya API yanıtı işlenirken hata oluştu:", e);
                        reject("API yanıtı işlenirken hata oluştu. Yanıt: " + response.responseText);
                    }
                },
                onerror: function(response) {
                    console.error("[DEBUG] API çağrısı başarısız oldu (onerror):", response);
                    reject(`Ağ hatası veya API çağrısı başarısız. Durum kodu: ${response.status}, Yanıt: ${response.responseText}`);
                },
                ontimeout: function() {
                    console.error("[DEBUG] API çağrısı zaman aşımına uğradı.");
                    reject("API çağrısı zaman aşımına uğradı.");
                }
            });
        });
    }

    function showNotification(message, type = 'info') {
        let notification = document.getElementById('gh-translate-notification');
        if (!notification) {
            notification = document.createElement('div');
            notification.id = 'gh-translate-notification';
            notification.style.position = 'fixed';
            notification.style.bottom = '20px';
            notification.style.right = '20px';
            notification.style.background = '#333';
            notification.style.color = 'white';
            notification.style.padding = '10px 15px';
            notification.style.borderRadius = '5px';
            notification.style.zIndex = '99999';
            notification.style.display = 'none';
            notification.style.opacity = '0.9';
            notification.style.fontSize = '14px';
            notification.style.boxShadow = '0 2px 10px rgba(0,0,0,0.5)';
            document.body.appendChild(notification);
        }
        notification.textContent = message;
        notification.style.background = type === 'error' ? '#d9534f' : (type === 'success' ? '#5cb85c' : '#333');
        notification.style.display = 'block';
        setTimeout(() => {
            notification.style.display = 'none';
        }, 8000);
    }


    // --- Ana Çeviri Mantığı ---
    async function translatePage() {
        if (!TARGET_SOURCE_LANGS_CODES) {
            showNotification("Lütfen önce 'Taranacak Kaynak Dilleri Ayarla' seçeneğini kullanarak dilleri ayarlayın.", 'error');
            return;
        }

        showNotification(`Sayfa taranıyor ve ${TARGET_SOURCE_LANGS_CODES} dillerinden metinler çevriliyor...`);
        console.log(`[DEBUG] Taranacak diller: ${TARGET_SOURCE_LANGS_CODES}`);

        const textNodesToTranslate = [];
        const uniqueTexts = new Set();

        // GitHub README, Wiki, issue yorumları gibi ana içerik alanlarını hedefleyin.
        // Daha genel p etiketlerini de ekledim.
        const containers = document.querySelectorAll(
            '.markdown-body,p,h1,h2,h3, .wiki-body, .js-issue-row .col-9, .js-comment-body, .f5.color-fg-muted, .comment-body, .Box-body p, .BorderGrid-cell p'
        );

        console.log(`[DEBUG] ${containers.length} adet olası çeviri konteyneri bulundu.`);

        containers.forEach(container => {
            const walk = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);
            let node;
            while ((node = walk.nextNode())) {
                const text = node.nodeValue.trim();
                if (text !== '' &&
                    node.parentNode &&
                    node.parentNode.nodeName !== 'SCRIPT' &&
                    node.parentNode.nodeName !== 'STYLE' &&
                    node.parentNode.nodeName !== 'CODE' &&
                    node.parentNode.nodeName !== 'PRE' &&
                    node.parentNode.nodeName !== 'KBD' &&
                    !node.parentNode.classList.contains('highlight') &&
                    text.length > 5 && // Çok kısa metinleri atla (örn: "a", "1", "...")
                    containsAnySelectedNonLatinScript(text) && // Sadece seçilen dillerin karakterlerini içerenleri tara
                    !uniqueTexts.has(text) // Aynı metni birden fazla kez işlememek için
                ) {
                    textNodesToTranslate.push({ node: node, originalText: text });
                    uniqueTexts.add(text);
                    // console.log(`[DEBUG] Çevrilecek metin düğümü bulundu: "${text.substring(0, Math.min(text.length, 50))}..."`);
                }
            }
        });

        if (textNodesToTranslate.length === 0) {
            showNotification("Çevrilecek metin bulunamadı (seçilen dillerde).", 'info');
            console.log("[DEBUG] Çevrilecek metin düğümü bulunamadı (seçilen dillerde).");
            return;
        }

        showNotification(`${textNodesToTranslate.length} adet taranan metin bulundu. Çeviriliyor... (Bu işlem biraz zaman alabilir)`);
        console.log(`[DEBUG] Toplam ${textNodesToTranslate.length} adet metin düğümü çeviri için kuyruğa alındı.`);

        let translatedCount = 0;
        let failedCount = 0;

        // Her metin düğümünü API'nin limitlerine takılmamak için sırayla çevirelim
        for (const item of textNodesToTranslate) {
            const node = item.node;
            const originalText = item.originalText;

            try {
                const translatedText = await translateText(originalText, TARGET_LANG);
                console.log(`[DEBUG] Orijinal: "${originalText}" -> Çevrilen: "${translatedText}"`);
                if (translatedText && translatedText.trim() !== originalText.trim()) {
                    node.nodeValue = translatedText;
                    translatedCount++;
                    console.log(`[DEBUG] Metin başarıyla güncellendi. Yeni metin: "${node.nodeValue}"`);
                } else {
                    console.log(`[DEBUG] Metin güncellenmedi. Orijinal metinle aynıydı veya boş döndü. (Orijinal: "${originalText}", Gelen: "${translatedText}")`);
                }
            } catch (error) {
                console.warn(`[DEBUG] Metin çevrilemedi: "${originalText.substring(0, Math.min(originalText.length, 50))}..." Hata: ${error}`);
                failedCount++;
            }
            // Her API çağrısı arasında kısa bir gecikme
            await new Promise(resolve => setTimeout(resolve, 100));
        }

        if (translatedCount > 0) {
            showNotification(`${translatedCount} metin başarıyla çevrildi. ${failedCount} metin çevrilemedi.`, 'success');
        } else {
            showNotification("Taranan metinler ya zaten hedef dildeydi ya da çevrilecek metin bulunamadı.", 'info');
        }
        console.log("[DEBUG] Çeviri işlemi tamamlandı.");
    }

    // Sayfa yüklendiğinde ve biraz sonra tekrar çeviriyi dene
    window.addEventListener('load', translatePage);
    setTimeout(translatePage, 3000);

})();