Google Translate API Library

Google Translate API Library for UserScripts

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greasyfork.org/scripts/521561/1508824/Google%20Translate%20API%20Library.js

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==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;
}