BackTranslation 0.4.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation/languages.py +1 -0
  2. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation/translated.py +4 -5
  3. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation/translation.py +55 -37
  4. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation/translation_Baidu.py +42 -0
  5. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation.egg-info/PKG-INFO +56 -7
  6. {backtranslation-0.4.0 → backtranslation-0.5.0}/PKG-INFO +56 -7
  7. {backtranslation-0.4.0 → backtranslation-0.5.0}/README.md +56 -7
  8. {backtranslation-0.4.0 → backtranslation-0.5.0}/tests/test_translated.py +13 -0
  9. {backtranslation-0.4.0 → backtranslation-0.5.0}/tests/test_translation.py +107 -0
  10. {backtranslation-0.4.0 → backtranslation-0.5.0}/tests/test_translation_baidu.py +82 -0
  11. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation/__init__.py +0 -0
  12. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation.egg-info/SOURCES.txt +0 -0
  13. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation.egg-info/dependency_links.txt +0 -0
  14. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation.egg-info/requires.txt +0 -0
  15. {backtranslation-0.4.0 → backtranslation-0.5.0}/BackTranslation.egg-info/top_level.txt +0 -0
  16. {backtranslation-0.4.0 → backtranslation-0.5.0}/LICENSE +0 -0
  17. {backtranslation-0.4.0 → backtranslation-0.5.0}/setup.cfg +0 -0
  18. {backtranslation-0.4.0 → backtranslation-0.5.0}/setup.py +0 -0
  19. {backtranslation-0.4.0 → backtranslation-0.5.0}/tests/__init__.py +0 -0
