Glossarion / google_free_translate.py
Shirochi's picture
Upload google_free_translate.py
207ab7d verified
# google_free_translate.py
"""
Free Google Translate API implementation using the web endpoint.
No API key or billing required!
"""
import logging
import threading
import time
import json
import requests
from typing import Optional, Dict, Any
import urllib.parse
import random
class GoogleFreeTranslateNew:
"""Google Translate API for free translation using public web endpoints.
Uses the same endpoints as the Google Translate web interface and mobile apps.
No API key or Google Cloud credentials required!
"""
name = 'Google(Free)New'
free = True
endpoint: str = 'https://translate.googleapis.com/translate_a/single'
def __init__(self, source_language: str = "auto", target_language: str = "en", logger=None):
self.source_language = source_language
self.target_language = target_language
self.logger = logger or logging.getLogger(__name__)
self.cache = {} # Simple in-memory cache
self.cache_lock = threading.Lock()
self.request_lock = threading.Lock()
self.last_request_time = 0
self.rate_limit = 0.5 # 500ms between requests to avoid rate limiting
# Multiple user agents to rotate through (helps avoid 403)
self.user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
]
def _get_source_code(self):
"""Get the source language code."""
lang_map = {
"zh": "zh-CN",
"ja": "ja",
"ko": "ko",
"en": "en",
"auto": "auto"
}
return lang_map.get(self.source_language, self.source_language)
def _get_target_code(self):
"""Get the target language code."""
lang_map = {
"en": "en",
"zh": "zh-CN",
"ja": "ja",
"ko": "ko"
}
return lang_map.get(self.target_language, self.target_language)
def get_headers(self):
"""Get request headers that mimic a browser with random user agent."""
return {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': random.choice(self.user_agents), # Random user agent to avoid detection
'Referer': 'https://translate.google.com/',
'Origin': 'https://translate.google.com',
}
def _get_cache_key(self, text: str) -> str:
"""Generate cache key for the translation."""
return f"{self.source_language}_{self.target_language}_{hash(text)}"
def _rate_limit_delay(self):
"""Ensure rate limiting between requests."""
with self.request_lock:
current_time = time.time()
time_since_last = current_time - self.last_request_time
if time_since_last < self.rate_limit:
sleep_time = self.rate_limit - time_since_last
time.sleep(sleep_time)
self.last_request_time = time.time()
def translate(self, text: str) -> Dict[str, Any]:
"""
Translate text using Google's free web API.
Args:
text: Text to translate
Returns:
Dict with 'translatedText' and 'detectedSourceLanguage' keys
"""
if not text or not text.strip():
return {
'translatedText': '',
'detectedSourceLanguage': self.source_language
}
# Check cache first
cache_key = self._get_cache_key(text)
with self.cache_lock:
if cache_key in self.cache:
self.logger.debug(f"Cache hit for translation: {text[:50]}...")
return self.cache[cache_key]
# Rate limiting
self._rate_limit_delay()
try:
# Prepare request data
source_lang = self._get_source_code()
target_lang = self._get_target_code()
# Try multiple FREE endpoint formats (no credentials needed)
# These are public endpoints used by the web interface and mobile apps
# Ordered from most reliable to least reliable based on common usage
endpoints_to_try = [
'https://translate.googleapis.com/translate_a/single', # Most reliable public endpoint
'https://clients5.google.com/translate_a/t', # Mobile client endpoint
'https://translate.google.com/translate_a/single', # Direct web endpoint
'https://clients5.google.com/translate_a/single', # Alternative client5
]
for endpoint_url in endpoints_to_try:
try:
if 'clients5.google.com/translate_a/t' in endpoint_url:
# Use mobile client API format
result = self._translate_via_mobile_api(text, source_lang, target_lang, endpoint_url)
else:
# Use the public translate_a/single API format
result = self._translate_via_single_api(text, source_lang, target_lang, endpoint_url)
if result:
# Cache successful result
with self.cache_lock:
self.cache[cache_key] = result
# Limit cache size
if len(self.cache) > 1000:
# Remove oldest entries
oldest_keys = list(self.cache.keys())[:100]
for key in oldest_keys:
del self.cache[key]
return result
except Exception as e:
self.logger.warning(f"Failed with endpoint {endpoint_url}: {e}")
continue
# If all endpoints failed
raise Exception("All Google Translate endpoints failed")
except Exception as e:
self.logger.error(f"Translation failed: {e}")
# Return original text as fallback
return {
'translatedText': text,
'detectedSourceLanguage': self.source_language,
'error': str(e)
}
def _translate_via_single_api(self, text: str, source_lang: str, target_lang: str, endpoint_url: str) -> Optional[Dict[str, Any]]:
"""Translate using the translate_a/single endpoint (older format)."""
# Try multiple client types to avoid 403 errors
client_types = ['gtx', 'webapp', 't', 'dict-chrome-ex', 'android']
for client_type in client_types:
params = {
'client': client_type,
'sl': source_lang,
'tl': target_lang,
'dt': 't',
'q': text
}
# Add additional parameters for certain clients
if client_type == 'webapp':
params['dj'] = '1' # Return JSON format
params['dt'] = ['t', 'bd', 'ex', 'ld', 'md', 'qca', 'rw', 'rm', 'ss', 'at']
elif client_type == 'dict-chrome-ex':
params['dt'] = ['t', 'bd']
try:
result = self._try_single_api_request(params, endpoint_url, client_type)
if result:
return result
except Exception as e:
self.logger.debug(f"Client type {client_type} failed: {e}")
continue
return None
def _try_single_api_request(self, params: dict, endpoint_url: str, client_type: str) -> Optional[Dict[str, Any]]:
"""Try a single API request with given parameters."""
response = requests.get(
endpoint_url,
params=params,
headers=self.get_headers(),
timeout=10
)
if response.status_code == 200:
try:
result = response.json()
# Handle webapp format (dj=1 returns different structure)
if client_type == 'webapp' and isinstance(result, dict):
if 'sentences' in result:
translated_parts = []
for sentence in result['sentences']:
if 'trans' in sentence:
translated_parts.append(sentence['trans'])
translated_text = ''.join(translated_parts)
detected_lang = result.get('src', params.get('sl', 'auto'))
return {
'translatedText': translated_text,
'detectedSourceLanguage': detected_lang
}
# Handle standard array format
elif isinstance(result, list) and len(result) > 0:
# Extract translated text
translated_parts = []
for item in result[0]:
if isinstance(item, list) and len(item) > 0:
translated_parts.append(item[0])
translated_text = ''.join(translated_parts)
# Try to detect source language from response
detected_lang = params.get('sl', 'auto')
if len(result) > 2 and isinstance(result[2], str):
detected_lang = result[2]
return {
'translatedText': translated_text,
'detectedSourceLanguage': detected_lang
}
except (json.JSONDecodeError, KeyError, IndexError, TypeError) as e:
self.logger.warning(f"Failed to parse single API response with client {client_type}: {e}")
return None
def _translate_via_mobile_api(self, text: str, source_lang: str, target_lang: str, endpoint_url: str) -> Optional[Dict[str, Any]]:
"""Translate using the mobile client endpoint (clients5.google.com/translate_a/t)."""
params = {
'client': 't', # Mobile client
'sl': source_lang,
'tl': target_lang,
'q': text
}
# Use different headers for mobile endpoint
mobile_headers = {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9',
'User-Agent': 'GoogleTranslate/6.29.59279 (Linux; U; Android 11; Pixel 5)',
'Content-Type': 'application/x-www-form-urlencoded',
}
response = requests.post(
endpoint_url,
data=params,
headers=mobile_headers,
timeout=10
)
if response.status_code == 200:
try:
# This endpoint returns a simple JSON array
result = response.json()
if isinstance(result, list) and len(result) > 0:
# Extract translated text from first element
if isinstance(result[0], str):
translated_text = result[0]
elif isinstance(result[0], list) and len(result[0]) > 0:
translated_text = result[0][0]
else:
return None
return {
'translatedText': translated_text,
'detectedSourceLanguage': source_lang
}
except (json.JSONDecodeError, KeyError, IndexError, TypeError) as e:
self.logger.warning(f"Failed to parse mobile API response: {e}")
return None
# Convenience function for easy usage
def translate_text(text: str, source_lang: str = "auto", target_lang: str = "en") -> str:
"""
Simple function to translate text using Google's free API.
Args:
text: Text to translate
source_lang: Source language code (default: auto-detect)
target_lang: Target language code (default: en)
Returns:
Translated text
"""
translator = GoogleFreeTranslateNew(source_lang, target_lang)
result = translator.translate(text)
return result.get('translatedText', text)
if __name__ == "__main__":
# Test the translator
translator = GoogleFreeTranslateNew("auto", "en")
test_texts = [
"こんにちは、世界!", # Japanese
"안녕하세요 세계!", # Korean
"你好世界!", # Chinese
"Hola mundo" # Spanish
]
for test_text in test_texts:
result = translator.translate(test_text)
print(f"Original: {test_text}")
print(f"Translated: {result['translatedText']}")
print(f"Detected language: {result['detectedSourceLanguage']}")
print("-" * 50)