Nidhal Baccouri commited on
Commit
dfa459d
·
unverified ·
2 Parent(s): 539cf1c 07738db

Merge pull request #218 from hanlhan/feature/tencent

Browse files
deep_translator/__init__.py CHANGED
@@ -14,6 +14,7 @@ from deep_translator.mymemory import MyMemoryTranslator
14
  from deep_translator.papago import PapagoTranslator
15
  from deep_translator.pons import PonsTranslator
16
  from deep_translator.qcri import QcriTranslator
 
17
  from deep_translator.yandex import YandexTranslator
18
 
19
  __author__ = """Nidhal Baccouri"""
@@ -32,6 +33,7 @@ __all__ = [
32
  "LibreTranslator",
33
  "PapagoTranslator",
34
  "ChatGptTranslator",
 
35
  "BaiduTranslator",
36
  "single_detection",
37
  "batch_detection",
 
14
  from deep_translator.papago import PapagoTranslator
15
  from deep_translator.pons import PonsTranslator
16
  from deep_translator.qcri import QcriTranslator
17
+ from deep_translator.tencent import TencentTranslator
18
  from deep_translator.yandex import YandexTranslator
19
 
20
  __author__ = """Nidhal Baccouri"""
 
33
  "LibreTranslator",
34
  "PapagoTranslator",
35
  "ChatGptTranslator",
36
+ "TencentTranslator",
37
  "BaiduTranslator",
38
  "single_detection",
39
  "batch_detection",
deep_translator/constants.py CHANGED
@@ -7,6 +7,8 @@ LIBRE_ENV_VAR = "LIBRE_API_KEY"
7
  MSFT_ENV_VAR = "MICROSOFT_API_KEY"
8
  QCRI_ENV_VAR = "QCRI_API_KEY"
9
  YANDEX_ENV_VAR = "YANDEX_API_KEY"
 
 
10
  BAIDU_APPID_ENV_VAR = "BAIDU_APPID"
11
  BAIDU_APPKEY_ENV_VAR = "BAIDU_APPKEY"
12
 
@@ -25,6 +27,7 @@ BASE_URLS = {
25
  "PAPAGO_API": "https://openapi.naver.com/v1/papago/n2mt",
26
  "LIBRE": "https://libretranslate.com/",
27
  "LIBRE_FREE": "https://libretranslate.de/",
 
28
  "BAIDU": "https://fanyi-api.baidu.com/api/trans/vip/translate",
29
  }
30
 
@@ -609,6 +612,26 @@ LIBRE_LANGUAGES_TO_CODES = {
609
  "Vietnamese": "vi",
610
  }
611
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  BAIDU_LANGUAGE_TO_CODE = {
613
  "arabic": "ara",
614
  "bulgarian": "bul",
 
7
  MSFT_ENV_VAR = "MICROSOFT_API_KEY"
8
  QCRI_ENV_VAR = "QCRI_API_KEY"
9
  YANDEX_ENV_VAR = "YANDEX_API_KEY"
10
+ TENCENT_SECRET_ID_ENV_VAR = "TENCENT_SECRET_ID"
11
+ TENCENT_SECRET_KEY_ENV_VAR = "TENCENT_SECRET_KEY"
12
  BAIDU_APPID_ENV_VAR = "BAIDU_APPID"
13
  BAIDU_APPKEY_ENV_VAR = "BAIDU_APPKEY"
14
 
 
27
  "PAPAGO_API": "https://openapi.naver.com/v1/papago/n2mt",
28
  "LIBRE": "https://libretranslate.com/",
29
  "LIBRE_FREE": "https://libretranslate.de/",
30
+ "TENENT": "https://tmt.tencentcloudapi.com",
31
  "BAIDU": "https://fanyi-api.baidu.com/api/trans/vip/translate",
32
  }
33
 
 
612
  "Vietnamese": "vi",
613
  }
614
 
