Multi-Language Translator (Unofficial)

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

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 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);

})();