bing_translator 5.1.0 → 6.1.0

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 (3) hide show
  1. checksums.yaml +5 -5
  2. data/lib/bing_translator.rb +120 -141
  3. metadata +7 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: daad79b2f54a858f37545758c63e0854ddcbc786
4
- data.tar.gz: fa1e9b14c2fe4eed5e54bdc3c2a22e8455f68bff
2
+ SHA256:
3
+ metadata.gz: 5f88718168ee707a700ed926c13ff7539155d5e94535145c912672520a166f72
4
+ data.tar.gz: 0d5afc1c573bb1c613a7ca77fc7cc8ae2373477677845b3b21f4e425b6ea2fa1
5
5
  SHA512:
6
- metadata.gz: ea5a72e3e4726b67f015e1b90c7a2235cb5209fc1a4be0bdecceed69168b7d76ed26376b7756f34a306302846fa0fc129bdf1e5da0e73fa8433c149b873a51a5
7
- data.tar.gz: 12d3963621499c04bdee4fde27a287fbac46a9628a1811a4f0e241ab65404a95433bb17d0e711a5d2a0f06966e56bfd514642857a5a8f92a845c975f3f4df08d
6
+ metadata.gz: 61a293de90f373bbe12a79989c32a416949b557761dcfb3a773b7fb5c2f9a3bb1657bc084661dee065aa603c0497ada6bf3e6fbc66ea17c99a4f2bfb200a7b6f
7
+ data.tar.gz: 0d9afa36690c92b84987392f5b5ca1d970e5b057144f355fe8733e304a4acc444522159fd0fc8bbc5510c6a2417872972b84aa77124dbfd2d45899e9e2a5a6d1
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # encoding: utf-8
2
+
3
3
  # (c) 2011-present. Ricky Elrod <ricky@elrod.me>
4
4
  # Released under the MIT license.
5
5
  require 'rubygems'
@@ -7,186 +7,165 @@ require 'cgi'
7
7
  require 'uri'
8
8
  require 'net/http'
9
9
  require 'net/https'
10
- require 'nokogiri'
11
10
  require 'json'
12
- require 'savon'
13
11
 
14
12
  class BingTranslator
15
- WSDL_URI = 'http://api.microsofttranslator.com/V2/soap.svc?wsdl'
16
- NAMESPACE_URI = 'http://api.microsofttranslator.com/V2'
17
- COGNITIVE_ACCESS_TOKEN_URI = URI.parse('https://api.cognitive.microsoft.com/sts/v1.0/issueToken').freeze
18
-
19
13
  class Exception < StandardError; end
20
14
 
21
- def initialize(subscription_key, options = {})
22
- @skip_ssl_verify = options.fetch(:skip_ssl_verify, false)
23
- @subscription_key = subscription_key
24
- end
15
+ class ApiClient
16
+ API_HOST = 'https://api.cognitive.microsofttranslator.com'.freeze
17
+ COGNITIVE_ACCESS_TOKEN_URI =
18
+ URI.parse('https://api.cognitive.microsoft.com/sts/v1.0/issueToken').freeze
25
19
 
26
- def translate(text, params = {})
27
- raise "Must provide :to." if params[:to].nil?
20
+ def initialize(subscription_key, skip_ssl_verify, read_timeout = 60, open_timeout = 60)
21
+ @subscription_key = subscription_key
22
+ @skip_ssl_verify = skip_ssl_verify
23
+ @read_timeout = read_timeout
24
+ @open_timeout = open_timeout
25
+ end
28
26
 
29
- # Important notice: param order makes sense in SOAP. Do not reorder or delete!
30
- params = {
31
- 'text' => text.to_s,
32
- 'from' => params[:from].to_s,
33
- 'to' => params[:to].to_s,
34
- 'category' => 'general',
35
- 'contentType' => params[:content_type] || 'text/plain'
36
- }
27
+ def get(path, params: {}, headers: {}, authorization: false)
28
+ uri = request_uri(path, params)
29
+ request = Net::HTTP::Get.new(uri.request_uri, default_headers(authorization).merge(headers))
37
30
 
38
- result(:translate, params)
39
- end
40
-
41
- def translate_array(texts, params = {})
42
- raise "Must provide :to." if params[:to].nil?
43
-
44
- # Important notice: param order makes sense in SOAP. Do not reorder or delete!
45
- params = {
46
- 'texts' => { 'arr:string' => texts },
47
- 'from' => params[:from].to_s,
48
- 'to' => params[:to].to_s,
49
- 'category' => 'general',
50
- 'contentType' => params[:content_type] || 'text/plain'
51
- }
52
-
53
- array_wrap(result(:translate_array, params)[:translate_array_response]).map{|r| r[:translated_text]}
54
- end
31
+ json_response(uri, request)
32
+ end
55
33
 
56
- def translate_array2(texts, params = {})
57
- raise "Must provide :to." if params[:to].nil?
34
+ def post(path, params: {}, headers: {}, data: {}, authorization: true)
35
+ uri = request_uri(path, params)
58
36
 
59
- # Important notice: param order makes sense in SOAP. Do not reorder or delete!
60
- params = {
61
- 'texts' => { 'arr:string' => texts },
62
- 'from' => params[:from].to_s,
63
- 'to' => params[:to].to_s,
64
- 'category' => 'general',
65
- 'contentType' => params[:content_type] || 'text/plain'
66
- }
37
+ request = Net::HTTP::Post.new(uri.request_uri, default_headers(authorization).merge(headers))
38
+ request.body = data
67
39
 
68
- array_wrap(result(:translate_array2, params)[:translate_array2_response]).map{|r| [r[:translated_text], r[:alignment]]}
69
- end
40
+ json_response(uri, request)
41
+ end
70
42
 
71
- def detect(text)
72
- params = {
73
- 'text' => text.to_s,
74
- 'language' => '',
75
- }
43
+ private
76
44
 
77
- if lang = result(:detect, params)
78
- lang.to_sym
45
+ def default_headers(authorization = true)
46
+ headers = { 'Content-Type' => 'application/json' }
47
+ headers['Authorization'] = "Bearer #{access_token}" if authorization
48
+ headers
79
49
  end
80
- end
81
50
 
82
- # format: 'audio/wav' [default] or 'audio/mp3'
83
- # language: valid translator language code
84
- # options: 'MinSize' [default] or 'MaxQuality'
85
- def speak(text, params = {})
86
- raise "Must provide :language" if params[:language].nil?
51
+ def json_response(uri, request)
52
+ http = http_client(uri)
87
53
 
88
- params = {
89
- 'text' => text.to_s,
90
- 'language' => params[:language].to_s,
91
- 'format' => params[:format] || 'audio/wav',
92
- 'options' => params[:options] || 'MinSize',
93
- }
54
+ response = http.request(request)
94
55
 
95
- uri = URI.parse(result(:speak, params))
56
+ raise Exception.new("Unsuccessful API call: Code: #{response.code}") unless response.code == '200'
57
+ JSON.parse(response.body)
58
+ end
96
59
 
97
- http = Net::HTTP.new(uri.host, uri.port)
98
- if uri.scheme == "https"
60
+ def http_client(uri)
61
+ http = Net::HTTP.new(uri.host, 443)
99
62
  http.use_ssl = true
100
63
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @skip_ssl_verify
64
+ http.read_timeout = @read_timeout
65
+ http.open_timeout = @open_timeout
66
+ http
101
67
  end
102
- results = http.get(uri.to_s, {'Authorization' => "Bearer #{get_access_token['access_token']}"})
103
68
 
104
- if results.response.code.to_i == 200
105
- results.body
106
- else
107
- html = Nokogiri::HTML(results.body)
108
- raise Exception, html.xpath("//text()").remove.map(&:to_s).join(' ')
69
+ def request_uri(path, params)
70
+ encoded_params = URI.encode_www_form(params.merge('api-version' => '3.0'))
71
+ uri = URI.parse("#{API_HOST}#{path}")
72
+ uri.query = encoded_params
73
+ uri
74
+ end
75
+
76
+ def access_token
77
+ if @access_token.nil? || Time.now >= @access_token_expiration_time
78
+ @access_token = request_new_access_token
79
+ end
80
+ @access_token
81
+ end
82
+
83
+ def request_new_access_token
84
+ headers = {
85
+ 'Ocp-Apim-Subscription-Key' => @subscription_key
86
+ }
87
+
88
+ http = Net::HTTP.new(COGNITIVE_ACCESS_TOKEN_URI.host, COGNITIVE_ACCESS_TOKEN_URI.port)
89
+ http.use_ssl = true
90
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @skip_ssl_verify
91
+
92
+ response = http.post(COGNITIVE_ACCESS_TOKEN_URI.path, '', headers)
93
+ if response.code != '200'
94
+ raise Exception.new('Invalid credentials')
95
+ else
96
+ @access_token_expiration_time = Time.now + 480
97
+ response.body
98
+ end
109
99
  end
110
100
  end
111
101
 
112
- def supported_language_codes
113
- result(:get_languages_for_translate)[:string]
102
+ def initialize(subscription_key, options = {})
103
+ skip_ssl_verify = options.fetch(:skip_ssl_verify, false)
104
+ read_timeout = options.fetch(:read_timeout, 60)
105
+ open_timeout = options.fetch(:open_timeout, 60)
106
+ @api_client = ApiClient.new(subscription_key, skip_ssl_verify, read_timeout, open_timeout)
114
107
  end
115
108
 
116
- def language_names(codes, locale = 'en')
117
- response = result(:get_language_names, locale: locale, languageCodes: {'a:string' => codes}) do
118
- attributes 'xmlns:a' => 'http://schemas.microsoft.com/2003/10/Serialization/Arrays'
109
+ def translate(text, params)
110
+ translate_array([text], params).first
111
+ end
112
+
113
+ def translate_array(texts, params = {})
114
+ translations = translation_request(texts, params)
115
+ translations.map do |translation|
116
+ translation['text'] if translation.is_a?(Hash)
119
117
  end
118
+ end
120
119
 
121
- response[:string]
120
+ def translate_array2(texts, params = {})
121
+ translations = translation_request(texts, params.merge('includeAlignment' => true))
122
+ translations.map do |translation|
123
+ [translation['text'], translation['alignment']['proj']] if translation.is_a?(Hash)
124
+ end
122
125
  end
123
126
 
124
- private
127
+ def detect(text)
128
+ data = [{ 'Text' => text }].to_json
125
129
 
126
- # Get a new access token and set it internally as @access_token
127
- #
128
- # @return {hash}
129
- # Returns existing @access_token if we don't need a new token yet,
130
- # or returns the one just obtained.
131
- def get_access_token
132
- headers = {
133
- 'Ocp-Apim-Subscription-Key' => @subscription_key
134
- }
135
-
136
- http = Net::HTTP.new(COGNITIVE_ACCESS_TOKEN_URI.host, COGNITIVE_ACCESS_TOKEN_URI.port)
137
- http.use_ssl = true
138
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @skip_ssl_verify
139
-
140
- response = http.post(COGNITIVE_ACCESS_TOKEN_URI.path, "", headers)
141
-
142
- @access_token = {
143
- 'access_token' => response.body,
144
- 'expires_at' => Time.now + 480
145
- }
130
+ response_json = api_client.post('/detect', data: data)
131
+ best_detection = response_json.sort_by { |detection| -detection['score'] }.first
132
+ best_detection['language'].to_sym
146
133
  end
147
134
 