615
+ TENCENT_LANGUAGE_TO_CODE = {
616
+ "arabic": "ar",
617
+ "chinese (simplified)": "zh",
618
+ "chinese (traditional)": "zh-TW",
619
+ "english": "en",
620
+ "french": "fr",
621
+ "german": "de",
622
+ "hindi": "hi",
623
+ "indonesian": "id",
624
+ "japanese": "ja",
625
+ "korean": "ko",
626
+ "malay": "ms",
627
+ "portuguese": "pt",
628
+ "russian": "ru",
629
+ "spanish": "es",
630
+ "thai": "th",
631
+ "turkish": "tr",
632
+ "vietnamese": "vi",
633
+ }
634
+
635
  BAIDU_LANGUAGE_TO_CODE = {
636
  "arabic": "ara",
637
  "bulgarian": "bul",
deep_translator/exceptions.py CHANGED
@@ -182,6 +182,19 @@ class AuthorizationException(Exception):
182
  super().__init__(msg, *args)
183
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  class BaiduAPIerror(Exception):
186
  """
187
  exception thrown if Baidu API returns one of its errors
 
182
  super().__init__(msg, *args)
183
 
184
 
185
+ class TencentAPIerror(Exception):
186
+ """
187
+ exception thrown if Tencent API returns one of its errors
188
+ """
189
+
190
+ def __init__(self, api_message):
191
+ self.api_message = str(api_message)
192
+ self.message = "Tencent API returned the following error"
193
+
194
+ def __str__(self):
195
+ return "{}: {}".format(self.message, self.api_message)
196
+
197
+
198
  class BaiduAPIerror(Exception):
199
  """
200
  exception thrown if Baidu API returns one of its errors
deep_translator/tencent.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ tencent translator API
3
+ """
4
+
5
+ __copyright__ = "Copyright (C) 2020 Nidhal Baccouri"
6
+
7
+ import base64
8
+ import hashlib
9
+ import hmac
10
+ import os
11
+ import time
12
+ from typing import List, Optional
13
+
14
+ import requests
15
+
16
+ from deep_translator.base import BaseTranslator
17
+ from deep_translator.constants import (
18
+ BASE_URLS,
19
+ TENCENT_LANGUAGE_TO_CODE,
20
+ TENCENT_SECRET_ID_ENV_VAR,
21
+ TENCENT_SECRET_KEY_ENV_VAR,
22
+ )
23
+ from deep_translator.exceptions import (
24
+ ApiKeyException,
25
+ ServerException,
26
+ TencentAPIerror,
27
+ TranslationNotFound,
28
+ )
29
+ from deep_translator.validate import is_empty, is_input_valid
30
+
31
+
32
+ class TencentTranslator(BaseTranslator):
33
+ """
34
+ class that wraps functions, which use the TentCentTranslator translator
35
+ under the hood to translate word(s)
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ source: str = "en",
41
+ target: str = "zh",
42
+ secret_id: Optional[str] = os.getenv(TENCENT_SECRET_ID_ENV_VAR, None),
43
+ secret_key: Optional[str] = os.getenv(
44
+ TENCENT_SECRET_KEY_ENV_VAR, None
45
+ ),
46
+ **kwargs
47
+ ):
48
+ """
49
+ @param secret_id: your tencent cloud api secret id.
50
+ Get one here: https://console.cloud.tencent.com/capi
51
+ @param secret_key: your tencent cloud api secret key.
52
+ @param source: source language
53
+ @param target: target language
54
+ """
55
+ if not secret_id:
56
+ raise ApiKeyException(env_var=TENCENT_SECRET_ID_ENV_VAR)
57
+
58
+ if not secret_key:
59
+ raise ApiKeyException(env_var=TENCENT_SECRET_KEY_ENV_VAR)
60
+
61
+ self.secret_id = secret_id
62
+ self.secret_key = secret_key
63
+ url = BASE_URLS.get("TENENT")
64
+ super().__init__(
65
+ base_url=url,
66
+ source=source,
67
+ target=target,
68
+ languages=TENCENT_LANGUAGE_TO_CODE,
69
+ **kwargs
70
+ )
71
+
72
+ def translate(self, text: str, **kwargs) -> str:
73
+ """
74
+ @param text: text to translate
75
+ @return: translated text
76
+ """
77
+ if is_input_valid(text):
78
+ if self._same_source_target() or is_empty(text):
79
+ return text
80
+
81
+ # Create the request parameters.
82
+ translate_endpoint = self._base_url.replace("https://", "")
83
+ params = {
84
+ "Action": "TextTranslate",
85
+ "Nonce": 11886,
86
+ "ProjectId": 0,
87
+ "Region": "ap-guangzhou",
88
+ "SecretId": self.secret_id,
89
+ "Source": self.source,
90
+ "SourceText": text,
91
+ "Target": self.target,
92
+ "Timestamp": int(time.time()),
93
+ "Version": "2018-03-21",
94
+ }
95
+ s = "GET" + translate_endpoint + "/?"
96
+ query_str = "&".join(
97
+ "%s=%s" % (k, params[k]) for k in sorted(params)
98
+ )
99
+ hmac_str = hmac.new(
100
+ self.secret_key.encode("utf8"),
101
+ (s + query_str).encode("utf8"),
102
+ hashlib.sha1,
103
+ ).digest()
104
+ params["Signature"] = base64.b64encode(hmac_str)
105
+
106
+ # Do the request and check the connection.
107
+ try:
108
+ response = requests.get(self._base_url, params=params)
109
+ except ConnectionError:
110
+ raise ServerException(503)
111
+ # If the answer is not success, raise server exception.
112
+ if response.status_code != 200:
113
+ raise ServerException(response.status_code)
114
+ # Get the response and check is not empty.
115
+ res = response.json()
116
+ if not res:
117
+ raise TranslationNotFound(text)
118
+ # Process and return the response.
119
+ if "Error" in res["Response"]:
120
+ raise TencentAPIerror(res["Response"]["Error"]["Code"])
121
+ return res["Response"]["TargetText"]
122
+
123
+ def translate_file(self, path: str, **kwargs) -> str:
124
+ return self._translate_file(path, **kwargs)
125
+
126
+ def translate_batch(self, batch: List[str], **kwargs) -> List[str]:
127
+ """
128
+ @param batch: list of texts to translate
129
+ @return: list of translations
130
+ """
131
+ return self._translate_batch(batch, **kwargs)
docs/README.rst CHANGED
@@ -664,6 +664,35 @@ Libre Translator
664
 
