deepl-rb 2.5.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/add_issues_to_kanban.yml +16 -0
  3. data/.gitlab-ci.yml +135 -0
  4. data/.rubocop.yml +27 -0
  5. data/CHANGELOG.md +34 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/CONTRIBUTING.md +37 -0
  8. data/Gemfile +3 -1
  9. data/LICENSE.md +1 -0
  10. data/README.md +115 -5
  11. data/Rakefile +7 -5
  12. data/SECURITY.md +58 -0
  13. data/VERSION +1 -1
  14. data/deepl-rb.gemspec +36 -20
  15. data/lib/deepl/api.rb +11 -1
  16. data/lib/deepl/configuration.rb +34 -3
  17. data/lib/deepl/document_api.rb +120 -0
  18. data/lib/deepl/exceptions/authorization_failed.rb +3 -0
  19. data/lib/deepl/exceptions/bad_request.rb +3 -0
  20. data/lib/deepl/exceptions/document_translation_error.rb +15 -0
  21. data/lib/deepl/exceptions/error.rb +6 -0
  22. data/lib/deepl/exceptions/limit_exceeded.rb +7 -0
  23. data/lib/deepl/exceptions/not_found.rb +3 -0
  24. data/lib/deepl/exceptions/not_supported.rb +3 -0
  25. data/lib/deepl/exceptions/quota_exceeded.rb +3 -0
  26. data/lib/deepl/exceptions/request_entity_too_large.rb +3 -0
  27. data/lib/deepl/exceptions/request_error.rb +4 -2
  28. data/lib/deepl/exceptions/server_error.rb +18 -0
  29. data/lib/deepl/glossary_api.rb +3 -0
  30. data/lib/deepl/requests/base.rb +89 -34
  31. data/lib/deepl/requests/document/download.rb +44 -0
  32. data/lib/deepl/requests/document/get_status.rb +44 -0
  33. data/lib/deepl/requests/document/upload.rb +64 -0
  34. data/lib/deepl/requests/glossary/create.rb +15 -1
  35. data/lib/deepl/requests/glossary/destroy.rb +8 -1
  36. data/lib/deepl/requests/glossary/entries.rb +8 -1
  37. data/lib/deepl/requests/glossary/find.rb +8 -1
  38. data/lib/deepl/requests/glossary/language_pairs.rb +9 -2
  39. data/lib/deepl/requests/glossary/list.rb +9 -2
  40. data/lib/deepl/requests/languages.rb +9 -2
  41. data/lib/deepl/requests/translate.rb +33 -11
  42. data/lib/deepl/requests/usage.rb +9 -2
  43. data/lib/deepl/resources/base.rb +3 -0
  44. data/lib/deepl/resources/document_handle.rb +57 -0
  45. data/lib/deepl/resources/document_translation_status.rb +54 -0
  46. data/lib/deepl/resources/glossary.rb +3 -0
  47. data/lib/deepl/resources/language.rb +3 -0
  48. data/lib/deepl/resources/language_pair.rb +3 -0
  49. data/lib/deepl/resources/text.rb +3 -0
  50. data/lib/deepl/resources/usage.rb +3 -0
  51. data/lib/deepl/utils/backoff_timer.rb +46 -0
  52. data/lib/deepl/utils/exception_builder.rb +18 -13
  53. data/lib/deepl.rb +47 -0
  54. data/lib/http_client_options.rb +22 -0
  55. data/license_checker.sh +8 -0
  56. data/spec/api/api_spec.rb +8 -4
  57. data/spec/api/configuration_spec.rb +92 -18
  58. data/spec/api/deepl_spec.rb +225 -86
  59. data/spec/fixtures/vcr_cassettes/deepl_document.yml +95 -0
  60. data/spec/fixtures/vcr_cassettes/deepl_document_download.yml +1214 -0
  61. data/spec/fixtures/vcr_cassettes/deepl_glossaries.yml +812 -23
  62. data/spec/fixtures/vcr_cassettes/deepl_languages.yml +28 -17
  63. data/spec/fixtures/vcr_cassettes/deepl_translate.yml +161 -53
  64. data/spec/fixtures/vcr_cassettes/deepl_usage.yml +93 -3
  65. data/spec/fixtures/vcr_cassettes/glossaries.yml +1237 -15
  66. data/spec/fixtures/vcr_cassettes/languages.yml +159 -44
  67. data/spec/fixtures/vcr_cassettes/translate_texts.yml +9742 -12
  68. data/spec/fixtures/vcr_cassettes/usage.yml +134 -2
  69. data/spec/integration_tests/document_api_spec.rb +143 -0
  70. data/spec/integration_tests/integration_test_utils.rb +170 -0
  71. data/spec/requests/glossary/create_spec.rb +23 -13
  72. data/spec/requests/glossary/destroy_spec.rb +33 -17
  73. data/spec/requests/glossary/entries_spec.rb +31 -17
  74. data/spec/requests/glossary/find_spec.rb +31 -17
  75. data/spec/requests/glossary/language_pairs_spec.rb +17 -7
  76. data/spec/requests/glossary/list_spec.rb +21 -11
  77. data/spec/requests/languages_spec.rb +31 -21
  78. data/spec/requests/translate_spec.rb +125 -131
  79. data/spec/requests/usage_spec.rb +17 -7
  80. data/spec/resources/glossary_spec.rb +15 -12
  81. data/spec/resources/language_pair_spec.rb +10 -7
  82. data/spec/resources/language_spec.rb +21 -18
  83. data/spec/resources/text_spec.rb +10 -7
  84. data/spec/resources/usage_spec.rb +16 -13
  85. data/spec/spec_helper.rb +63 -6
  86. metadata +32 -9