@@ -15,6 +15,7 @@ LANG_CODES = {
15
15
  'chichewa': 'ny',
16
16
  'chinese (simplified)': 'zh-cn',
17
17
  'chinese (traditional)': 'zh-tw',
18
+ 'chinese (cantonese)': 'yue',
18
19
  'corsican': 'co',
19
20
  'croatian': 'hr',
20
21
  'czech': 'cs',
@@ -2,23 +2,22 @@ class Translated(object):
2
2
  """
3
3
  Save the translated result
4
4
  """
5
- def __init__(self, src_lang, tmp_lang, text, trans_text, back_text):
5
+ def __init__(self, src_lang, tmp_lang, text, trans_text, back_text, dst_lang=None, mode='back_translation'):
6
6
  self.source_text = text
7
7
  self.src = src_lang
8
8
  self.tmp = tmp_lang
9
+ self.dst = dst_lang if dst_lang is not None else tmp_lang
10
+ self.mode = mode
9
11
  self.tran_text = trans_text
10
12
  self.result_text = back_text
11
13
 
12
-
13
14
  def __str__(self):
14
15
  return self.__unicode__()
15
16
 
16
-
17
-
18
17
  def __unicode__(self):
19
18
  if len(self.result_text.split(' ')) > 30:
20
19
  show_text = ' '.join(self.result_text.split(' ')[:30])
21
20
  show_text += '...'
22
21
  else:
23
22
  show_text = self.result_text
24
- return u'Translated(src={src}, tmp={tmp}, result_text={text})'.format(src=self.src, tmp=self.tmp, text=show_text)
23
+ return u'Translated(src={src}, tmp={tmp}, result_text={text})'.format(src=self.src, tmp=self.tmp, text=show_text)
@@ -31,12 +31,8 @@ class BackTranslation(object):
31
31
  self.MAX_LENGTH = 5000
32
32
 
33
33
  def translate(self, text, src=None, tmp=None, sleeping=0):
34
-
35
- if not src:
36
- src = self.translator.detect(text).lang
37
-
38
- if src.lower() not in self.Languages:
39
- raise ValueError("'{}': INVALID source language.".format(src))
34
+ src = self._resolve_src(text, src)
35
+ self._validate_language(src, 'source')
40
36
 
41
37
  # if tmp is null, set a default language for tmp.
42
38
  if not tmp:
@@ -45,52 +41,74 @@ class BackTranslation(object):
45
41
  else:
46
42
  tmp = 'en'
47
43
 
48
- if tmp not in self.Languages:
49
- raise ValueError("'{}': INVALID transited language.".format(tmp))
50
-
51
- if src == tmp:
52
- raise ValueError("Transited language ({tmp}) should different from source language ({src}).".format(
53
- tmp=tmp, src=src))
44
+ self._validate_language(tmp, 'transited')
45
+ self._validate_different(src, tmp, 'Transited')
54
46
 
55
47
  # check the length of text
56
48
  if len(text) > self.MAX_LENGTH:
57
49
  original_sentences = self._split_segement(sent_tokenize(text))
58
50
 
59
- try:
60
- t_text = self.translator.translate(original_sentences, src=src, dest=tmp)
61
- except httpcore.ConnectTimeout:
62
- raise httpcore.ConnectTimeout("Connection timed out. If you are blocked by Google, try using the 'proxies' parameter or a different 'url'.")
63
- except Exception as e:
64
- raise Exception("Translation failed (try increasing the 'sleeping' parameter): {}".format(e))
51
+ t_text = self._translate_with_google(original_sentences, src, tmp, 'Translation')
65
52
  tran_text = ' '.join([t.text for t in t_text])
66
53
  time.sleep(sleeping)
67
- try:
68
- r_text = self.translator.translate([t.text for t in t_text], src=tmp, dest=src)
69
- except httpcore.ConnectTimeout:
70
- raise httpcore.ConnectTimeout("Connection timed out. If you are blocked by Google, try using the 'proxies' parameter or a different 'url'.")
71
- except Exception as e:
72
- raise Exception("Back-translation failed (try increasing the 'sleeping' parameter): {}".format(e))
54
+ r_text = self._translate_with_google([t.text for t in t_text], tmp, src, 'Back-translation')
73
55
  back_text = ' '.join([r.text for r in r_text])
74
56
  back_text.rstrip()
75
57
  else:
76
- try:
77
- mid_text = self.translator.translate(text, src=src, dest=tmp)
78
- except httpcore.ConnectTimeout:
79
- raise httpcore.ConnectTimeout("Connection timed out. If you are blocked by Google, try using the 'proxies' parameter or a different 'url'.")
80
- except Exception as e:
81
- raise Exception("Translation failed (try increasing the 'sleeping' parameter): {}".format(e))
58
+ mid_text = self._translate_with_google(text, src, tmp, 'Translation')
82
59
  tran_text = mid_text.text
83
60
  time.sleep(sleeping) # Sleep between translation
84
- try:
85
- result_text = self.translator.translate(tran_text, src=tmp, dest=src)
86
- except httpcore.ConnectTimeout:
87
- raise httpcore.ConnectTimeout("Connection timed out. If you are blocked by Google, try using the 'proxies' parameter or a different 'url'.")
88
- except Exception as e:
89
- raise Exception("Back-translation failed (try increasing the 'sleeping' parameter): {}".format(e))
61
+ result_text = self._translate_with_google(tran_text, tmp, src, 'Back-translation')
90
62
  back_text = result_text.text
91
- result = Translated(src_lang=src, tmp_lang=tmp, text=text, trans_text=tran_text, back_text=back_text)
63
+ result = Translated(src_lang=src, tmp_lang=tmp, text=text, trans_text=tran_text, back_text=back_text,
64
+ dst_lang=tmp, mode='back_translation')
92
65
  return result
93
66
 
67
+ def direct_translate(self, text, src=None, dst=None, sleeping=0):
68
+ if not dst:
69
+ raise ValueError("destination language is required")
70
+
71
+ src = self._resolve_src(text, src)
72
+
73
+ self._validate_language(src, 'source')
74
+ self._validate_language(dst, 'destination')
75
+ self._validate_different(src, dst, 'Destination')
76
+
77
+ if len(text) > self.MAX_LENGTH:
78
+ original_sentences = self._split_segement(sent_tokenize(text))
79
+ translated_text = self._translate_with_google(original_sentences, src, dst, 'Translation')
80
+ result_text = ' '.join([t.text for t in translated_text])
81
+ else:
82
+ translated_text = self._translate_with_google(text, src, dst, 'Translation')
83
+ result_text = translated_text.text
84
+
85
+ result = Translated(src_lang=src, tmp_lang=dst, text=text, trans_text=result_text, back_text=result_text,
86
+ dst_lang=dst, mode='direct')
87
+ return result
88
+
89
+ def _resolve_src(self, text, src):
90
+ if not src:
91
+ return self.translator.detect(text).lang
92
+ return src
93
+
94
+ def _validate_language(self, language, language_type):
95
+ if language.lower() not in self.Languages:
96
+ raise ValueError("'{}': INVALID {} language.".format(language, language_type))
97
+
98
+ def _validate_different(self, src, dst, dst_label):
99
+ if src == dst:
100
+ raise ValueError("{} language ({dst}) should different from source language ({src}).".format(
101
+ dst_label, dst=dst, src=src))
102
+
103
+ def _translate_with_google(self, text, src, dst, action):
104
+ try:
105
+ return self.translator.translate(text, src=src, dest=dst)
106
+ except httpcore.ConnectTimeout:
107
+ raise httpcore.ConnectTimeout(
108
+ "Connection timed out. If you are blocked by Google, try using the 'proxies' parameter or a different 'url'.")
109
+ except Exception as e:
110
+ raise Exception("{} failed (try increasing the 'sleeping' parameter): {}".format(action, e))
111
+
94
112
  def _split_segement(self, sentences):
95
113
  """
96
114
  Split the long sentences into multiple sentences whose lengths are less than MAX_LENGTH.
@@ -72,6 +72,48 @@ class BackTranslation_Baidu(object):
72
72
  result = Translated(src_lang=src, tmp_lang=tmp, text=text, trans_text=tran_text, back_text=back_text)
73
73
  return result
74
74
 
75
+ def direct_translate(self, text, src='auto', dst=None, sleeping=1):
76
+ if not dst:
77
+ raise ValueError("destination language is required")
78
+
79
+ if src == dst:
80
+ raise ValueError(
81
+ "Destination language ({dst}) should different from source language ({src}).".format(dst=dst, src=src))
82
+
83
+ if len(text.encode('utf-8')) > self.MAX_LENGTH:
84
+ original_sentences = [
85
+ sentence for sentence in self._split_segement(sent_tokenize(text))
86
+ if sentence != ""
87
+ ]
88
+ tran_text = []
89
+ resolved_src = src
90
+ for index, sentence in enumerate(original_sentences):
91
+ result = self._sendRequest(sentence, resolved_src, dst)
92
+ if resolved_src == 'auto':
93
+ resolved_src = self._get_srcLang(result)
94
+ if resolved_src == dst:
95
+ raise ValueError(
96
+ "Destination language ({dst}) should different from source language ({src}).".format(
97
+ dst=dst, src=resolved_src))
98
+ tran_text.append(self._get_translatedText(result))
99
+ if index < len(original_sentences) - 1:
100
+ time.sleep(sleeping)
101
+ tran_text = ' '.join(tran_text)
102
+ src = resolved_src
103
+ else:
104
+ result = self._sendRequest(text, src, dst)
105
+ if src == 'auto':
106
+ src = self._get_srcLang(result)
107
+ if src == dst:
108
+ raise ValueError(
109
+ "Destination language ({dst}) should different from source language ({src}).".format(
110
+ dst=dst, src=src))
111
+ tran_text = self._get_translatedText(result)
112
+
113
+ result = Translated(src_lang=src, tmp_lang=dst, text=text, trans_text=tran_text, back_text=tran_text,
114
+ dst_lang=dst, mode='direct')
115
+ return result
116
+
75
117
  def _sendRequest(self, text, src, tmp):
76
118
  salt = random.randint(32768, 65536)
77
119
  sign = hashlib.md5((self.appid + text + str(salt) + self.secretKey).encode()).hexdigest()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BackTranslation
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Back translation for Natural Language Processing (NLP) using Google Translate
5
5
  Home-page: https://github.com/hhhwwwuuu/BackTranslation
6
6
  Author: Zhiqiang Wu
@@ -30,11 +30,11 @@ Dynamic: requires-python
30
30
  Dynamic: summary
31
31
 
32
32
  # BackTranslation
33
- [![version](https://img.shields.io/badge/version-0.3.1-blue)](https://pypi.org/project/BackTranslation/#description)
33
+ [![version](https://img.shields.io/badge/version-0.5.0-blue)](https://pypi.org/project/BackTranslation/#description)
34
34
  [![Downloads](https://pepy.tech/badge/backtranslation)](https://pepy.tech/project/backtranslation)
35
35
  [![license](https://img.shields.io/badge/license-MIT-green)](https://github.com/hhhwwwuuu/BackTranslation/blob/main/LICENSE)
36
36
 
37
- BackTranslation is a python library that implemented to back translate the words among any two languages. This utilizes [googletrans](https://py-googletrans.readthedocs.io/en/latest/) library and [Baidu Translation API](http://api.fanyi.baidu.com/) to translate the words.
37
+ BackTranslation is a python library that implements back translation and direct translation among languages. It utilizes the [googletrans](https://py-googletrans.readthedocs.io/en/latest/) library and [Baidu Translation API](http://api.fanyi.baidu.com/) to translate text.
38
38
 
39
39
  Since there is an error in current verison of googletrans, you have to create only one instance to do back-translation for your work. Otherwise, it is easy to cause a bug from multi-requests. We will keep implementing this library with other translator libraries soon.
40
40
 
@@ -49,6 +49,37 @@ $ pip install BackTranslation
49
49
 
50
50
 
51
51
  ## Usage
52
+ ### Direct translation with googletrans
53
+ Translate text from language A directly to language B without translating it back.
54
+
55
+ Parameters:
56
+ * **url**: option. provide a list of services urls for translation if need. Default url is _translate.google.com_.
57
+ * **proxies**: Optional. Proxies configuration. Dictionary mapping protocol or protocol and host to the URL of the proxy.
58
+ i.e. proxies = {'http': '127.0.0.1:1234', 'http://host.name': '127.0.0.1:4012'}
59
+ * **text**: required. Original text that need to translate.
60
+ * **src**: option. Source language code of original text. If this parameter is None, the method will detect the language of text automatically. (Default: None)
61
+ * **dst**: required. Destination language code.
62
+ * **sleeping**: option. Kept for API consistency. Direct translation only sends the A-to-B request. (Default: 0)
63
+
64
+ Return parameter: object _Translated_.
65
+
66
+ Attributes:
67
+ * source_text: original sentence.
68
+ * src: the language of original sentence.
69
+ * dst: the target language.
70
+ * tmp: the same as dst for direct translation.
71
+ * tran_text: translated result in dst language.
72
+ * result_text: translated result in dst language.
73
+ * mode: _direct_.
74
+
75
+ ```python
76
+ from BackTranslation import BackTranslation
77
+ trans = BackTranslation()
78
+ result = trans.direct_translate('hello', src='en', dst='zh-cn')
79
+ print(result.result_text)
80
+ # '你好'
81
+ ```
82
+
52
83
  ### Backtranslation with googletrans
53
84
  Translate the original text to other language and translate back to augment the diversity of data in NLP research.
54
85
 
@@ -67,8 +98,10 @@ Attributes:
67
98
  * source_text: original sentence.
68
99
  * src: the language of original sentence
69
100
  * tmp: the target language as middle man
70
- * trans_text: intermediate result
71
- * back_text: back-tranlsated result
101
+ * dst: the same as tmp for back-translation
102
+ * tran_text: intermediate result
103
+ * result_text: back-translated result
104
+ * mode: _back_translation_
72
105
 
73
106
  ```python
74
107
  from BackTranslation import BackTranslation
@@ -113,7 +146,9 @@ Parameters:
113
146
  from BackTranslation import BackTranslation
114
147
  trans = BackTranslation()
115
148
  trans.searchLanguage('Chinese')
116
- # {'chinese (simplified)': 'zh-cn', 'chinese (traditional)': 'zh-tw'}
149
+ # {'chinese (simplified)': 'zh-cn', 'chinese (traditional)': 'zh-tw', 'chinese (cantonese)': 'yue'}
150
+ trans.searchLanguage('Cantonese')
151
+ # {'chinese (cantonese)': 'yue'}
117
152
  ```
118
153
  ### Backtranslation_Baidu with Baidu Translation API
119
154
  To use this stable translation, you are required to register in [Baidu Translation API]((http://api.fanyi.baidu.com/)) for getting your own appID.
@@ -121,6 +156,16 @@ It supports 2 million chacters per day for free.
121
156
  _Note: Currently, they only support Chinese phone number to register the accout._
122
157
  * **sleeping**: option. Baidu standard API allows only 1 request per second (QPS limit). Set `sleeping=1` (default) to stay within the limit. Increase if you encounter errors. (Default: 1)
123
158
 
159
+ Direct translation:
160
+ ````python
161
+ from BackTranslation import BackTranslation_Baidu
162
+ trans = BackTranslation_Baidu(appid='YOUR APPID', secretKey='YOUR SECRETKEY')
163
+ result = trans.direct_translate('hello', src='en', dst='zh')
164
+ print(result.result_text) # direct translated result
165
+ trans.closeHTTP()
166
+ ````
167
+
168
+ Back-translation:
124
169
  ````python
125
170
  from BackTranslation import BackTranslation_Baidu
126
171
  trans = BackTranslation_Baidu(appid='YOUR APPID', secretKey='YOUR SECRETKEY')
@@ -134,7 +179,11 @@ Since Baidu provides the different language code, it will be updated soon.
134
179
 
135
180
 
136
181
  ## Version Information
137
- **Version 0.3.1: fix some bugs for Baidu translator.**
182
+ **Version 0.5.0: add direct translation APIs for Google and Baidu translators, keep existing back-translation behavior compatible, extend `Translated` with `dst` and `mode`, add Cantonese (`yue`) language support, and update tests and documentation.**
183
+
184
+ Version 0.4.0: fix bugs (#1 #2 #3 #4 #6), add automated tests and CI/CD pipeline.
185
+
186
+ Version 0.3.1: fix some bugs for Baidu translator.
138
187
 
139
188
  Version 0.2.2: fix the services url for Google Translator.
140
189
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BackTranslation
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Back translation for Natural Language Processing (NLP) using Google Translate
5
5
  Home-page: https://github.com/hhhwwwuuu/BackTranslation
6
6
  Author: Zhiqiang Wu
@@ -30,11 +30,11 @@ Dynamic: requires-python
30
30
  Dynamic: summary
31
31
 
32
32
  # BackTranslation
33
- [![version](https://img.shields.io/badge/version-0.3.1-blue)](https://pypi.org/project/BackTranslation/#description)
33
+ [![version](https://img.shields.io/badge/version-0.5.0-blue)](https://pypi.org/project/BackTranslation/#description)
34
34
  [![Downloads](https://pepy.tech/badge/backtranslation)](https://pepy.tech/project/backtranslation)
35
35
  [![license](https://img.shields.io/badge/license-MIT-green)](https://github.com/hhhwwwuuu/BackTranslation/blob/main/LICENSE)
36
36
 
37
- BackTranslation is a python library that implemented to back translate the words among any two languages. This utilizes [googletrans](https://py-googletrans.readthedocs.io/en/latest/) library and [Baidu Translation API](http://api.fanyi.baidu.com/) to translate the words.
37
+ BackTranslation is a python library that implements back translation and direct translation among languages. It utilizes the [googletrans](https://py-googletrans.readthedocs.io/en/latest/) library and [Baidu Translation API](http://api.fanyi.baidu.com/) to translate text.
38
38
 
39
39
  Since there is an error in current verison of googletrans, you have to create only one instance to do back-translation for your work. Otherwise, it is easy to cause a bug from multi-requests. We will keep implementing this library with other translator libraries soon.
40
40
 
@@ -49,6 +49,37 @@ $ pip install BackTranslation
49
49
 
50
50
 
51
51
  ## Usage
52
+ ### Direct translation with googletrans
53
+ Translate text from language A directly to language B without translating it back.
54
+
55
+ Parameters:
56
+ * **url**: option. provide a list of services urls for translation if need. Default url is _translate.google.com_.
57
+ * **proxies**: Optional. Proxies configuration. Dictionary mapping protocol or protocol and host to the URL of the proxy.
58
+ i.e. proxies = {'http': '127.0.0.1:1234', 'http://host.name': '127.0.0.1:4012'}
59
+ * **text**: required. Original text that need to translate.
60
+ * **src**: option. Source language code of original text. If this parameter is None, the method will detect the language of text automatically. (Default: None)
61
+ * **dst**: required. Destination language code.
62
+ * **sleeping**: option. Kept for API consistency. Direct translation only sends the A-to-B request. (Default: 0)
63
+
64
+ Return parameter: object _Translated_.
65
+
66
+ Attributes:
67
+ * source_text: original sentence.
68
+ * src: the language of original sentence.
69
+ * dst: the target language.
70
+ * tmp: the same as dst for direct translation.
71
+ * tran_text: translated result in dst language.
72
+ * result_text: translated result in dst language.
73
+ * mode: _direct_.
74
+
75
+ ```python
76
+ from BackTranslation import BackTranslation
77
+ trans = BackTranslation()
78
+ result = trans.direct_translate('hello', src='en', dst='zh-cn')
79
+ print(result.result_text)
80
+ # '你好'
81
+ ```
82
+
52
83
  ### Backtranslation with googletrans
53
84
  Translate the original text to other language and translate back to augment the diversity of data in NLP research.
54
85
 
@@ -67,8 +98,10 @@ Attributes:
67
98
  * source_text: original sentence.
68
99
  * src: the language of original sentence
69
100
  * tmp: the target language as middle man
70
- * trans_text: intermediate result
71
- * back_text: back-tranlsated result
101
+ * dst: the same as tmp for back-translation
102
+ * tran_text: intermediate result
103
+ * result_text: back-translated result
104
+ * mode: _back_translation_
72
105
 
73
106
  ```python
74
107
  from BackTranslation import BackTranslation
@@ -113,7 +146,9 @@ Parameters:
113
146
  from BackTranslation import BackTranslation
114
147
  trans = BackTranslation()
115
148
  trans.searchLanguage('Chinese')
116
- # {'chinese (simplified)': 'zh-cn', 'chinese (traditional)': 'zh-tw'}
149
+ # {'chinese (simplified)': 'zh-cn', 'chinese (traditional)': 'zh-tw', 'chinese (cantonese)': 'yue'}
150
+ trans.searchLanguage('Cantonese')
151
+ # {'chinese (cantonese)': 'yue'}
117
152
  ```
118
153
  ### Backtranslation_Baidu with Baidu Translation API
119
154
  To use this stable translation, you are required to register in [Baidu Translation API]((http://api.fanyi.baidu.com/)) for getting your own appID.
@@ -121,6 +156,16 @@ It supports 2 million chacters per day for free.
121
156
  _Note: Currently, they only support Chinese phone number to register the accout._
122
157
  * **sleeping**: option. Baidu standard API allows only 1 request per second (QPS limit). Set `sleeping=1` (default) to stay within the limit. Increase if you encounter errors. (Default: 1)
123
158
 
159
+ Direct translation:
160
+ ````python
161
+ from BackTranslation import BackTranslation_Baidu
162
+ trans = BackTranslation_Baidu(appid='YOUR APPID', secretKey='YOUR SECRETKEY')
163
+ result = trans.direct_translate('hello', src='en', dst='zh')
164
+ print(result.result_text) # direct translated result
165
+ trans.closeHTTP()
166
+ ````
167
+
168
+ Back-translation:
124
169
  ````python
125
170
  from BackTranslation import BackTranslation_Baidu
126
171
  trans = BackTranslation_Baidu(appid='YOUR APPID', secretKey='YOUR SECRETKEY')
@@ -134,7 +179,11 @@ Since Baidu provides the different language code, it will be updated soon.
134
179
 
135
180
 
136
181
  ## Version Information
137
- **Version 0.3.1: fix some bugs for Baidu translator.**
182
+ **Version 0.5.0: add direct translation APIs for Google and Baidu translators, keep existing back-translation behavior compatible, extend `Translated` with `dst` and `mode`, add Cantonese (`yue`) language support, and update tests and documentation.**
183
+
184
+ Version 0.4.0: fix bugs (#1 #2 #3 #4 #6), add automated tests and CI/CD pipeline.
185
+
186
+ Version 0.3.1: fix some bugs for Baidu translator.
138
187
 
139
188
  Version 0.2.2: fix the services url for Google Translator.
140
189
 
@@ -1,9 +1,9 @@
1
1
  # BackTranslation
2
- [![version](https://img.shields.io/badge/version-0.3.1-blue)](https://pypi.org/project/BackTranslation/#description)
2
+ [![version](https://img.shields.io/badge/version-0.5.0-blue)](https://pypi.org/project/BackTranslation/#description)
3
3
  [![Downloads](https://pepy.tech/badge/backtranslation)](https://pepy.tech/project/backtranslation)
4
4
  [![license](https://img.shields.io/badge/license-MIT-green)](https://github.com/hhhwwwuuu/BackTranslation/blob/main/LICENSE)
5
5
 
6
- BackTranslation is a python library that implemented to back translate the words among any two languages. This utilizes [googletrans](https://py-googletrans.readthedocs.io/en/latest/) library and [Baidu Translation API](http://api.fanyi.baidu.com/) to translate the words.
6
+ BackTranslation is a python library that implements back translation and direct translation among languages. It utilizes the [googletrans](https://py-googletrans.readthedocs.io/en/latest/) library and [Baidu Translation API](http://api.fanyi.baidu.com/) to translate text.
7
7
 
8
8
  Since there is an error in current verison of googletrans, you have to create only one instance to do back-translation for your work. Otherwise, it is easy to cause a bug from multi-requests. We will keep implementing this library with other translator libraries soon.
9
9
 
@@ -18,6 +18,37 @@ $ pip install BackTranslation
18
18
 
19
19
 
20
20
  ## Usage
21
+ ### Direct translation with googletrans
22
+ Translate text from language A directly to language B without translating it back.
23
+
24
+ Parameters:
25
+ * **url**: option. provide a list of services urls for translation if need. Default url is _translate.google.com_.
26
+ * **proxies**: Optional. Proxies configuration. Dictionary mapping protocol or protocol and host to the URL of the proxy.
27
+ i.e. proxies = {'http': '127.0.0.1:1234', 'http://host.name': '127.0.0.1:4012'}
28
+ * **text**: required. Original text that need to translate.
29
+ * **src**: option. Source language code of original text. If this parameter is None, the method will detect the language of text automatically. (Default: None)
30
+ * **dst**: required. Destination language code.
31
+ * **sleeping**: option. Kept for API consistency. Direct translation only sends the A-to-B request. (Default: 0)
32
+
33
+ Return parameter: object _Translated_.
34
+
35
+ Attributes:
36
+ * source_text: original sentence.
37
+ * src: the language of original sentence.
38
+ * dst: the target language.
39
+ * tmp: the same as dst for direct translation.
40
+ * tran_text: translated result in dst language.
41
+ * result_text: translated result in dst language.
42
+ * mode: _direct_.
43
+
44
+ ```python
45
+ from BackTranslation import BackTranslation
46
+ trans = BackTranslation()
47
+ result = trans.direct_translate('hello', src='en', dst='zh-cn')
48
+ print(result.result_text)
49
+ # '你好'
50
+ ```
51
+
21
52
  ### Backtranslation with googletrans
22
53
  Translate the original text to other language and translate back to augment the diversity of data in NLP research.
23
54
 
@@ -36,8 +67,10 @@ Attributes:
36
67
  * source_text: original sentence.
37
68
  * src: the language of original sentence
38
69
  * tmp: the target language as middle man
39
- * trans_text: intermediate result
40
- * back_text: back-tranlsated result
70
+ * dst: the same as tmp for back-translation
71
+ * tran_text: intermediate result
72
+ * result_text: back-translated result
73
+ * mode: _back_translation_
41
74
 
42
75
  ```python
43
76
  from BackTranslation import BackTranslation
@@ -82,7 +115,9 @@ Parameters:
82
115
  from BackTranslation import BackTranslation
83
116
  trans = BackTranslation()
84
117
  trans.searchLanguage('Chinese')
85
- # {'chinese (simplified)': 'zh-cn', 'chinese (traditional)': 'zh-tw'}
118
+ # {'chinese (simplified)': 'zh-cn', 'chinese (traditional)': 'zh-tw', 'chinese (cantonese)': 'yue'}
119
+ trans.searchLanguage('Cantonese')
120
+ # {'chinese (cantonese)': 'yue'}
86
121
  ```
87
122
  ### Backtranslation_Baidu with Baidu Translation API
88
123
  To use this stable translation, you are required to register in [Baidu Translation API]((http://api.fanyi.baidu.com/)) for getting your own appID.
@@ -90,6 +125,16 @@ It supports 2 million chacters per day for free.
90
125
  _Note: Currently, they only support Chinese phone number to register the accout._
91
126
  * **sleeping**: option. Baidu standard API allows only 1 request per second (QPS limit). Set `sleeping=1` (default) to stay within the limit. Increase if you encounter errors. (Default: 1)
92
127
 
128
+ Direct translation:
129
+ ````python
130
+ from BackTranslation import BackTranslation_Baidu
131
+ trans = BackTranslation_Baidu(appid='YOUR APPID', secretKey='YOUR SECRETKEY')
132
+ result = trans.direct_translate('hello', src='en', dst='zh')
133
+ print(result.result_text) # direct translated result
134
+ trans.closeHTTP()
135
+ ````
136
+
137
+ Back-translation:
93
138
  ````python
94
139
  from BackTranslation import BackTranslation_Baidu
95
140
  trans = BackTranslation_Baidu(appid='YOUR APPID', secretKey='YOUR SECRETKEY')
@@ -103,7 +148,11 @@ Since Baidu provides the different language code, it will be updated soon.
103
148
 
104
149
 
105
150
  ## Version Information
106
- **Version 0.3.1: fix some bugs for Baidu translator.**
151
+ **Version 0.5.0: add direct translation APIs for Google and Baidu translators, keep existing back-translation behavior compatible, extend `Translated` with `dst` and `mode`, add Cantonese (`yue`) language support, and update tests and documentation.**
152
+
153
+ Version 0.4.0: fix bugs (#1 #2 #3 #4 #6), add automated tests and CI/CD pipeline.
154
+
155
+ Version 0.3.1: fix some bugs for Baidu translator.
107
156
 
108
157
  Version 0.2.2: fix the services url for Google Translator.
109
158
 
@@ -118,4 +167,4 @@ Welcome to contribute BackTranslation library!
118
167
 
119
168
  ## reference
120
169
  - [googletrans](https://py-googletrans.readthedocs.io/en/latest/)
121
- - [Baidu Translation API](http://api.fanyi.baidu.com/)
170
+ - [Baidu Translation API](http://api.fanyi.baidu.com/)
@@ -11,10 +11,23 @@ def test_attributes():
11
11
  assert r.source_text == 'hello'
12
12
  assert r.src == 'en'
13
13
  assert r.tmp == 'zh-cn'
14
+ assert r.dst == 'zh-cn'
15
+ assert r.mode == 'back_translation'
14
16
  assert r.tran_text == '你好'
15
17
  assert r.result_text == 'hello there'
16
18
 
17
19
 
20
+ def test_direct_attributes():
21
+ r = Translated(src_lang='en', tmp_lang='zh-cn', text='hello', trans_text='你好',
22
+ back_text='你好', dst_lang='zh-cn', mode='direct')
23
+ assert r.src == 'en'
24
+ assert r.tmp == 'zh-cn'
25
+ assert r.dst == 'zh-cn'
26
+ assert r.mode == 'direct'
27
+ assert r.tran_text == '你好'
28
+ assert r.result_text == '你好'
29
+
30
+
18
31
  def test_str_short():
19
32
  r = make_result('hello there')
20
33
  s = str(r)
@@ -46,6 +46,32 @@ def test_basic_translation():
46
46
  assert result.result_text == 'Hello there'
47
47
 
48
48
 
49
+ def test_direct_translation():
50
+ trans, mock_translator = _build_trans()
51
+ mock_translator.translate.return_value = _make_translate_result('你好')
52
+ result = trans.direct_translate('hello', src='en', dst='zh-cn')
53
+ assert result.src == 'en'
54
+ assert result.tmp == 'zh-cn'
55
+ assert result.dst == 'zh-cn'
56
+ assert result.mode == 'direct'
57
+ assert result.source_text == 'hello'
58
+ assert result.tran_text == '你好'
59
+ assert result.result_text == '你好'
60
+ mock_translator.translate.assert_called_once_with('hello', src='en', dest='zh-cn')
61
+
62
+
63
+ def test_direct_translation_to_cantonese():
64
+ trans, mock_translator = _build_trans()
65
+ mock_translator.translate.return_value = _make_translate_result('你好')
66
+ result = trans.direct_translate('hello', src='en', dst='yue')
67
+ assert result.src == 'en'
68
+ assert result.tmp == 'yue'
69
+ assert result.dst == 'yue'
70
+ assert result.mode == 'direct'
71
+ assert result.result_text == '你好'
72
+ mock_translator.translate.assert_called_once_with('hello', src='en', dest='yue')
73
+
74
+
49
75
  def test_auto_detect_src():
50
76
  trans, mock_translator = _build_trans()
51
77
  mock_translator.translate.side_effect = [
@@ -56,6 +82,14 @@ def test_auto_detect_src():
56
82
  assert result.src == 'en'
57
83
 
58
84
 
85
+ def test_direct_auto_detect_src():
86
+ trans, mock_translator = _build_trans()
87
+ mock_translator.translate.return_value = _make_translate_result('你好')
88
+ result = trans.direct_translate('hello', dst='zh-cn')
89
+ assert result.src == 'en'
90
+ mock_translator.detect.assert_called_once_with('hello')
91
+
92
+
59
93
  def test_default_tmp_when_src_en():
60
94
  trans, mock_translator = _build_trans()
61
95
  mock_translator.translate.side_effect = [
@@ -76,24 +110,62 @@ def test_default_tmp_when_src_not_en():
76
110
  assert result.tmp == 'en'
77
111
 
78
112
 
113
+ def test_back_translation_with_cantonese_tmp():
114
+ trans, mock_translator = _build_trans()
115
+ mock_translator.translate.side_effect = [
116
+ _make_translate_result('你好'),
117
+ _make_translate_result('Hello there'),
118
+ ]
119
+ result = trans.translate('hello', src='en', tmp='yue')
120
+ assert result.src == 'en'
121
+ assert result.tmp == 'yue'
122
+ assert result.dst == 'yue'
123
+ assert result.tran_text == '你好'
124
+ assert result.result_text == 'Hello there'
125
+
126
+
79
127
  def test_invalid_src_raises():
80
128
  trans, _ = _build_trans()
81
129
  with pytest.raises(ValueError, match='INVALID source language'):
82
130
  trans.translate('hello', src='xx')
83
131
 
84
132
 
133
+ def test_direct_missing_dst_raises():
134
+ trans, _ = _build_trans()
135
+ with pytest.raises(ValueError, match='destination language is required'):
136
+ trans.direct_translate('hello', src='en')
137
+
138
+
139
+ def test_direct_invalid_src_raises():
140
+ trans, _ = _build_trans()
141
+ with pytest.raises(ValueError, match='INVALID source language'):
142
+ trans.direct_translate('hello', src='xx', dst='zh-cn')
143
+
144
+
85
145
  def test_invalid_tmp_raises():
86
146
  trans, _ = _build_trans()
87
147
  with pytest.raises(ValueError, match='INVALID transited language'):
88
148
  trans.translate('hello', src='en', tmp='xx')
89
149
 
90
150
 
151
+ def test_direct_invalid_dst_raises():
152
+ trans, _ = _build_trans()
153
+ with pytest.raises(ValueError, match='INVALID destination language'):
154
+ trans.direct_translate('hello', src='en', dst='xx')
155
+
156
+
91
157
  def test_same_src_tmp_raises():
92
158
  trans, _ = _build_trans()
93
159
  with pytest.raises(ValueError, match='should different'):
94
160
  trans.translate('hello', src='en', tmp='en')
95
161
 
96
162
 
163
+ def test_direct_same_src_dst_raises():
164
+ trans, _ = _build_trans()
165
+ with pytest.raises(ValueError, match='should different'):
166
+ trans.direct_translate('hello', src='en', dst='en')
167
+
168
+
97
169
  def test_sleeping_is_called(mocker):
98
170
  trans, mock_translator = _build_trans()
99
171
  mock_translator.translate.side_effect = [
@@ -112,6 +184,13 @@ def test_connect_timeout_raises_friendly_message():
112
184
  trans.translate('hello', src='en', tmp='zh-cn')
113
185
 
114
186
 
187
+ def test_direct_connect_timeout_raises_friendly_message():
188
+ trans, mock_translator = _build_trans()
189
+ mock_translator.translate.side_effect = httpcore.ConnectTimeout()
190
+ with pytest.raises(httpcore.ConnectTimeout, match='proxies'):
191
+ trans.direct_translate('hello', src='en', dst='zh-cn')
192
+
193
+
115
194
  def test_general_exception_raises_with_hint():
116
195
  trans, mock_translator = _build_trans()
117
196
  mock_translator.translate.side_effect = Exception('some googletrans error')
@@ -119,10 +198,24 @@ def test_general_exception_raises_with_hint():
119
198
  trans.translate('hello', src='en', tmp='zh-cn')
120
199
 
121
200
 
201
+ def test_direct_general_exception_raises_with_hint():
202
+ trans, mock_translator = _build_trans()
203
+ mock_translator.translate.side_effect = Exception('some googletrans error')
204
+ with pytest.raises(Exception, match="sleeping"):
205
+ trans.direct_translate('hello', src='en', dst='zh-cn')
206
+
207
+
122
208
  def test_search_language():
123
209
  trans, _ = _build_trans()
124
210
  result = trans.searchLanguage('Chinese')
125
211
  assert 'zh-cn' in result.values()
212
+ assert 'yue' in result.values()
213
+
214
+
215
+ def test_search_cantonese_language():
216
+ trans, _ = _build_trans()
217
+ result = trans.searchLanguage('Cantonese')
218
+ assert result == {'chinese (cantonese)': 'yue'}
126
219
 
127
220
 
128
221
  def test_search_language_not_found():
@@ -146,3 +239,17 @@ def test_long_text_split(mocker):
146
239
  result = trans.translate(long_text, src='en', tmp='zh-cn')
147
240
  assert result.src == 'en'
148
241
  assert mock_sleep.called
242
+
243
+
244
+ def test_direct_long_text_split(mocker):
245
+ trans, mock_translator = _build_trans()
246
+ long_text = 'Hello world. ' * 500
247
+ sentences = ['Hello world. ' * 250, 'Hello world. ' * 250]
248
+ mock_translator.translate.return_value = [
249
+ _make_translate_result('你好一'),
250
+ _make_translate_result('你好二'),
251
+ ]
252
+ mocker.patch('BackTranslation.translation.sent_tokenize', return_value=sentences)
253
+ result = trans.direct_translate(long_text, src='en', dst='zh-cn')
254
+ assert result.result_text == '你好一 你好二'
255
+ mock_translator.translate.assert_called_once_with([s.rstrip() for s in sentences], src='en', dest='zh-cn')
@@ -32,6 +32,29 @@ def test_basic_translation(trans):
32
32
  assert result.result_text == 'Hello there'
33
33
 
34
34
 
35
+ def test_direct_translation(trans):
36
+ trans._sendRequest.return_value = _mock_response('en', '你好')
37
+ result = trans.direct_translate('hello', src='en', dst='zh')
38
+ assert result.src == 'en'
39
+ assert result.tmp == 'zh'
40
+ assert result.dst == 'zh'
41
+ assert result.mode == 'direct'
42
+ assert result.tran_text == '你好'
43
+ assert result.result_text == '你好'
44
+ trans._sendRequest.assert_called_once_with('hello', 'en', 'zh')
45
+
46
+
47
+ def test_direct_translation_to_cantonese(trans):
48
+ trans._sendRequest.return_value = _mock_response('en', '你好')
49
+ result = trans.direct_translate('hello', src='en', dst='yue')
50
+ assert result.src == 'en'
51
+ assert result.tmp == 'yue'
52
+ assert result.dst == 'yue'
53
+ assert result.mode == 'direct'
54
+ assert result.result_text == '你好'
55
+ trans._sendRequest.assert_called_once_with('hello', 'en', 'yue')
56
+
57
+
35
58
  def test_back_translation_uses_tran_text_not_original(trans):
36
59
  """Issue #6: back-translation must use tran_text, not the original text."""
37
60
  calls = []
@@ -49,6 +72,23 @@ def test_back_translation_uses_tran_text_not_original(trans):
49
72
  assert calls[1][0] == '你好', "Back-translation must use intermediate text, not original"
50
73
 
51
74
 
75
+ def test_back_translation_with_cantonese_tmp(trans):
76
+ trans._sendRequest.side_effect = [
77
+ _mock_response('en', '你好'),
78
+ _mock_response('yue', 'Hello there'),
79
+ ]
80
+ result = trans.translate('hello', src='en', tmp='yue')
81
+ assert result.src == 'en'
82
+ assert result.tmp == 'yue'
83
+ assert result.dst == 'yue'
84
+ assert result.tran_text == '你好'
85
+ assert result.result_text == 'Hello there'
86
+ assert trans._sendRequest.call_args_list == [
87
+ call('hello', 'en', 'yue'),
88
+ call('你好', 'yue', 'en'),
89
+ ]
90
+
91
+
52
92
  def test_auto_detect_src(trans):
53
93
  trans._sendRequest.side_effect = [
54
94
  _mock_response('en', 'detected'),
@@ -59,6 +99,19 @@ def test_auto_detect_src(trans):
59
99
  assert result.src == 'en'
60
100
 
61
101
 
102
+ def test_direct_auto_detect_src(trans):
103
+ trans._sendRequest.return_value = _mock_response('en', '你好')
104
+ result = trans.direct_translate('hello', src='auto', dst='zh')
105
+ assert result.src == 'en'
106
+ trans._sendRequest.assert_called_once_with('hello', 'auto', 'zh')
107
+
108
+
109
+ def test_direct_auto_detect_same_src_dst_raises(trans):
110
+ trans._sendRequest.return_value = _mock_response('en', 'hello')
111
+ with pytest.raises(ValueError, match='should different'):
112
+ trans.direct_translate('hello', src='auto', dst='en')
113
+
114
+
62
115
  def test_default_tmp_when_src_en(trans):
63
116
  trans._sendRequest.side_effect = [
64
117
  _mock_response('en', '你好'),
@@ -82,6 +135,16 @@ def test_same_src_tmp_raises(trans):
82
135
  trans.translate('hello', src='en', tmp='en')
83
136
 
84
137
 
138
+ def test_direct_missing_dst_raises(trans):
139
+ with pytest.raises(ValueError, match='destination language is required'):
140
+ trans.direct_translate('hello', src='en')
141
+
142
+
143
+ def test_direct_same_src_dst_raises(trans):
144
+ with pytest.raises(ValueError, match='should different'):
145
+ trans.direct_translate('hello', src='en', dst='en')
146
+
147
+
85
148
  def test_invalid_appid():
86
149
  with patch('BackTranslation.translation_Baidu.http.client.HTTPConnection'):
87
150
  with pytest.raises(ValueError, match='INVALID appid'):
@@ -113,3 +176,22 @@ def test_default_sleeping_is_one(trans, mocker):
113
176
  mock_sleep = mocker.patch('BackTranslation.translation_Baidu.time.sleep')
114
177
  trans.translate('hello', src='en', tmp='zh')
115
178
  mock_sleep.assert_called_with(1)
179
+
180
+
181
+ def test_direct_long_text_split(trans, mocker):
182
+ long_text = 'Hello world. ' * 600
183
+ mocker.patch('BackTranslation.translation_Baidu.sent_tokenize', return_value=['ignored'])
184
+ mocker.patch.object(trans, '_split_segement', return_value=['hello one', 'hello two'])
185
+ trans._sendRequest.side_effect = [
186
+ _mock_response('en', '你好一'),
187
+ _mock_response('en', '你好二'),
188
+ ]
189
+ mock_sleep = mocker.patch('BackTranslation.translation_Baidu.time.sleep')
190
+ result = trans.direct_translate(long_text, src='auto', dst='zh')
191
+ assert result.src == 'en'
192
+ assert result.result_text == '你好一 你好二'
193
+ assert trans._sendRequest.call_args_list == [
194
+ call('hello one', 'auto', 'zh'),
195
+ call('hello two', 'en', 'zh'),
196
+ ]
197
+ mock_sleep.assert_called_once_with(1)
File without changes