665
  translated = LibreTranslator(source='auto', target='en').translate_file('path/to/file')
666
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
 
668
  BaiduTranslator
669
  -----------------
 
664
 
665
  translated = LibreTranslator(source='auto', target='en').translate_file('path/to/file')
666
 
667
+ TencentTranslator
668
+ -----------------
669
+
670
+ .. note::
671
+
672
+ In order to use the TencentTranslator translator, you need to generate a secret_id and a secret_key.
673
+ deep-translator supports both Pro and free APIs. Just check the examples below.
674
+ Visit https://cloud.tencent.com/document/api/551/15619 for more information on how to generate your Tencent secret_id
675
+ and secret_key.
676
+
677
+ - Simple translation
678
+
679
+ .. code-block:: python
680
+
681
+ text = 'Hello world'
682
+ translated = TencentTranslator(secret_id="your-secret_id", secret_key="your-secret_key" source="en", target="zh").translate(text)
683
+
684
+ - Translate batch of texts
685
+
686
+ .. code-block:: python
687
+
688
+ texts = ["Hello world", "How are you?"]
689
+ translated = TencentTranslator(secret_id="your-secret_id", secret_key="your-secret_key" source="en", target="zh").translate_batch(texts)
690
+
691
+ - Translate from a file:
692
+
693
+ .. code-block:: python
694
+
695
+ translated = TencentTranslator(secret_id="your-secret_id", secret_key="your-secret_key" source="en", target="zh").translate_file('path/to/file')
696
 
697
  BaiduTranslator
698
  -----------------
tests/test_tencent.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from unittest.mock import Mock, patch
2
+
3
+ import pytest
4
+
5
+ from deep_translator import TencentTranslator
6
+ from deep_translator.exceptions import TencentAPIerror
7
+
8
+
9
+ @patch("deep_translator.tencent.requests")
10
+ def test_simple_translation(mock_requests):
11
+ translator = TencentTranslator(
12
+ secret_id="imagine-this-is-an-valid-secret-id",
13
+ secret_key="imagine-this-is-an-valid-secret-key",
14
+ source="en",
15
+ target="zh",
16
+ )
17
+ # Set the request response mock.
18
+ mock_response = Mock()
19
+ mock_response.status_code = 200
20
+ mock_response.json.return_value = {
21
+ "Response": {
22
+ "TargetText": "你好",
23
+ "Source": "en",
24
+ "Target": "zh",
25
+ "RequestId": "000ee211-f19e-4a34-a214-e2bb1122d248",
26
+ }
27
+ }
28
+ mock_requests.get.return_value = mock_response
29
+ translation = translator.translate("hello")
30
+ assert translation == "你好"
31
+
32
+
33
+ @patch("deep_translator.tencent.requests")
34
+ def test_wrong_api_key(mock_requests):
35
+ translator = TencentTranslator(
36
+ secret_id="imagine-this-is-a-wrong-secret-id",
37
+ secret_key="imagine-this-is-a-wrong-secret-id",
38
+ source="en",
39
+ target="zh",
40
+ )
41
+
42
+ mock_response = Mock()
43
+ mock_response.status_code = 200
44
+ mock_response.json.return_value = {
45
+ "Response": {
46
+ "Error": {
47
+ "Code": "AuthFailure.SignatureFailure",
48
+ "Message": "The provided credentials could not be validated. \
49
+ Please check your signature is correct.",
50
+ },
51
+ "RequestId": "ed93f3cb-f35e-473f-b9f3-0d451b8b79c6",
52
+ }
53
+ }
54
+ mock_requests.get.return_value = mock_response
55
+ with pytest.raises(TencentAPIerror):
56
+ translator.translate("Hello")
57
+
58
+
59
+ # the remaining tests are actual requests to Tencent translator API and use secret_id and secret_key
60
+ # if secret_id and secret_key variable is None, they are skipped
61
+
62
+ secret_id = None
63
+ secret_key = None
64
+
65
+
66
+ @pytest.mark.skipif(
67
+ secret_id is None or secret_key is None,
68
+ reason="secret_id or secret_key is not provided",
69
+ )
70
+ def test_tencent_successful_post_onetarget():
71
+ posted = TencentTranslator(
72
+ secret_id=secret_id, secret_key=secret_key, source="en", target="zh"
73
+ ).translate("Hello! How are you?")
74
+ assert isinstance(posted, str)