Multi-Language Translator (Unofficial)

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

  1. // ==UserScript==
  2. // @name Multi-Language Translator (Unofficial)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description GitHub sayfalarındaki Latin olmayan karakterli metinleri resmi olmayan Google Translate API'sini kullanarak çevirir.
  6. // @author ekstra26
  7. // @license MIT
  8. // @match https://github.com/*
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // @connect translate.googleapis.com
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. // --- Kullanıcının Ayarları ---
  20. let TARGET_SOURCE_LANGS_CODES = GM_getValue('targetSourceLangCodes', 'zh,ja,ko');
  21. const TARGET_LANG = 'en'; // Hedef dil (İngilizce)
  22.  
  23. // Unicode karakter aralıkları (basit dil/script tespiti için)
  24. const LANG_RANGES = {
  25. 'zh': {
  26. name: 'Çince (Basit/Geleneksel), Japonca (Kanji), Korece (Hanja)',
  27. regex: /[\u4E00-\u9FFF\u3400-\u4DBF]/
  28. },
  29. 'ja': {
  30. name: 'Japonca (Hiragana/Katakana)',
  31. regex: /[\u3040-\u30FF]/
  32. },
  33. 'ko': {
  34. name: 'Korece (Hangul)',
  35. regex: /[\uAC00-\uD7AF]/
  36. },
  37. 'ru': {
  38. name: 'Rusça (Kiril)',
  39. regex: /[\u0400-\u04FF]/
  40. },
  41. 'ar': {
  42. name: 'Arapça',
  43. regex: /[\u0600-\u06FF\u0750-\u077F]/
  44. },
  45. 'el': {
  46. name: 'Yunanca',
  47. regex: /[\u0370-\u03FF\u1F00-\u1FFF]/
  48. },
  49. 'th': {
  50. name: 'Tayca',
  51. regex: /[\u0E00-\u0E7F]/
  52. },
  53. };
  54.  
  55. GM_registerMenuCommand("Taranacak Kaynak Dilleri Ayarla", function() {
  56. const availableLangs = Object.keys(LANG_RANGES).map(code => `${code} (${LANG_RANGES[code].name})`).join('\n');
  57. let newLangs = prompt(
  58. `Lütfen taranacak dil kodlarını virgülle ayırarak girin rn: zh,ja,ko).\n` +
  59. `Mevcut ve önerilen diller:\n` +
  60. `${availableLangs}\n\n` +
  61. `Not: Bu ayar, metinlerin hangi karakterleri içerdiğini tespit etmek için kullanılır. ` +
  62. `API'nin kendisi çoğu zaman doğru dilde çeviri yapar.`,
  63. TARGET_SOURCE_LANGS_CODES
  64. );
  65. if (newLangs !== null) {
  66. TARGET_SOURCE_LANGS_CODES = newLangs.trim().toLowerCase();
  67. GM_setValue('targetSourceLangCodes', TARGET_SOURCE_LANGS_CODES);
  68. alert(`Taranacak diller başarıyla kaydedildi: ${TARGET_SOURCE_LANGS_CODES}. Sayfayı yenileyerek çeviriyi başlatın.`);
  69. } else {
  70. alert('Dil ayarı iptal edildi.');
  71. }
  72. });
  73.  
  74. GM_registerMenuCommand("Şimdi Çeviriyi Başlat", function() {
  75. if (!GM_getValue('targetSourceLangCodes')) {
  76. showNotification("Lütfen önce 'Taranacak Kaynak Dilleri Ayarla' seçeneğini kullanarak dilleri ayarlayın.", 'error');
  77. return;
  78. }
  79. console.log("[DEBUG] Manuel çeviri başlatılıyor...");
  80. translatePage();
  81. });
  82.  
  83.  
  84. // --- Yardımcı Fonksiyonlar ---
  85.  
  86. function containsAnySelectedNonLatinScript(text) {
  87. const selectedCodes = TARGET_SOURCE_LANGS_CODES.split(',').map(c => c.trim());
  88. for (const code of selectedCodes) {
  89. const langInfo = LANG_RANGES[code];
  90. if (langInfo && langInfo.regex.test(text)) {
  91. return true;
  92. }
  93. }
  94. return false;
  95. }
  96.  
  97. function translateText(text, targetLang) {
  98. return new Promise((resolve, reject) => {
  99. const encodedText = encodeURIComponent(text);
  100. const apiUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=auto&tl=${targetLang}&q=${encodedText}`;
  101.  
  102. console.log(`[DEBUG] API çağrısı yapılıyor (ilk 100 karakter): ${apiUrl.substring(0, 100)}...`); // URL'yi kısalt
  103. console.log(`[DEBUG] Orijinal Metin gönderiliyor: "${text}"`);
  104.  
  105. GM_xmlhttpRequest({
  106. method: "GET",
  107. url: apiUrl,
  108. onload: function(response) {
  109. console.log(`[DEBUG] API Yanıt Durumu: ${response.status}`);
  110. console.log(`[DEBUG] API Yanıt Metni (tamamını):`, response.responseText); // Tam yanıtı logla
  111. try {
  112. const data = JSON.parse(response.responseText);
  113. if (data && data[0] && data[0][0] && data[0][0][0]) {
  114. const translated = data[0][0][0];
  115. console.log(`[DEBUG] Çevrilen Metin (parsed): "${translated}"`);
  116. resolve(translated);
  117. } else {
  118. console.warn("[DEBUG] Çeviri sonucu bulunamadı veya beklenmeyen yanıt yapısı:", data);
  119. reject("Çeviri sonucu bulunamadı veya API yanıt yapısı değişmiş olabilir.");
  120. }
  121. } catch (e) {
  122. console.error("[DEBUG] JSON parse hatası veya API yanıtı işlenirken hata oluştu:", e);
  123. reject("API yanıtı işlenirken hata oluştu. Yanıt: " + response.responseText);
  124. }
  125. },
  126. onerror: function(response) {
  127. console.error("[DEBUG] API çağrısı başarısız oldu (onerror):", response);
  128. reject(`Ağ hatası veya API çağrısı başarısız. Durum kodu: ${response.status}, Yanıt: ${response.responseText}`);
  129. },
  130. ontimeout: function() {
  131. console.error("[DEBUG] API çağrısı zaman aşımına uğradı.");
  132. reject("API çağrısı zaman aşımına uğradı.");
  133. }
  134. });
  135. });
  136. }
  137.  
  138. function showNotification(message, type = 'info') {
  139. let notification = document.getElementById('gh-translate-notification');
  140. if (!notification) {
  141. notification = document.createElement('div');
  142. notification.id = 'gh-translate-notification';
  143. notification.style.position = 'fixed';
  144. notification.style.bottom = '20px';
  145. notification.style.right = '20px';
  146. notification.style.background = '#333';
  147. notification.style.color = 'white';
  148. notification.style.padding = '10px 15px';
  149. notification.style.borderRadius = '5px';
  150. notification.style.zIndex = '99999';
  151. notification.style.display = 'none';
  152. notification.style.opacity = '0.9';
  153. notification.style.fontSize = '14px';
  154. notification.style.boxShadow = '0 2px 10px rgba(0,0,0,0.5)';
  155. document.body.appendChild(notification);
  156. }
  157. notification.textContent = message;
  158. notification.style.background = type === 'error' ? '#d9534f' : (type === 'success' ? '#5cb85c' : '#333');
  159. notification.style.display = 'block';
  160. setTimeout(() => {
  161. notification.style.display = 'none';
  162. }, 8000);
  163. }
  164.  
  165.  
  166. // --- Ana Çeviri Mantığı ---
  167. async function translatePage() {
  168. if (!TARGET_SOURCE_LANGS_CODES) {
  169. showNotification("Lütfen önce 'Taranacak Kaynak Dilleri Ayarla' seçeneğini kullanarak dilleri ayarlayın.", 'error');
  170. return;
  171. }
  172.  
  173. showNotification(`Sayfa taranıyor ve ${TARGET_SOURCE_LANGS_CODES} dillerinden metinler çevriliyor...`);
  174. console.log(`[DEBUG] Taranacak diller: ${TARGET_SOURCE_LANGS_CODES}`);
  175.  
  176. const textNodesToTranslate = [];
  177. const uniqueTexts = new Set();
  178.  
  179. // GitHub README, Wiki, issue yorumları gibi ana içerik alanlarını hedefleyin.
  180. // Daha genel p etiketlerini de ekledim.
  181. const containers = document.querySelectorAll(
  182. '.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'
  183. );
  184.  
  185. console.log(`[DEBUG] ${containers.length} adet olası çeviri konteyneri bulundu.`);
  186.  
  187. containers.forEach(container => {
  188. const walk = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);
  189. let node;
  190. while ((node = walk.nextNode())) {
  191. const text = node.nodeValue.trim();
  192. if (text !== '' &&
  193. node.parentNode &&
  194. node.parentNode.nodeName !== 'SCRIPT' &&
  195. node.parentNode.nodeName !== 'STYLE' &&
  196. node.parentNode.nodeName !== 'CODE' &&
  197. node.parentNode.nodeName !== 'PRE' &&
  198. node.parentNode.nodeName !== 'KBD' &&
  199. !node.parentNode.classList.contains('highlight') &&
  200. text.length > 5 && // Çok kısa metinleri atla (örn: "a", "1", "...")
  201. containsAnySelectedNonLatinScript(text) && // Sadece seçilen dillerin karakterlerini içerenleri tara
  202. !uniqueTexts.has(text) // Aynı metni birden fazla kez işlememek için
  203. ) {
  204. textNodesToTranslate.push({ node: node, originalText: text });
  205. uniqueTexts.add(text);
  206. // console.log(`[DEBUG] Çevrilecek metin düğümü bulundu: "${text.substring(0, Math.min(text.length, 50))}..."`);
  207. }
  208. }
  209. });
  210.  
  211. if (textNodesToTranslate.length === 0) {
  212. showNotification("Çevrilecek metin bulunamadı (seçilen dillerde).", 'info');
  213. console.log("[DEBUG] Çevrilecek metin düğümü bulunamadı (seçilen dillerde).");
  214. return;
  215. }
  216.  
  217. showNotification(`${textNodesToTranslate.length} adet taranan metin bulundu. Çeviriliyor... (Bu işlem biraz zaman alabilir)`);
  218. console.log(`[DEBUG] Toplam ${textNodesToTranslate.length} adet metin düğümü çeviri için kuyruğa alındı.`);
  219.  
  220. let translatedCount = 0;
  221. let failedCount = 0;
  222.  
  223. // Her metin düğümünü API'nin limitlerine takılmamak için sırayla çevirelim
  224. for (const item of textNodesToTranslate) {
  225. const node = item.node;
  226. const originalText = item.originalText;
  227.  
  228. try {
  229. const translatedText = await translateText(originalText, TARGET_LANG);
  230. console.log(`[DEBUG] Orijinal: "${originalText}" -> Çevrilen: "${translatedText}"`);
  231. if (translatedText && translatedText.trim() !== originalText.trim()) {
  232. node.nodeValue = translatedText;
  233. translatedCount++;
  234. console.log(`[DEBUG] Metin başarıyla güncellendi. Yeni metin: "${node.nodeValue}"`);
  235. } else {
  236. console.log(`[DEBUG] Metin güncellenmedi. Orijinal metinle aynıydı veya boş döndü. (Orijinal: "${originalText}", Gelen: "${translatedText}")`);
  237. }
  238. } catch (error) {
  239. console.warn(`[DEBUG] Metin çevrilemedi: "${originalText.substring(0, Math.min(originalText.length, 50))}..." Hata: ${error}`);
  240. failedCount++;
  241. }
  242. // Her API çağrısı arasında kısa bir gecikme
  243. await new Promise(resolve => setTimeout(resolve, 100));
  244. }
  245.  
  246. if (translatedCount > 0) {
  247. showNotification(`${translatedCount} metin başarıyla çevrildi. ${failedCount} metin çevrilemedi.`, 'success');
  248. } else {
  249. showNotification("Taranan metinler ya zaten hedef dildeydi ya da çevrilecek metin bulunamadı.", 'info');
  250. }
  251. console.log("[DEBUG] Çeviri işlemi tamamlandı.");
  252. }
  253.  
  254. // Sayfa yüklendiğinde ve biraz sonra tekrar çeviriyi dene
  255. window.addEventListener('load', translatePage);
  256. setTimeout(translatePage, 3000);
  257.  
  258. })();