@@ -1,19 +1,29 @@
1
+ # Copyright 2018 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
4
7
  module Requests
5
- class Base
8
+ class Base # rubocop:disable Metrics/ClassLength
6
9
  attr_reader :api, :response, :options
7
10
 
8
- def initialize(api, options = {})
11
+ def initialize(api, options = {}, additional_headers = {})
9
12
  @api = api
10
13
  @options = options
14
+ @additional_headers = additional_headers
15
+ @num_retries = 0
16
+ @backoff_timer = Utils::BackoffTimer.new
11
17
  end
12
18
 
13
19
  def request
14
20
  raise NotImplementedError
15
21
  end
16
22
 
23
+ def details
24
+ "HTTP Headers #{headers}"
25
+ end
26
+
17
27
  private
18
28
 
19
29
  def option?(name)
@@ -36,43 +46,81 @@ module DeepL
36
46
  end
37
47
  end
38
48
 
39
- def post(payload)
40
- request = Net::HTTP::Post.new(uri.request_uri, headers)
41
- request.set_form_data(payload.compact)
42
- response = http.request(request)
43
-
44
- validate_response!(request, response)
45
- [request, response]
49
+ # Files to reset: list of file objects to rewind when retrying the request
50
+ def execute_request_with_retries(req, files_to_reset = []) # rubocop:disable all
51
+ api.configuration.logger&.info("Request to the DeepL API: #{self}")
52
+ api.configuration.logger&.debug("Request details: #{details}")
53
+ loop do
54
+ resp = api.http_client.request(req)
55
+ validate_response!(resp)
56
+ return [req, resp]
57
+ rescue DeepL::Exceptions::Error => e
58
+ raise e unless should_retry?(resp, e, @backoff_timer.num_retries)
59
+
60
+ unless e.nil?
61
+ api.configuration.logger&.info("Encountered a retryable exception: #{e.message}")
62
+ end
63
+ api.configuration.logger&.info("Starting retry #{@backoff_timer.num_retries + 1} for " \
64
+ "request #{request} after sleeping for " \
65
+ "#{format('%.2f', @backoff_timer.time_until_deadline)}")
66
+ files_to_reset.each(&:rewind)
67
+ @backoff_timer.sleep_until_deadline
68
+ next
69
+ rescue Net::HTTPBadResponse, Net::HTTPServerError, Net::HTTPFatalError, Timeout::Error,
70
+ SocketError => e
71
+ unless e.nil?
72
+ api.configuration.logger&.info("Encountered a retryable exception: #{e.message}")
73
+ end
74
+ api.configuration.logger&.info("Starting retry #{@backoff_timer.num_retries + 1} for " \
75
+ "request #{request} after sleeping for " \
76
+ "#{format('%.2f', @backoff_timer.time_until_deadline)}")
77
+ files_to_reset.each(&:rewind)
78
+ @backoff_timer.sleep_until_deadline
79
+ next
80
+ end
46
81
  end
47
82
 
