bing_translator 5.2.0 → 6.0.0.beta.1

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 +4 -4
  2. data/lib/bing_translator.rb +165 -192
  3. metadata +7 -102
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30d94b0e0875f32aeadcdaba47c6c9744dab2628
4
- data.tar.gz: 60ca01d9b5ceb5eed85b52ed8d2bd0512bdfb149
3
+ metadata.gz: 9106de975569bef3303a4940318a78b536beaf10
4
+ data.tar.gz: e40c556e6f474d185b74f6753635a448720600f6
5
5
  SHA512:
6
- metadata.gz: a6b42ea3aae1e84110313d12c37499c2639399ad759633afeae288cac2823c3d2b8cb41c22212e245e628a2340b2441d1aba19a92f20cb925f0677be3fbcd7ee
7
- data.tar.gz: 2a3735bf3b80e69c22c77d47f4e760f7613f46a2be04b50e25f97651f496608c144b1e34c805e71b6757a6b89387b4d759235aa2f59c694eacfe968de3c5d968
6
+ metadata.gz: 8b84d0f6c1b4a405ee631221a961f8e5462c7de1b0ca3eb26b0f0b2dc105e3b0e8ac19c085c8de320191a66895af313fc1b1f718f81b13505a6b31ab8a20108c
7
+ data.tar.gz: c22b944db83f5b437b9fa56e8df83da1bd69f7b81f9ef65dedb51a66d82c79714fba29701da46a6bd1b027e56ff69e54f3e5c21bddfccacddfadd3daa252e3a4
@@ -1,192 +1,165 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
- # (c) 2011-present. Ricky Elrod <ricky@elrod.me>
4
- # Released under the MIT license.
5
- require 'rubygems'
6
- require 'cgi'
7
- require 'uri'
8
- require 'net/http'
9
- require 'net/https'
10
- require 'nokogiri'
11
- require 'json'
12
- require 'savon'
13
-
14
- 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
- class Exception < StandardError; end
20
-
21
- def initialize(subscription_key, options = {})
22
- @skip_ssl_verify = options.fetch(:skip_ssl_verify, false)
23
- @subscription_key = subscription_key
24
- end
25
-
26
- def translate(text, params = {})
27
- raise "Must provide :to." if params[:to].nil?
28
-
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
- }
37
-
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
55
-
56
- def translate_array2(texts, params = {})
57
- raise "Must provide :to." if params[:to].nil?
58
-
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
- }
67
-
68
- array_wrap(result(:translate_array2, params)[:translate_array2_response]).map{|r| [r[:translated_text], r[:alignment]]}
69
- end
70
-
71
- def detect(text)
72
- params = {
73
- 'text' => text.to_s,
74
- 'language' => '',
75
- }
76
-
77
- if lang = result(:detect, params)
78
- lang.to_sym
79
- end
80
- end
81
-
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?
87
-
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
- }
94
-
95
- uri = URI.parse(result(:speak, params))
96
-
97
- http = Net::HTTP.new(uri.host, uri.port)
98
- if uri.scheme == "https"
99
- http.use_ssl = true
100
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @skip_ssl_verify
101
- end
102
- results = http.get(uri.to_s, {'Authorization' => "Bearer #{get_access_token['access_token']}"})
103
-
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(' ')
109
- end
110
- end
111
-
112
- def supported_language_codes
113
- result(:get_languages_for_translate)[:string]
114
- end
115
-
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'
119
- end
120
-
121
- response[:string]
122
- end
123
-
124
- private
125
-
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
- }
146
- end
147
-
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
153
- end
154
-
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]}]
160
- end
161
-
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
- )
180
- end
181
-
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]
190
- end
191
- end
192
- end
1
+ #!/usr/bin/env ruby
2
+
3
+ # (c) 2011-present. Ricky Elrod <ricky@elrod.me>
4
+ # Released under the MIT license.
5
+ require 'rubygems'
6
+ require 'cgi'
7
+ require 'uri'
8
+ require 'net/http'
9
+ require 'net/https'
10
+ require 'json'
11
+
12
+ class BingTranslator
13
+ class Exception < StandardError; end
14
+
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
19
+
20
+ def initialize(subscription_key, skip_ssl_verify)
21
+ @subscription_key = subscription_key
22
+ @skip_ssl_verify = skip_ssl_verify
23
+ end
24
+
25
+ def get(path, params: {}, headers: {}, authorization: false)
26
+ uri = request_uri(path, params)
27
+ request = Net::HTTP::Get.new(uri.request_uri, default_headers(authorization).merge(headers))
28
+
29
+ json_response(uri, request)
30
+ end
31
+
32
+ def post(path, params: {}, headers: {}, data: {}, authorization: true)
33
+ uri = request_uri(path, params)
34
+
35
+ request = Net::HTTP::Post.new(uri.request_uri, default_headers(authorization).merge(headers))
36
+ request.body = data
37
+
38
+ json_response(uri, request)
39
+ end
40
+
41
+ private
42
+
43
+ def default_headers(authorization = true)
44
+ headers = { 'Content-Type' => 'application/json' }
45
+ headers['Authorization'] = "Bearer #{access_token}" if authorization
46
+ headers
47
+ end
48
+
49
+ def json_response(uri, request)
50
+ http = http_client(uri)
51
+
52
+ response = http.request(request)
53
+
54
+ raise Exception.new("Unsuccessful API call: Code: #{response.code}") unless response.code == '200'
55
+ JSON.parse(response.body)
56
+ end
57
+
58
+ def http_client(uri)
59
+ http = Net::HTTP.new(uri.host, 443)
60
+ http.use_ssl = true
61
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @skip_ssl_verify
62
+ http
63
+ end
64
+
65
+ def request_uri(path, params)
66
+ encoded_params = URI.encode_www_form(params.merge('api-version' => '3.0'))
67
+ uri = URI.parse("#{API_HOST}#{path}")
68
+ uri.query = encoded_params
69
+ uri
70
+ end
71
+
72
+ def access_token
73
+ if @access_token.nil? || Time.now < @access_token_expiration_time
74
+ @access_token = request_new_access_token
75
+ end
76
+ @access_token
77
+ end
78
+
79
+ def request_new_access_token
80
+ headers = {
81
+ 'Ocp-Apim-Subscription-Key' => @subscription_key
82
+ }
83
+
84
+ http = Net::HTTP.new(COGNITIVE_ACCESS_TOKEN_URI.host, COGNITIVE_ACCESS_TOKEN_URI.port)
85
+ http.use_ssl = true
86
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @skip_ssl_verify
87
+
88
+ response = http.post(COGNITIVE_ACCESS_TOKEN_URI.path, '', headers)
89
+ if response.code != '200'
90
+ raise Exception.new('Invalid credentials')
91
+ else
92
+ @access_token_expiration_time = Time.now + 480
93
+ response.body
94
+ end
95
+ end
96
+ end
97
+
98
+ def initialize(subscription_key, options = {})
99
+ skip_ssl_verify = options.fetch(:skip_ssl_verify, false)
100
+ @api_client = ApiClient.new(subscription_key, skip_ssl_verify)
101
+ end
102
+
103
+ def translate(text, params)
104
+ translate_array([text], params).first
105
+ end
106
+
107
+ def translate_array(texts, params = {})
108
+ translations = translation_request(texts, params)
109
+ translations.map do |translation|
110
+ translation['text'] if translation.is_a?(Hash)
111
+ end
112
+ end
113
+
114
+ def translate_array2(texts, params = {})
115
+ translations = translation_request(texts, params.merge('includeAlignment' => true))
116
+ translations.map do |translation|
117
+ [translation['text'], translation['alignment']['proj']] if translation.is_a?(Hash)
118
+ end
119
+ end
120
+
121
+ def detect(text)
122
+ data = [{ 'Text' => text }].to_json
123
+
124
+ response_json = api_client.post('/detect', data: data)
125
+ best_detection = response_json.sort_by { |detection| -detection['score'] }.first
126
+ best_detection['language'].to_sym
127
+ end
128
+
129
+ def speak(text, params = {})
130
+ raise Exception.new('Not supported since 3.0.0')
131
+ end
132
+
133
+ def supported_language_codes
134
+ response_json = api_client.get('/languages',
135
+ params: { scope: 'translation' },
136
+ authorization: false)
137
+ response_json['translation'].keys
138
+ end
139
+
140
+ def language_names(codes, locale = 'en')
141
+ response_json = api_client.get('/languages',
142
+ params: { scope: 'translation' },
143
+ headers: { 'Accept-Language' => locale },
144
+ authorization: false)
145
+ codes.map do |code|
146
+ response = response_json['translation'][code.to_s]
147
+ response['name'] unless response.nil?
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ attr_reader :api_client
154
+
155
+ def translation_request(texts, params)
156
+ raise Exception.new('Must provide :to.') if params[:to].nil?
157
+
158
+ data = texts.map { |text| { 'Text' => text } }.to_json
159
+ response_json = api_client.post('/translate', params: params, data: data)
160
+ response_json.map do |translation|
161
+ # There should be just one translation, but who knows...
162
+ translation['translations'].find { |result| result['to'] == params[:to].to_s }
163
+ end
164
+ end
165
+ end
metadata CHANGED
@@ -1,119 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bing_translator
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 6.0.0.beta.1
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-29 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.6.0
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: 1.9.0
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 1.6.0
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: 1.9.0
33
13
  - !ruby/object:Gem::Dependency
