deepl-rb 2.5.3 → 3.0.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 (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 +39 -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 +121 -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 +155 -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