48
- def get
49
- request = Net::HTTP::Get.new(uri.request_uri, headers)
50
- response = http.request(request)
83
+ def should_retry?(response, exception, num_retries)
84
+ return false if num_retries >= api.configuration.max_network_retries
85
+ return exception.should_retry? if response.nil?
51
86
 
52
- validate_response!(request, response)
53
- [request, response]
87
+ response.is_a?(Net::HTTPTooManyRequests) || response.is_a?(Net::HTTPInternalServerError)
54
88
  end
55
89
 
56
- def delete
57
- request = Net::HTTP::Delete.new(uri.request_uri, headers)
58
- response = http.request(request)
90
+ def post_request(payload)
91
+ http_headers = add_json_content_type(headers)
92
+ post_req = Net::HTTP::Post.new(uri.path, http_headers)
93
+ post_req.body = payload.merge(options).to_json
94
+ post_req
95
+ end
59
96
 
60
- validate_response!(request, response)
61
- [request, response]
97
+ def post_request_with_file(form_data)
98
+ http_headers = add_multipart_form_content_type(headers)
99
+ post_req = Net::HTTP::Post.new(uri.request_uri, http_headers)
100
+ # options are passed in `form_data`
101
+ form_data += options.map { |key, value| [key.to_s, value.to_s] }
102
+ post_req.set_form(form_data, 'multipart/form-data')
103
+ post_req
62
104
  end
63
105
 
64
- def http
65
- @http ||= begin
66
- http = Net::HTTP.new(uri.host, uri.port)
67
- http.use_ssl = uri.scheme == 'https'
68
- http
69
- end
106
+ def get_request # rubocop:disable Naming/AccessorMethodName
107
+ http_headers = add_json_content_type(headers)
108
+ get_req = Net::HTTP::Get.new(uri.path, http_headers)
109
+ get_req.body = options.to_json
110
+ get_req
70
111
  end
71
112
 
72
- def validate_response!(request, response)
113
+ def delete_request
114
+ http_headers = add_json_content_type(headers)
115
+ del_req = Net::HTTP::Delete.new(uri.path, http_headers)
116
+ del_req.body = options.to_json
117
+ del_req
118
+ end
119
+
120
+ def validate_response!(response)
73
121
  return if response.is_a?(Net::HTTPSuccess)
74
122
 
75
- raise Utils::ExceptionBuilder.new(request, response).build
123
+ raise Utils::ExceptionBuilder.new(response).build
76
124
  end
77
125
 
78
126
  def path
@@ -84,12 +132,8 @@ module DeepL
84
132
  end
85
133
 
86
134
  def uri
87
- @uri ||= begin
88
- uri = URI(url)
89
- new_query = URI.decode_www_form(uri.query || '') + query_params.to_a
90
- uri.query = URI.encode_www_form(new_query)
91
- uri
92
- end
135
+ @uri ||= URI(url)
136
+ @uri
93
137
  end
94
138
 
95
139
  def host
@@ -101,7 +145,18 @@ module DeepL
101
145
  end
102
146
 
103
147
  def headers
104
- { 'Authorization' => "DeepL-Auth-Key #{api.configuration.auth_key}" }
148
+ { 'Authorization' => "DeepL-Auth-Key #{api.configuration.auth_key}",
149
+ 'User-Agent' => api.configuration.user_agent }.merge(@additional_headers)
150
+ end
151
+
152
+ def add_json_content_type(headers_to_add_to)
153
+ headers_to_add_to['Content-Type'] = 'application/json'
154
+ headers_to_add_to
155
+ end
156
+
157
+ def add_multipart_form_content_type(headers_to_add_to)
158
+ headers_to_add_to['Content-Type'] = 'multipart/form-data'
159
+ headers_to_add_to
105
160
  end
106
161
  end
107
162
  end
@@ -0,0 +1,44 @@
1
+ # Copyright 2024 DeepL SE (https://www.deepl.com)
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ module Document
9
+ class Download < Base
10
+ attr_reader :document_id, :document_key
11
+
12
+ def initialize(api, document_id, document_key, output_file)
13
+ super(api, {})
14
+ @document_id = document_id
15
+ @document_key = document_key
16
+ @output_file = output_file
17
+ end
18
+
19
+ def request
20
+ payload = { document_key: document_key }
21
+ extract_file(*execute_request_with_retries(post_request(payload)))
22
+ end
23
+
24
+ def details
25
+ "HTTP Headers: #{headers}\nPayload #{{ document_key: document_key }}"
26
+ end
27
+
28
+ def to_s
29
+ "POST #{uri.request_uri}"
30
+ end
31
+
32
+ private
33
+
34
+ def extract_file(_request, response)
35
+ File.write(@output_file, response.body)
36
+ end
37
+
38
+ def path
39
+ "document/#{document_id}/result"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright 2024 DeepL SE (https://www.deepl.com)
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ module Document
9
+ class GetStatus < Base
10
+ attr_reader :document_id, :document_key
11
+
12
+ def initialize(api, document_id, document_key, options = {}, additional_headers = {})
13
+ super(api, options, additional_headers)
14
+ @document_id = document_id
15
+ @document_key = document_key
16
+ end
17
+
18
+ def request
19
+ payload = { document_key: document_key }
20
+ build_doc_translation_status(*execute_request_with_retries(post_request(payload)))
21
+ end
22
+
23
+ def details
24
+ "HTTP Headers: #{headers}\nPayload #{{ document_key: document_key }}"
25
+ end
26
+
27
+ def to_s
28
+ "POST #{uri.request_uri}"
29
+ end
30
+
31
+ private
32
+
33
+ def build_doc_translation_status(request, response)
34
+ document_translation_status = JSON.parse(response.body)
35
+ Resources::DocumentTranslationStatus.new(document_translation_status, request, response)
36
+ end
37
+
38
+ def path
39
+ "document/#{document_id}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,64 @@
1
+ # Copyright 2024 DeepL SE (https://www.deepl.com)
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ module Document
9
+ class Upload < Base
10
+ attr_reader :input_file_path, :source_lang, :target_lang, :filename
11
+
12
+ SUPPORTED_OPTIONS = %w[formality glossary_id output_format].freeze
13
+
14
+ def initialize(api, input_file_path, source_lang, target_lang, filename = nil, # rubocop:disable Metrics/ParameterLists
15
+ options = {}, additional_headers = {})
16
+ super(api, options, additional_headers)
17
+ @input_file_path = input_file_path
18
+ @source_lang = source_lang
19
+ @target_lang = target_lang
20
+ @filename = filename
21
+ end
22
+
23
+ def request # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
24
+ input_file = File.open(input_file_path, 'rb')
25
+ form_data = [
26
+ ['file', input_file], ['source_lang', source_lang],
27
+ ['target_lang', target_lang]
28
+ ]
29
+ filename_param = filename || File.basename(input_file_path)
30
+ form_data.push(['filename', filename_param]) unless filename_param.nil?
31
+ # Manually add options due to multipart/form-data request
32
+ SUPPORTED_OPTIONS.each do |option|
33
+ form_data.push([option, options[option]]) unless options[option].nil?
34
+ end
35
+ build_doc_handle(*execute_request_with_retries(post_request_with_file(form_data),
36
+ [input_file]))
37
+ end
38
+
39
+ def details
40
+ "HTTP Headers: #{headers}\nPayload #{[
41
+ ['file', "File at #{input_file_path} opened in binary mode"],
42
+ ['source_lang', source_lang], ['target_lang', target_lang], ['filename', filename]
43
+ ]}"
44
+ end
45
+
46
+ def to_s
47
+ "POST #{uri.request_uri}"
48
+ end
49
+
50
+ private
51
+
52
+ def build_doc_handle(request, response)
53
+ parsed_response = JSON.parse(response.body)
54
+ Resources::DocumentHandle.new(parsed_response['document_id'],
55
+ parsed_response['document_key'], request, response)
56
+ end
57
+
58
+ def path
59
+ 'document'
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,6 @@
1
+ # Copyright 2022 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
@@ -20,7 +23,18 @@ module DeepL
20
23
  name: name, source_lang: source_lang, target_lang: target_lang, entries: entries_to_tsv,
21
24
  entries_format: entries_format
22
25
  }
23
- build_glossary(*post(payload))
26
+ build_glossary(*execute_request_with_retries(post_request(payload)))
27
+ end
28
+
29
+ def details
30
+ "HTTP Headers: #{headers}\nPayload #{{
31
+ name: name, source_lang: source_lang, target_lang: target_lang, entries: entries_to_tsv,
32
+ entries_format: entries_format
33
+ }}"
34
+ end
35
+
36
+ def to_s
37
+ "POST #{uri.request_uri}"
24
38
  end
25
39
 
26
40
  private
