Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added baidu support #219

Merged
merged 7 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deep_translator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

__copyright__ = "Copyright (C) 2020 Nidhal Baccouri"

from deep_translator.baidu import BaiduTranslator
from deep_translator.chatgpt import ChatGptTranslator
from deep_translator.deepl import DeeplTranslator
from deep_translator.detection import batch_detection, single_detection
Expand Down Expand Up @@ -31,6 +32,7 @@
"LibreTranslator",
"PapagoTranslator",
"ChatGptTranslator",
"BaiduTranslator",
"single_detection",
"batch_detection",
]
120 changes: 120 additions & 0 deletions deep_translator/baidu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
baidu translator API
"""

__copyright__ = "Copyright (C) 2020 Nidhal Baccouri"

import hashlib
import os
import random
from typing import List, Optional

import requests

from deep_translator.base import BaseTranslator
from deep_translator.constants import (
BAIDU_APPID_ENV_VAR,
BAIDU_APPKEY_ENV_VAR,
BAIDU_LANGUAGE_TO_CODE,
BASE_URLS,
)
from deep_translator.exceptions import (
ApiKeyException,
BaiduAPIerror,
ServerException,
TranslationNotFound,
)
from deep_translator.validate import is_empty, is_input_valid


class BaiduTranslator(BaseTranslator):
"""
class that wraps functions, which use the BaiduTranslator translator
under the hood to translate word(s)
"""

def __init__(
self,
source: str = "en",
target: str = "zh",
appid: Optional[str] = os.getenv(BAIDU_APPID_ENV_VAR, None),
appkey: Optional[str] = os.getenv(BAIDU_APPKEY_ENV_VAR, None),
**kwargs
):
"""
@param appid: your baidu cloud api appid.
Get one here: https://fanyi-api.baidu.com/choose
@param appkey: your baidu cloud api appkey.
@param source: source language
@param target: target language
"""
if not appid:
raise ApiKeyException(env_var=BAIDU_APPID_ENV_VAR)

if not appkey:
raise ApiKeyException(env_var=BAIDU_APPKEY_ENV_VAR)

self.appid = appid
self.appkey = appkey
super().__init__(
base_url=BASE_URLS.get("BAIDU"),
source=source,
target=target,
languages=BAIDU_LANGUAGE_TO_CODE,
**kwargs
)

def translate(self, text: str, **kwargs) -> str:
"""
@param text: text to translate
@return: translated text
"""
if is_input_valid(text):
if self._same_source_target() or is_empty(text):
return text

# Create the request parameters.
salt = random.randint(32768, 65536)
sign = hashlib.md5(
(self.appid + text + str(salt) + self.appkey).encode("utf-8")
).hexdigest()
headers = {"Content-Type": "application/x-www-form-urlencoded"}
payload = {
"appid": self.appid,
"q": text,
"from": self.source,
"to": self.target,
"salt": salt,
"sign": sign,
}

# Do the request and check the connection.
try:
response = requests.post(
self._base_url, params=payload, headers=headers
)
except ConnectionError:
raise ServerException(503)
if response.status_code != 200:
raise ServerException(response.status_code)
# Get the response and check is not empty.
res = response.json()
if not res:
raise TranslationNotFound(text)
# Process and return the response.
if "error_code" in res:
raise BaiduAPIerror(res["error_msg"])
if "trans_result" in res:
return "\n".join([s["dst"] for s in res["trans_result"]])
else:
raise TranslationNotFound(text)

def translate_file(self, path: str, **kwargs) -> str:
return self._translate_file(path, **kwargs)

def translate_batch(self, batch: List[str], **kwargs) -> List[str]:
"""
@param batch: list of texts to translate
@return: list of translations
"""
return self._translate_batch(batch, **kwargs)
34 changes: 34 additions & 0 deletions deep_translator/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
MSFT_ENV_VAR = "MICROSOFT_API_KEY"
QCRI_ENV_VAR = "QCRI_API_KEY"
YANDEX_ENV_VAR = "YANDEX_API_KEY"
BAIDU_APPID_ENV_VAR = "BAIDU_APPID"
BAIDU_APPKEY_ENV_VAR = "BAIDU_APPKEY"


BASE_URLS = {
Expand All @@ -23,6 +25,7 @@
"PAPAGO_API": "https://openapi.naver.com/v1/papago/n2mt",
"LIBRE": "https://libretranslate.com/",
"LIBRE_FREE": "https://libretranslate.de/",
"BAIDU": "https://fanyi-api.baidu.com/api/trans/vip/translate",
}

GOOGLE_LANGUAGES_TO_CODES = {
Expand Down Expand Up @@ -280,3 +283,34 @@
"Turkish": "tr",
"Vietnamese": "vi",
}

BAIDU_LANGUAGE_TO_CODE = {
"arabic": "ara",
"bulgarian": "bul",
"chinese (classical)": "wyw",
"chinese (simplified)": "zh",
"chinese (traditional)": "cht",
"czech": "cs",
"danish": "dan",
"dutch": "nl",
"english": "en",
"estonian": "est",
"finnish": "fin",
"french": "fra",
"german": "de",
"greek": "el",
"hungarian": "hu",
"italian": "it",
"japanese": "jp",
"korean": "kor",
"polish": "pl",
"portuguese": "pt",
"romanian": "ro",
"russian": "ru",
"slovenian": "slo",
"spanish": "spa",
"swedish": "swe",
"thai": "th",
"vietnamese": "vie",
"yueyu": "yue",
}
13 changes: 13 additions & 0 deletions deep_translator/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,16 @@ class AuthorizationException(Exception):
def __init__(self, api_key, *args):
msg = "Unauthorized access with the api key " + api_key
super().__init__(msg, *args)


class BaiduAPIerror(Exception):
"""
exception thrown if Baidu API returns one of its errors
"""

def __init__(self, api_message):
self.api_message = str(api_message)
self.message = "Baidu API returned the following error"

def __str__(self):
return "{}: {}".format(self.message, self.api_message)
60 changes: 60 additions & 0 deletions docs/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,66 @@ Libre Translator
translated = LibreTranslator(source='auto', target='en').translate_file('path/to/file')


BaiduTranslator
-----------------

.. note::

In order to use the BaiduTranslator translator, you need to generate a secret_id and a secret_key.
deep-translator supports both Pro and free APIs. Just check the examples below.
Visit http://api.fanyi.baidu.com/product/113 for more information on how to generate your Baidu appid
and appkey.

- Simple translation

.. code-block:: python

text = 'Hello world'
translated = BaiduTranslator(appid="your-appid", appkey="your-appkey" source="en", target="zh").translate(text)

- Translate batch of texts

.. code-block:: python
=
texts = ["Hello world", "How are you?"]
translated = BaiduTranslator(appid="your-appid", appkey="your-appkey" source="en", target="zh").translate_batch(texts)

- Translate from a file:

.. code-block:: python

translated = BaiduTranslator(appid="your-appid", appkey="your-appkey" source="en", target="zh").translate_file('path/to/file')

BaiduTranslator
-----------------

.. note::

In order to use the BaiduTranslator translator, you need to generate a secret_id and a secret_key.
deep-translator supports both Pro and free APIs. Just check the examples below.
Visit http://api.fanyi.baidu.com/product/113 for more information on how to generate your Baidu appid
and appkey.

- Simple translation

.. code-block:: python

text = 'Hello world'
translated = BaiduTranslator(appid="your-appid", appkey="your-appkey" source="en", target="zh").translate(text)

- Translate batch of texts

.. code-block:: python
=
texts = ["Hello world", "How are you?"]
translated = BaiduTranslator(appid="your-appid", appkey="your-appkey" source="en", target="zh").translate_batch(texts)

- Translate from a file:

.. code-block:: python

translated = BaiduTranslator(appid="your-appid", appkey="your-appkey" source="en", target="zh").translate_file('path/to/file')


Proxy usage
-------------
Expand Down
65 changes: 65 additions & 0 deletions tests/test_baidu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from unittest.mock import Mock, patch

import pytest

from deep_translator import BaiduTranslator
from deep_translator.exceptions import BaiduAPIerror


@patch("deep_translator.baidu.requests")
def test_simple_translation(mock_requests):
translator = BaiduTranslator(
appid="this-is-an-valid-appid",
appkey="this-is-an-valid-appkey",
source="en",
target="zh",
)
# Set the request response mock.
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"from": "en",
"to": "zh",
"trans_result": [{"src": "hello", "dst": "你好"}],
}
mock_requests.post.return_value = mock_response
translation = translator.translate("hello")
assert translation == "你好"


@patch("deep_translator.baidu.requests.get")
def test_wrong_api_key(mock_requests):
translator = BaiduTranslator(
appid="this-is-a-wrong-appid",
appkey="this-is-a-wrong-appkey",
source="en",
target="zh",
)
# Set the response status_code only.
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"error_code": "54001",
"error_msg": "Invalid Sign",
}
mock_requests.post.return_value = mock_response
with pytest.raises(BaiduAPIerror):
translator.translate("Hello")


# the remaining tests are actual requests to Baidu translator API and use appid and appkey
# if appid and appkey variable is None, they are skipped

appid = None
appkey = None


@pytest.mark.skipif(
appid is None or appkey is None,
reason="appid or appkey is not provided",
)
def test_baidu_successful_post_onetarget():
posted = BaiduTranslator(
appid=appid, appkey=appkey, source="en", target="zh"
).translate("Hello! How are you?")
assert isinstance(posted, str)