bing_translator 5.1.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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