@@ -1,3 +1,6 @@
1
+ # Copyright 2022 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
@@ -12,7 +15,11 @@ module DeepL
12
15
  end
13
16
 
14
17
  def request
15
- build_response(*delete)
18
+ build_response(*execute_request_with_retries(delete_request))
19
+ end
20
+
21
+ def to_s
22
+ "DELETE #{uri.request_uri}"
16
23
  end
17
24
 
18
25
  private
@@ -1,3 +1,6 @@
1
+ # Copyright 2022 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
@@ -12,7 +15,11 @@ module DeepL
12
15
  end
13
16
 
14
17
  def request
15
- build_entries(*get)
18
+ build_entries(*execute_request_with_retries(get_request))
19
+ end
20
+
21
+ def to_s
22
+ "GET #{uri.request_uri}"
16
23
  end
17
24
 
18
25
  private
@@ -1,3 +1,6 @@
1
+ # Copyright 2022 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
@@ -12,7 +15,11 @@ module DeepL
12
15
  end
13
16
 
14
17
  def request
15
- build_glossary(*get)
18
+ build_glossary(*execute_request_with_retries(get_request))
19
+ end
20
+
21
+ def to_s
22
+ "GET #{uri.request_uri}"
16
23
  end
17
24
 
18
25
  private
@@ -1,3 +1,6 @@
1
+ # Copyright 2022 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
@@ -5,11 +8,15 @@ module DeepL
5
8
  module Glossary
6
9
  class LanguagePairs < Base
7
10
  def initialize(api, options = {})
8
- super(api, options)
11
+ super
9
12
  end
10
13
 
11
14
  def request
12
- build_language_pair_list(*get)
15
+ build_language_pair_list(*execute_request_with_retries(get_request))
16
+ end
17
+
18
+ def to_s
19
+ "GET #{uri.request_uri}"
13
20
  end
14
21
 
15
22
  private
@@ -1,3 +1,6 @@
1
+ # Copyright 2022 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
@@ -5,11 +8,15 @@ module DeepL
5
8
  module Glossary
6
9
  class List < Base
7
10
  def initialize(api, options = {})
8
- super(api, options)
11
+ super
9
12
  end
10
13
 
11
14
  def request
12
- build_glossary_list(*get)
15
+ build_glossary_list(*execute_request_with_retries(get_request))
16
+ end
17
+
18
+ def to_s
19
+ "GET #{uri.request_uri}"
13
20
  end
14
21
 
15
22
  private
@@ -1,14 +1,21 @@
1
+ # Copyright 2021 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
4
7
  module Requests
5
8
  class Languages < Base
6
9
  def initialize(api, options = {})
7
- super(api, options)
10
+ super
8
11
  end
9
12
 
10
13
  def request
11
- build_languages(*get)
14
+ build_languages(*execute_request_with_retries(get_request))
15
+ end
16
+
17
+ def to_s
18
+ "GET #{uri.request_uri}"
12
19
  end
13
20
 
14
21
  private
@@ -1,17 +1,29 @@
1
+ # Copyright 2018 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
4
7
  module Requests
5
8
  class Translate < Base
6
- BOOLEAN_CONVERSION = { true => '1', false => '0' }.freeze
7
- ARRAY_CONVERSION = ->(value) { value.is_a?(Array) ? value.join(',') : value }.freeze
9
+ STRING_TO_BOOLEAN_MAP = { '1' => true, '0' => false }.freeze
10
+ BOOLEAN_TO_STRING_MAP = { true => '1', false => '0' }.freeze
11
+ STRING_TO_BOOLEAN_CONVERSION = ->(value) { STRING_TO_BOOLEAN_MAP[value] }
12
+ BOOLEAN_TO_STRING_CONVERSION = ->(value) { BOOLEAN_TO_STRING_MAP[value] }
13
+ STRING_TO_ARRAY_CONVERSION = lambda { |value|
14
+ if value.nil?
15
+ nil
16
+ else
17
+ (value.is_a?(Array) ? value : value.split(','))
18
+ end
19
+ }.freeze
8
20
  OPTIONS_CONVERSIONS = {
9
- split_sentences: BOOLEAN_CONVERSION,
10
- preserve_formatting: BOOLEAN_CONVERSION,
11
- outline_detection: BOOLEAN_CONVERSION,
12
- splitting_tags: ARRAY_CONVERSION,
13
- non_splitting_tags: ARRAY_CONVERSION,
14
- ignore_tags: ARRAY_CONVERSION
21
+ split_sentences: BOOLEAN_TO_STRING_CONVERSION,
22
+ preserve_formatting: STRING_TO_BOOLEAN_CONVERSION,
23
+ outline_detection: STRING_TO_BOOLEAN_CONVERSION,
24
+ splitting_tags: STRING_TO_ARRAY_CONVERSION,
25
+ non_splitting_tags: STRING_TO_ARRAY_CONVERSION,
26
+ ignore_tags: STRING_TO_ARRAY_CONVERSION
15
27
  }.freeze