34
14
  name: json
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: 1.8.0
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: 1.8.0
47
- - !ruby/object:Gem::Dependency
48
- name: savon
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: 2.10.0
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: 2.10.0
61
- - !ruby/object:Gem::Dependency
62
- name: rake
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '10.0'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '10.0'
75
- - !ruby/object:Gem::Dependency
76
- name: rspec
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '3.0'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '3.0'
89
- - !ruby/object:Gem::Dependency
90
- name: rspec-html-matchers
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: 0.9.1
96
- - - "<"
97
- - !ruby/object:Gem::Version
98
- version: 0.10.0
99
- type: :development
100
- prerelease: false
101
- version_requirements: !ruby/object:Gem::Requirement
102
- requirements:
103
- - - ">="
104
- - !ruby/object:Gem::Version
105
- version: 0.9.1
106
- - - "<"
107
- - !ruby/object:Gem::Version
108
- version: 0.10.0
109
- - !ruby/object:Gem::Dependency
110
- name: dotenv
111
15
  requirement: !ruby/object:Gem::Requirement
112
16
  requirements:
113
17
  - - ">="
114
18
  - !ruby/object:Gem::Version
115
19
  version: '0'
116
- type: :development
20
+ type: :runtime
117
21
  prerelease: false
118
22
  version_requirements: !ruby/object:Gem::Requirement
119
23
  requirements:
@@ -143,13 +47,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
143
47
  version: '0'
144
48
  required_rubygems_version: !ruby/object:Gem::Requirement
145
49
  requirements:
146
- - - ">="
50
+ - - ">"
147
51
  - !ruby/object:Gem::Version
148
- version: '0'
52
+ version: 1.3.1
149
53
  requirements: []
150
54
  rubyforge_project:
151
- rubygems_version: 2.6.13
55
+ rubygems_version: 2.4.5.1
152
56
  signing_key:
153
57
  specification_version: 4
154
58
  summary: Translate using the Bing HTTP API
155
59
  test_files: []
60
+ has_rdoc: