Google Translate API Library

Google Translate API Library for UserScripts

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyfork.org/scripts/521561/1508824/Google%20Translate%20API%20Library.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Google Translate API Library
// @namespace    http://tampermonkey.net/
// @version      2024.12.24.2
// @description  Google Translate API Library for UserScripts
// @author       Yuusei
// @grant        GM_xmlhttpRequest
// @icon         https://cdn-icons-png.flaticon.com/512/9502/9502737.png
// ==/UserScript==

const GoogleTranslateAPI = (function () {
	'use strict';

	const translationCache = new Map();
	const CACHE_EXPIRY = 24 * 60 * 60 * 1000;
	const MAX_BATCH_SIZE = 128;
	const MAX_RETRIES = 3;
	const RETRY_DELAY = 1000;

	const languages = {
		auto: 'Automatic',
		af: 'Afrikaans',
		sq: 'Albanian',
		am: 'Amharic',
		ar: 'Arabic',
		hy: 'Armenian',
		az: 'Azerbaijani',
		eu: 'Basque',
		be: 'Belarusian',
		bn: 'Bengali',
		bs: 'Bosnian',
		bg: 'Bulgarian',
		ca: 'Catalan',
		ceb: 'Cebuano',
		ny: 'Chichewa',
		'zh-cn': 'Chinese Simplified',
		'zh-tw': 'Chinese Traditional',
		co: 'Corsican',
		hr: 'Croatian',
		cs: 'Czech',
		da: 'Danish',
		nl: 'Dutch',
		en: 'English',
		eo: 'Esperanto',
		et: 'Estonian',
		tl: 'Filipino',
		fi: 'Finnish',
		fr: 'French',
		fy: 'Frisian',
		gl: 'Galician',
		ka: 'Georgian',
		de: 'German',
		el: 'Greek',
		gu: 'Gujarati',
		ht: 'Haitian Creole',
		ha: 'Hausa',
		haw: 'Hawaiian',
		iw: 'Hebrew',
		hi: 'Hindi',
		hmn: 'Hmong',
		hu: 'Hungarian',
		is: 'Icelandic',
		ig: 'Igbo',
		id: 'Indonesian',
		ga: 'Irish',
		it: 'Italian',
		ja: 'Japanese',
		jw: 'Javanese',
		kn: 'Kannada',
		kk: 'Kazakh',
		km: 'Khmer',
		ko: 'Korean',
		ku: 'Kurdish (Kurmanji)',
		ky: 'Kyrgyz',
		lo: 'Lao',
		la: 'Latin',
		lv: 'Latvian',
		lt: 'Lithuanian',
		lb: 'Luxembourgish',
		mk: 'Macedonian',
		mg: 'Malagasy',
		ms: 'Malay',
		ml: 'Malayalam',
		mt: 'Maltese',
		mi: 'Maori',
		mr: 'Marathi',
		mn: 'Mongolian',
		my: 'Myanmar (Burmese)',
		ne: 'Nepali',
		no: 'Norwegian',
		ps: 'Pashto',
		fa: 'Persian',
		pl: 'Polish',
		pt: 'Portuguese',
		pa: 'Punjabi',
		ro: 'Romanian',
		ru: 'Russian',
		sm: 'Samoan',
		gd: 'Scots Gaelic',
		sr: 'Serbian',
		st: 'Sesotho',
		sn: 'Shona',
		sd: 'Sindhi',
		si: 'Sinhala',
		sk: 'Slovak',
		sl: 'Slovenian',
		so: 'Somali',
		es: 'Spanish',
		su: 'Sundanese',
		sw: 'Swahili',
		sv: 'Swedish',
		tg: 'Tajik',
		ta: 'Tamil',
		te: 'Telugu',
		th: 'Thai',
		tr: 'Turkish',
		uk: 'Ukrainian',
		ur: 'Urdu',
		uz: 'Uzbek',
		vi: 'Vietnamese',
		cy: 'Welsh',
		xh: 'Xhosa',
		yi: 'Yiddish',
		yo: 'Yoruba',
		zu: 'Zulu',
	};

	function validateLanguage(language) {
		if (!language) {
			throw new Error('Language cannot be empty');
		}
		const isoCode = getISOCode(language);
		if (!isoCode) {
			throw new Error(`Language not supported: ${language}`);
		}
		return isoCode;
	}

	function getISOCode(language) {
		if (!language) return false;
		language = language.toLowerCase();
		if (language in languages) return language;

		let keys = Object.keys(languages).filter(key => {
			if (typeof languages[key] !== 'string') return false;
			return languages[key].toLowerCase() === language;
		});

		return keys[0] || false;
	}

	function isSupported(language) {
		return Boolean(getISOCode(language));
	}

	function getCacheKey(text, options) {
		return `${text}|${options.from}|${options.to}`;
	}

	function getFromCache(text, options) {
		const key = getCacheKey(text, options);
		const cached = translationCache.get(key);
		if (cached && Date.now() - cached.timestamp < CACHE_EXPIRY) {
			return cached.result;
		}
		return null;
	}

	function saveToCache(text, options, result) {
		const key = getCacheKey(text, options);
		translationCache.set(key, {
			result,
			timestamp: Date.now(),
		});
	}

	async function translate(text, options = {}) {
		if (!text) throw new Error('Text cannot be empty');

		options.from = options.from || 'auto';
		options.to = options.to || 'en';

		options.from = validateLanguage(options.from);
		options.to = validateLanguage(options.to);

		const cached = getFromCache(text, options);
		if (cached) return cached;

		let baseUrl = 'https://translate.googleapis.com/translate_a/single';
		let data = {
			client: 'gtx',
			sl: options.from,
			tl: options.to,
			dt: 't',
			q: text,
		};

		let url = `${baseUrl}?` + new URLSearchParams(data).toString();

		return new Promise((resolve, reject) => {
			GM_xmlhttpRequest({
				method: 'GET',
				url: url,
				headers: {
					'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
					Accept: '*/*',
				},
				timeout: 10000,
				onload: function (response) {
					try {
						if (response.status !== 200) {
							throw new Error(`HTTP Error: ${response.status}`);
						}

						const body = JSON.parse(response.responseText);

						if (!body || !Array.isArray(body) || !body[0]) {
							throw new Error('Invalid response structure');
						}

						let result = {
							text: '',
							from: {
								language: {
									didYouMean: false,
									iso: body[2] || options.from,
								},
								text: {
									autoCorrected: false,
									value: '',
									didYouMean: false,
								},
							},
							raw: body,
						};

						result.text = body[0]
							.filter(chunk => chunk && chunk[0])
							.map(chunk => decodeURIComponent(chunk[0].trim()))
							.join(' ')
							.trim();

						saveToCache(text, options, result);

						resolve(result);
					} catch (error) {
						reject(new Error('Error processing response: ' + error.message));
					}
				},
				onerror: function (error) {
					reject(new Error('Connection error: ' + error));
				},
				ontimeout: function () {
					reject(new Error('Timeout: Request took too long'));
				},
			});
		});
	}

	async function translateWithRetry(text, options = {}, maxRetries = MAX_RETRIES, delay = RETRY_DELAY) {
		let lastError;
		for (let i = 0; i < maxRetries; i++) {
			try {
				return await translate(text, options);
			} catch (error) {
				lastError = error;
				console.log(`Try ${i + 1}/${maxRetries} failed:`, error);
				if (i < maxRetries - 1) {
					await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
				}
			}
		}
		throw new Error(`Translation failed after ${maxRetries} attempts: ${lastError.message}`);
	}

	async function translateBatch(texts, options = {}) {
		if (!Array.isArray(texts)) {
			throw new Error('Texts must be an array');
		}

		const results = [];
		const chunks = [];

		// Chia nhỏ mảng texts thành các chunks có kích thước MAX_BATCH_SIZE
		for (let i = 0; i < texts.length; i += MAX_BATCH_SIZE) {
			chunks.push(texts.slice(i, i + MAX_BATCH_SIZE));
		}

		// Dịch từng chunk
		for (const chunk of chunks) {
			const chunkResults = await Promise.all(
				chunk.map(text => translateWithRetry(text, options).catch(error => ({ error })))
			);
			results.push(...chunkResults);
		}

		return results;
	}

	function clearExpiredCache() {
		const now = Date.now();
		for (const [key, value] of translationCache.entries()) {
			if (now - value.timestamp > CACHE_EXPIRY) {
				translationCache.delete(key);
			}
		}
	}

	setInterval(clearExpiredCache, 60 * 60 * 1000);

	return {
		translate,
		translateWithRetry,
		translateBatch,
		languages,
		getISOCode,
		isSupported,
		clearExpiredCache,
	};
})();

if (typeof module !== 'undefined' && module.exports) {
	module.exports = GoogleTranslateAPI;
}