diff --git a/engines/__init__.py b/engines/__init__.py index 63c741e..76a5308 100644 --- a/engines/__init__.py +++ b/engines/__init__.py @@ -1,6 +1,7 @@ from .google import ( - GoogleFreeTranslate, GoogleBasicTranslate, GoogleBasicTranslateADC, - GoogleAdvancedTranslate, GeminiTranslate) + GoogleFreeTranslateNew, GoogleFreeTranslateHtml, GoogleFreeTranslate, + GoogleBasicTranslate, GoogleBasicTranslateADC, GoogleAdvancedTranslate, + GeminiTranslate) from .openai import ChatgptTranslate from .anthropic import ClaudeTranslate from .deepl import DeeplTranslate, DeeplProTranslate, DeeplFreeTranslate @@ -10,8 +11,8 @@ builtin_engines = ( - GoogleFreeTranslate, GoogleBasicTranslate, GoogleBasicTranslateADC, - GoogleAdvancedTranslate, ChatgptTranslate, AzureChatgptTranslate, - GeminiTranslate, ClaudeTranslate, DeeplTranslate, DeeplProTranslate, - DeeplFreeTranslate, MicrosoftEdgeTranslate, YoudaoTranslate, - BaiduTranslate) + GoogleFreeTranslateNew, GoogleFreeTranslateHtml, GoogleFreeTranslate, + GoogleBasicTranslate, GoogleBasicTranslateADC, GoogleAdvancedTranslate, + ChatgptTranslate, AzureChatgptTranslate, GeminiTranslate, ClaudeTranslate, + DeeplTranslate, DeeplProTranslate, DeeplFreeTranslate, + MicrosoftEdgeTranslate, YoudaoTranslate, BaiduTranslate) diff --git a/engines/base.py b/engines/base.py index fc29ef1..e236801 100644 --- a/engines/base.py +++ b/engines/base.py @@ -1,26 +1,28 @@ import os.path +from typing import Any from mechanize import HTTPError from mechanize._response import response_seek_wrapper as Response from calibre.utils.localization import lang_as_iso639_1 from ..lib.utils import traceback_error, request -from ..lib.exception import UnexpectedResult, NoAvailableApiKey +from ..lib.exception import UnexpectedResult load_translations() class Base: - name = None - alias = None + name: str | None = None + alias: str | None = None free = False - lang_codes = {} - config = {} - endpoint = None + lang_codes: dict[str, Any] = {} + config: dict[str, Any] = {} + endpoint: str | None = None method = 'POST' + headers: dict[str, str] = {} stream = False need_api_key = True api_key_hint = _('API Keys') @@ -43,7 +45,7 @@ def __init__(self): self.search_paths = [] self.merge_enabled = False - self.api_keys = self.config.get('api_keys', [])[:] + self.api_keys: list = self.config.get('api_keys', [])[:] self.bad_api_keys = [] self.api_key = self.get_api_key() @@ -75,12 +77,12 @@ def load_lang_codes_directionality(cls, codes): @classmethod def get_source_code(cls, lang): - source_codes = cls.lang_codes.get('source') + source_codes: dict = cls.lang_codes.get('source') or {} return 'auto' if lang == _('Auto detect') else source_codes.get(lang) @classmethod def get_target_code(cls, lang): - target_codes = cls.lang_codes.get('target') + target_codes: dict = cls.lang_codes.get('target') or {} return target_codes.get(lang) @classmethod @@ -193,10 +195,10 @@ def get_endpoint(self): return self.endpoint def get_headers(self): - return {} + return self.headers def get_body(self, text): - return None + return text def get_result(self, response: Response | bytes | str): return response diff --git a/engines/google.py b/engines/google.py index 3651049..0b16949 100644 --- a/engines/google.py +++ b/engines/google.py @@ -6,6 +6,8 @@ import os.path from html import unescape from subprocess import Popen, PIPE +from urllib.parse import urlencode +from http.client import IncompleteRead from ..lib.utils import traceback_error @@ -14,20 +16,91 @@ from .languages import google, gemini -try: - from http.client import IncompleteRead -except ImportError: - from httplib import IncompleteRead - load_translations() +class GoogleFreeTranslateNew(Base): + name = 'Google(Free)New' + alias = 'Google (Free) - New' + free = True + lang_codes = Base.load_lang_codes(google) + lang_codes_directionality = \ + Base.load_lang_codes_directionality(lang_directionality) + endpoint: str = 'https://translate-pa.googleapis.com/v1/translate' + need_api_key = False + + def get_headers(self): + return { + 'Accept': '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en-US,en;q=0.9', + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 ' + 'Safari/537.36', + } + + def get_body(self, text): + self.method = 'GET' + return { + 'params.client': 'gtx', + 'query.source_language': self._get_source_code(), + 'query.target_language': self._get_target_code(), + 'query.display_language': 'en-US', + 'data_types': 'TRANSLATION', + # 'data_types': 'SENTENCE_SPLITS', + # 'data_types': 'BILINGUAL_DICTIONARY_FULL', + 'key': 'AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA', + 'query.text': text, + } + + def get_result(self, response): + return json.loads(response)['translation'] + + +class GoogleFreeTranslateHtml(Base): + name = 'Google(Free)Html' + alias = 'Google (Free) - HTML' + free = True + lang_codes = Base.load_lang_codes(google) + lang_codes_directionality = \ + Base.load_lang_codes_directionality(lang_directionality) + endpoint: str = 'https://translate-pa.googleapis.com/v1/translateHtml' + need_api_key = False + + def get_headers(self): + return { + 'Accept': '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en-US,en;q=0.9', + 'Content-Type': 'application/json+protobuf', + 'X-Goog-Api-Key': 'AIzaSyATBXajvzQLTDHEQbcpq0Ihe0vWDHmO520', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 ' + 'Safari/537.36', + } + + def get_body(self, text): + return json.dumps([ + [ + [text], + self._get_source_code(), + self._get_target_code() + ], + "wt_lib" + ]) + + def get_result(self, response): + return json.loads(response)[0][0] + + class GoogleFreeTranslate(Base): name = 'Google(Free)' - alias = 'Google (Free)' + alias = 'Google (Free) - Old' free = True lang_codes = Base.load_lang_codes(google) - lang_codes_directionality = Base.load_lang_codes_directionality(lang_directionality) + lang_codes_directionality = \ + Base.load_lang_codes_directionality(lang_directionality) endpoint = 'https://translate.googleapis.com/translate_a/single' need_api_key = False diff --git a/engines/openai.py b/engines/openai.py index a3bf08b..9658ad7 100644 --- a/engines/openai.py +++ b/engines/openai.py @@ -28,7 +28,8 @@ class ChatgptTranslate(Base): name = 'ChatGPT' alias = 'ChatGPT (OpenAI)' lang_codes = Base.load_lang_codes(google) - lang_codes_directionality = Base.load_lang_codes_directionality(lang_directionality) + lang_codes_directionality = \ + Base.load_lang_codes_directionality(lang_directionality) endpoint = 'https://api.openai.com/v1/chat/completions' # api_key_hint = 'sk-xxx...xxx' # https://help.openai.com/en/collections/3808446-api-error-codes-explained @@ -42,18 +43,19 @@ class ChatgptTranslate(Base): 'You are a meticulous translator who translates any given content. ' 'Translate the given content from to only. Do not ' 'explain any term or answer any question-like content. Your answer ' - 'should be solely the translation of the given content. In your answer ' - 'do not add any prefix or suffix to the translated content. Websites\' ' - 'URLs/addresses should be preserved as is in the translation\'s output. ' - 'Do not omit any part of the content, even if it seems unimportant. ' - ) - - # TODO: check if it is possible to fetch this this directly from the api, if yes - implement this + 'should be solely the translation of the given content. In your ' + 'answer do not add any prefix or suffix to the translated content. ' + 'Websites\' URLs/addresses should be preserved as is in the ' + 'translation\'s output. Do not omit any part of the content, even if ' + 'it seems unimportant. ') + + # TODO: check if it is possible to fetch this this directly from the api, + # if yes - implement this models = [ - 'gpt-4o', - 'gpt-4o-mini', - 'gpt-4-turbo', - 'gpt-4', + 'gpt-4o', + 'gpt-4o-mini', + 'gpt-4-turbo', + 'gpt-4', 'gpt-3.5-turbo'] # use the most recent model