148
- # Performs actual request to Bing Translator SOAP API
149
- def result(action, params = {}, &block)
150
- soap_client.call(action, message: build_soap_message(params), &block).body[:"#{action}_response"][:"#{action}_result"]
151
- rescue Savon::SOAPFault => e
152
- raise Exception, e.message
135
+ def speak(text, params = {})
136
+ raise Exception.new('Not supported since 3.0.0')
153
137
  end
154
138
 
155
- # Specify SOAP namespace in tag names (see https://github.com/savonrb/savon/issues/340 )
156
- #
157
- # @return [Hash]
158
- def build_soap_message(params)
159
- Hash[params.map{|k,v| ["v2:#{k}", v]}]
139
+ def supported_language_codes
140
+ response_json = api_client.get('/languages',
141
+ params: { scope: 'translation' },
142
+ authorization: false)
143
+ response_json['translation'].keys
160
144
  end
161
145
 
162
- # Private: Constructs SOAP client
163
- #
164
- # Construct and store new client when called first time.
165
- # Return stored client while access token is fresh.
166
- # Construct and store new client when token have been expired.
167
- def soap_client
168
- return @client if @client and @access_token and @access_token['expires_at'] and
169
- Time.now < @access_token['expires_at']
170
-
171
- @client = Savon.client(
172
- wsdl: WSDL_URI,
173
- namespace: NAMESPACE_URI,
174
- namespace_identifier: :v2,
175
- namespaces: {
176
- 'xmlns:arr' => 'http://schemas.microsoft.com/2003/10/Serialization/Arrays'
177
- },
178
- headers: {'Authorization' => "Bearer #{get_access_token['access_token']}"},
179
- )
146
+ def language_names(codes, locale = 'en')
147
+ response_json = api_client.get('/languages',
148
+ params: { scope: 'translation' },
149
+ headers: { 'Accept-Language' => locale },
150
+ authorization: false)
151
+ codes.map do |code|
152
+ response = response_json['translation'][code.to_s]
153
+ response['name'] unless response.nil?
154
+ end
180
155
  end
181
156
 
182
- # Private: Array#wrap based on ActiveSupport extension
183
- def array_wrap(object)
184
- if object.nil?
185
- []
186
- elsif object.respond_to?(:to_ary)
187
- object.to_ary || [object]
188
- else
189
- [object]
157
+ private
158
+
159
+ attr_reader :api_client
160
+
161
+ def translation_request(texts, params)
162
+ raise Exception.new('Must provide :to.') if params[:to].nil?
163
+
164
+ data = texts.map { |text| { 'Text' => text } }.to_json
165
+ response_json = api_client.post('/translate', params: params, data: data)
166
+ response_json.map do |translation|
167
+ # There should be just one translation, but who knows...
168
+ translation['translations'].find { |result| result['to'] == params[:to].to_s }
190
169
  end
191
170
  end
192
171
  end
metadata CHANGED
@@ -1,57 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bing_translator
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ricky Elrod
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-18 00:00:00.000000000 Z
11
+ date: 2019-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: nokogiri
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 1.8.1
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 1.8.1
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: json
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 1.8.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 1.8.0
41
- - !ruby/object:Gem::Dependency
42
- name: savon
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
17
+ - - ">="
46
18
  - !ruby/object:Gem::Version
47
- version: 2.10.0
19
+ version: '0'
48
20
  type: :runtime
49
21
  prerelease: false
50
22
  version_requirements: !ruby/object:Gem::Requirement
51
23
  requirements:
52
- - - "~>"
24
+ - - ">="
53
25
  - !ruby/object:Gem::Version
54
- version: 2.10.0
26
+ version: '0'
55
27
  description: Translate strings using the Bing HTTP API. Requires that you have a Client
56
28
  ID and Secret. See README.md for information.
57
29
  email: ricky@elrod.me
@@ -80,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
52
  version: '0'
81
53
  requirements: []
82
54
  rubyforge_project:
83
- rubygems_version: 2.6.13
55
+ rubygems_version: 2.7.6.2
84
56
  signing_key:
85
57
  specification_version: 4
86
58
  summary: Translate using the Bing HTTP API