16
28
 
17
29
  attr_reader :text, :source_lang, :target_lang, :ignore_tags, :splitting_tags,
@@ -27,15 +39,25 @@ module DeepL
27
39
  end
28
40
 
29
41
  def request
30
- payload = { text: text, source_lang: source_lang, target_lang: target_lang }
31
- build_texts(*post(payload))
42
+ text_arrayified = text.is_a?(Array) ? text : [text]
43
+ payload = { text: text_arrayified, source_lang: source_lang, target_lang: target_lang }
44
+ build_texts(*execute_request_with_retries(post_request(payload)))
45
+ end
46
+
47
+ def details
48
+ "HTTP Headers: #{headers}\nPayload #{{ text: text, source_lang: source_lang,
49
+ target_lang: target_lang }}"
50
+ end
51
+
52
+ def to_s
53
+ "POST #{uri.request_uri}"
32
54
  end
33
55
 
34
56
  private
35
57
 
36
58
  def tweak_parameters!
37
59
  OPTIONS_CONVERSIONS.each do |param, converter|
38
- next unless option?(param) && converter[option(param)]
60
+ next unless option?(param) && !converter[option(param)].nil?
39
61
 
40
62
  set_option(param, converter[option(param)])
41
63
  end
@@ -1,14 +1,21 @@
1
+ # Copyright 2018 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
4
7
  module Requests
5
8
  class Usage < Base
6
9
  def initialize(api, options = {})
7
- super(api, options)
10
+ super
8
11
  end
9
12
 
10
13
  def request
11
- build_usage(*get)
14
+ build_usage(*execute_request_with_retries(get_request))
15
+ end
16
+
17
+ def to_s
18
+ "GET #{uri.request_uri}"
12
19
  end
13
20
 
14
21
  private
@@ -1,3 +1,6 @@
1
+ # Copyright 2018 Daniel Herzog
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE.md file.
1
4
  # frozen_string_literal: true
2
5
 
3
6
  module DeepL
@@ -0,0 +1,57 @@
1
+ # Copyright 2024 DeepL SE (https://www.deepl.com)
2
+ # Use of this source code is governed by an MIT
3
+ # license that can be found in the LICENSE file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Resources
8
+ class DocumentHandle < Base
9
+ attr_reader :document_id, :document_key
10
+
11
+ def initialize(document_id, document_key, *args)
12
+ super(*args)
13
+
14
+ @document_id = document_id
15
+ @document_key = document_key
16
+ end
17
+
18
+ def to_s
19
+ "DocumentHandle: ID: #{document_id} - Key: #{document_key}"
20
+ end
21
+
22
+ ##
23
+ # For this DocumentHandle, waits until the document translation is finished and returns the
24
+ # final status of the document.
25
+ #
26
+ # @return [DeepL::Resources::DocumentTranslationStatus] Final status of the document
27
+ # translation.
28
+
29
+ def wait_until_document_translation_finished
30
+ doc_status = nil
31
+ max_tries = max_doc_status_queries
32
+ num_tries = 0
33
+ loop do
34
+ num_tries += 1
35
+ sleep(calculate_waiting_time(doc_status)) unless doc_status.nil?
36
+ doc_status = DeepL.document.get_status(self)
37
+ break if doc_status.finished? || num_tries > max_tries
38
+ end
39
+ doc_status
40
+ end
41
+
42
+ private
43
+
44
+ def calculate_waiting_time(_resp)
45
+ # ignore _resp.seconds_remaining for now, while it is unreliable
46
+ 5
47
+ end
48
+
49
+ def max_doc_status_queries
50
+ configured_value = DeepL.configuration.max_doc_status_queries
51
+ return configured_value unless configured_value.nil?
52
+
53
+ 10
54
+ end
55
+ end
56
+ end
57
+ end