deepl-rb 3.8.0 → 3.9.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +1 -1
  3. data/CHANGELOG.md +23 -1
  4. data/README.md +213 -4
  5. data/VERSION +1 -1
  6. data/deepl-rb.gemspec +34 -6
  7. data/lib/deepl/document_api.rb +11 -1
  8. data/lib/deepl/requests/base.rb +55 -3
  9. data/lib/deepl/requests/document/upload.rb +50 -1
  10. data/lib/deepl/requests/translate.rb +4 -1
  11. data/lib/deepl/requests/translation_memory/base.rb +47 -0
  12. data/lib/deepl/requests/translation_memory/create_export.rb +41 -0
  13. data/lib/deepl/requests/translation_memory/create_import.rb +53 -0
  14. data/lib/deepl/requests/translation_memory/destroy.rb +37 -0
  15. data/lib/deepl/requests/translation_memory/download_export.rb +35 -0
  16. data/lib/deepl/requests/translation_memory/find.rb +38 -0
  17. data/lib/deepl/requests/translation_memory/find_job.rb +38 -0
  18. data/lib/deepl/requests/translation_memory/list.rb +5 -18
  19. data/lib/deepl/requests/translation_memory/segments.rb +46 -0
  20. data/lib/deepl/requests/translation_memory/storage_base.rb +47 -0
  21. data/lib/deepl/requests/translation_memory/upload_file.rb +47 -0
  22. data/lib/deepl/resources/translation_memory.rb +4 -1
  23. data/lib/deepl/resources/translation_memory_export.rb +37 -0
  24. data/lib/deepl/resources/translation_memory_import.rb +29 -0
  25. data/lib/deepl/resources/translation_memory_job.rb +142 -0
  26. data/lib/deepl/resources/translation_memory_segments.rb +78 -0
  27. data/lib/deepl/translation_memory_api.rb +293 -1
  28. data/lib/deepl/utils/time_parser.rb +26 -0
  29. data/lib/deepl.rb +31 -0
  30. data/lib/version.rb +1 -1
  31. data/spec/integration_tests/document_api_spec.rb +47 -0
  32. data/spec/integration_tests/integration_test_utils.rb +8 -0
  33. data/spec/integration_tests/translate_api_spec.rb +19 -0
  34. data/spec/integration_tests/translation_memory_api_spec.rb +192 -2
  35. data/spec/integration_tests/translation_memory_error_paths_spec.rb +68 -0
  36. data/spec/requests/document/upload_spec.rb +130 -0
  37. data/spec/requests/translate_spec.rb +55 -0
  38. data/spec/requests/translation_memory/create_export_spec.rb +37 -0
  39. data/spec/requests/translation_memory/create_import_spec.rb +61 -0
  40. data/spec/requests/translation_memory/destroy_spec.rb +36 -0
  41. data/spec/requests/translation_memory/download_export_spec.rb +54 -0
  42. data/spec/requests/translation_memory/find_job_spec.rb +37 -0
  43. data/spec/requests/translation_memory/find_spec.rb +36 -0
  44. data/spec/requests/translation_memory/segments_spec.rb +58 -0
  45. data/spec/requests/translation_memory/upload_file_spec.rb +55 -0
  46. data/spec/resources/translation_memory_export_spec.rb +38 -0
  47. data/spec/resources/translation_memory_import_spec.rb +30 -0
  48. data/spec/resources/translation_memory_job_spec.rb +109 -0
  49. data/spec/resources/translation_memory_segments_spec.rb +78 -0
  50. data/spec/resources/translation_memory_spec.rb +18 -1
  51. data/spec/support/managed_glossary.rb +48 -0
  52. data/spec/support/managed_translation_memory.rb +48 -0
  53. metadata +34 -3
@@ -0,0 +1,78 @@
1
+ # Copyright 2026 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.md file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Resources
8
+ class TranslationMemoryTargetSegment
9
+ attr_reader :target_segment_id, :target_language, :target_text, :creation_time,
10
+ :updated_time, :last_used_time
11
+
12
+ def initialize(target_segment)
13
+ @target_segment_id = target_segment['target_segment_id']
14
+ @target_language = target_segment['target_language']
15
+ @target_text = target_segment['target_text']
16
+ @creation_time = Utils::TimeParser.parse_optional_time(target_segment['creation_time'])
17
+ @updated_time = Utils::TimeParser.parse_optional_time(target_segment['updated_time'])
18
+ @last_used_time = Utils::TimeParser.parse_optional_time(target_segment['last_used_time'])
19
+ end
20
+
21
+ def to_s
22
+ "#{target_language}: #{target_text}"
23
+ end
24
+ end
25
+
26
+ class TranslationMemorySegment
27
+ attr_reader :source_segment_id, :source_text, :targets, :creation_time, :updated_time,
28
+ :last_used_time
29
+
30
+ def initialize(segment)
31
+ @source_segment_id = segment['source_segment_id']
32
+ @source_text = segment['source_text']
33
+ @targets = (segment['targets'] || []).map do |target|
34
+ TranslationMemoryTargetSegment.new(target)
35
+ end
36
+ @creation_time = Utils::TimeParser.parse_optional_time(segment['creation_time'])
37
+ @updated_time = Utils::TimeParser.parse_optional_time(segment['updated_time'])
38
+ @last_used_time = Utils::TimeParser.parse_optional_time(segment['last_used_time'])
39
+ end
40
+
41
+ def to_s
42
+ "#{source_segment_id} - #{source_text}"
43
+ end
44
+ end
45
+
46
+ ##
47
+ # One page of the segments of a translation memory. Pagination is cursor-based: pass
48
+ # `next_page_cursor` as the `page_cursor` option of the next request until it is nil.
49
+
50
+ class TranslationMemorySegments < Base
51
+ attr_reader :segments, :segment_count, :next_page_cursor
52
+
53
+ def initialize(segments_response, *args)
54
+ super(*args)
55
+ @segments = (segments_response['segments'] || []).map do |segment|
56
+ TranslationMemorySegment.new(segment)
57
+ end
58
+ # Note that this is the number of segments stored in the translation memory, it is not
59
+ # reduced by a text filter.
60
+ @segment_count = segments_response['segment_count'] || 0
61
+ @next_page_cursor = segments_response['next_page_cursor']
62
+ end
63
+
64
+ ##
65
+ # Checks whether another page of segments can be requested.
66
+ #
67
+ # @return [true] if so
68
+
69
+ def next_page?
70
+ !next_page_cursor.nil?
71
+ end
72
+
73
+ def to_s
74
+ "#{segments.size} of #{segment_count} segment(s)"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -4,14 +4,306 @@
4
4
  # frozen_string_literal: true
5
5
 
6
6
  module DeepL
7
- class TranslationMemoryApi
7
+ class TranslationMemoryApi # rubocop:disable Metrics/ClassLength
8
+ # Time to wait between two status queries of an import or export job.
9
+ JOB_POLLING_INTERVAL_SECONDS = 5
10
+
8
11
  def initialize(api, options = {})
9
12
  @api = api
10
13
  @options = options
11
14
  end
12
15
 
16
+ ##
17
+ # Lists the translation memories of the account.
18
+ #
19
+ # @param [Hash] options Additional options for the request. Supports `page` (page number for
20
+ # pagination, 0-indexed) and `page_size` (number of items per page).
21
+ # @return [Array<DeepL::Resources::TranslationMemory>] The translation memories of the page.
22
+
13
23
  def list(options = {})
14
24
  DeepL::Requests::TranslationMemory::List.new(@api, options).request
15
25
  end
26
+
27
+ ##
28
+ # Retrieves a single translation memory.
29
+ #
30
+ # @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
31
+ # ID or object.
32
+ # @param [Hash] options Additional options for the request.
33
+ # @return [DeepL::Resources::TranslationMemory] The requested translation memory.
34
+
35
+ def find(translation_memory, options = {})
36
+ DeepL::Requests::TranslationMemory::Find.new(
37
+ @api, extract_translation_memory_id(translation_memory), options
38
+ ).request
39
+ end
40
+
41
+ ##
42
+ # Retrieves one page of the segments of a translation memory. Pagination is cursor-based:
43
+ # omit `page_cursor` on the first call, then pass the `next_page_cursor` of the previous
44
+ # response until it is nil.
45
+ #
46
+ # @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
47
+ # ID or object.
48
+ # @param [Hash] options Additional options for the request. Supports `page_size` (maximum
49
+ # number of segments per page, 1-100, defaults to 50), `page_cursor`
50
+ # (cursor of a previous response), `filter_text` (substring filter
51
+ # across source and target text, at least 2 characters) and
52
+ # `filter_case_sensitive` (whether the filter is case-sensitive,
53
+ # defaults to false).
54
+ # @return [DeepL::Resources::TranslationMemorySegments] The requested page of segments.
55
+
56
+ def segments(translation_memory, options = {})
57
+ DeepL::Requests::TranslationMemory::Segments.new(
58
+ @api, extract_translation_memory_id(translation_memory), options
59
+ ).request
60
+ end
61
+
62
+ ##
63
+ # Deletes a translation memory.
64
+ #
65
+ # @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
66
+ # ID or object.
67
+ # @param [Hash] options Additional options for the request.
68
+ # @return [String] The ID of the deleted translation memory.
69
+
70
+ def destroy(translation_memory, options = {})
71
+ DeepL::Requests::TranslationMemory::Destroy.new(
72
+ @api, extract_translation_memory_id(translation_memory), options
73
+ ).request
74
+ end
75
+
76
+ ##
77
+ # Creates an import job for a new translation memory. The job only declares the file, upload
78
+ # the TMX file itself to the returned upload URL with `upload_file`, then poll `find_job` for
79
+ # the outcome. Use `import_from_filepath` to do all three steps at once.
80
+ #
81
+ # @param [String] file_name Name of the TMX file to import, for example "legal.tmx".
82
+ # @param [Integer] content_length Size of the TMX file in bytes.
83
+ # @param [String, nil] content_type MIME type of the file, defaults to "application/xml".
84
+ # @param [String, nil] display_name Name of the resulting translation memory, defaults to the
85
+ # file name.
86
+ # @param [Hash] additional_headers Additional HTTP headers for the request.
87
+ # @return [DeepL::Resources::TranslationMemoryImport] The job ID and the upload URL.
88
+
89
+ def create_import(file_name, content_length, content_type: nil, display_name: nil,
90
+ additional_headers: {})
91
+ DeepL::Requests::TranslationMemory::CreateImport.new(
92
+ @api, file_name, content_length,
93
+ { content_type: content_type, display_name: display_name }.compact, additional_headers
94
+ ).request
95
+ end
96
+
97
+ ##
98
+ # Uploads a TMX file to the upload URL of an import job, which starts the processing. The
99
+ # upload URL is a pre-signed storage URL outside of the DeepL API, so no authorization header
100
+ # is sent with this request.
101
+ #
102
+ # @param [String, DeepL::Resources::TranslationMemoryImport] translation_memory_import Import
103
+ # returned by `create_import`, or its upload URL.
104
+ # @param [String] file_content Content of the TMX file.
105
+ # @param [String] content_type MIME type of the file. Must match the `content_type` declared
106
+ # when the import job was created.
107
+ # @return [nil]
108
+
109
+ def upload_file(translation_memory_import, file_content,
110
+ content_type: Requests::TranslationMemory::UploadFile::DEFAULT_CONTENT_TYPE)
111
+ DeepL::Requests::TranslationMemory::UploadFile.new(
112
+ @api, extract_upload_url(translation_memory_import), file_content, content_type
113
+ ).request
114
+ end
115
+
116
+ ##
117
+ # Creates an export job for a translation memory. Poll `find_job` for the download URL of the
118
+ # exported TMX file. Use `export_to_filepath` to do both steps and write the file at once.
119
+ #
120
+ # @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
121
+ # ID or object.
122
+ # @param [Hash] options Additional options for the request.
123
+ # @return [DeepL::Resources::TranslationMemoryExport] The job ID, and whether the API reused a
124
+ # previously completed export.
125
+
126
+ def create_export(translation_memory, options = {})
127
+ DeepL::Requests::TranslationMemory::CreateExport.new(
128
+ @api, extract_translation_memory_id(translation_memory), options
129
+ ).request
130
+ end
131
+
132
+ ##
133
+ # Retrieves the status of a translation memory import or export job.
134
+ #
135
+ # @param [String, DeepL::Resources::TranslationMemoryJob] job Job ID or object.
136
+ # @param [Hash] options Additional options for the request.
137
+ # @return [DeepL::Resources::TranslationMemoryJob] The current status of the job.
138
+
139
+ def find_job(job, options = {})
140
+ DeepL::Requests::TranslationMemory::FindJob.new(@api, extract_job_id(job), options).request
141
+ end
142
+
143
+ ##
144
+ # Polls a translation memory import or export job until it is finished, `sleep`ing between
145
+ # the status queries, and returns the final status.
146
+ #
147
+ # Note that an import job keeps reporting `awaiting_input` for a while after its file has
148
+ # been uploaded, because the API detects the upload asynchronously. That status is therefore
149
+ # polled through like any other non-terminal one. A job whose file is never uploaded does not
150
+ # finish on its own, so pass `timeout_s` when that is a possibility.
151
+ #
152
+ # @raise [DeepL::Exceptions::Error] If the job failed or expired, or if `timeout_s` elapsed
153
+ # before the job finished.
154
+ #
155
+ # @param [String, DeepL::Resources::TranslationMemoryJob] job Job ID or object.
156
+ # @param [Hash] options Additional options for the status queries.
157
+ # @param [Numeric, nil] timeout_s Maximum time in seconds to wait for the job to finish. Note
158
+ # that this is not accurate to the second, the status is only
159
+ # queried every five seconds.
160
+ # @return [DeepL::Resources::TranslationMemoryJob] The finished job.
161
+
162
+ def wait_until_job_done(job, options = {}, timeout_s: nil)
163
+ job_status = find_job(job, options)
164
+ started_at = monotonic_time
165
+ until job_status.finished?
166
+ raise_timeout_error(timeout_s) if timeout_exceeded?(started_at, timeout_s)
167
+
168
+ log_job_polling
169
+ sleep(JOB_POLLING_INTERVAL_SECONDS)
170
+ job_status = find_job(job, options)
171
+ end
172
+ raise_job_error(job_status) if job_status.error?
173
+
174
+ job_status
175
+ end
176
+
177
+ ##
178
+ # Downloads the TMX file of a completed export job. The download URL is a pre-signed storage
179
+ # URL outside of the DeepL API, so no authorization header is sent with this request.
180
+ #
181
+ # @raise [DeepL::Exceptions::Error] If the job carries no download URL, for example because
182
+ # it has not completed yet.
183
+ #
184
+ # @param [DeepL::Resources::TranslationMemoryJob, String] job Completed export job carrying
185
+ # the download URL, or the download URL itself.
186
+ # @param [String] output_path Path to the file to write to. Will be overwritten if the file
187
+ # already exists.
188
+
189
+ def download_export(job, output_path)
190
+ DeepL::Requests::TranslationMemory::DownloadExport.new(@api, extract_download_url(job),
191
+ output_path).request
192
+ end
193
+
194
+ ##
195
+ # Imports a TMX file as a new translation memory: creates the import job, uploads the file
196
+ # and waits for the processing to finish.
197
+ #
198
+ # @raise [DeepL::Exceptions::Error] If the import fails.
199
+ #
200
+ # @param [String] input_file_path Path to the TMX file to import.
201
+ # @param [String, nil] display_name Name of the resulting translation memory, defaults to the
202
+ # file name.
203
+ # @param [Numeric, nil] timeout_s Maximum time in seconds to wait for the import to finish.
204
+ # Note that the API keeps reporting `awaiting_input` for a
205
+ # while after the upload, so allow for a generous timeout.
206
+ # @return [DeepL::Resources::TranslationMemoryJob] The finished import job, its result carries
207
+ # the ID of the new translation memory.
208
+
209
+ def import_from_filepath(input_file_path, display_name: nil, timeout_s: nil)
210
+ unless File.exist?(input_file_path)
211
+ raise Exceptions::Error, "No file found at #{input_file_path}"
212
+ end
213
+
214
+ file_content = File.binread(input_file_path)
215
+ created = create_import(File.basename(input_file_path), file_content.bytesize,
216
+ display_name: display_name)
217
+ upload_file(created, file_content)
218
+ wait_until_job_done(created.job_id, timeout_s: timeout_s)
219
+ end
220
+
221
+ ##
222
+ # Exports a translation memory to a TMX file: creates the export job, waits for it to finish
223
+ # and writes the result to +output_path+.
224
+ #
225
+ # @raise [DeepL::Exceptions::Error] If the export fails.
226
+ #
227
+ # @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
228
+ # ID or object.
229
+ # @param [String] output_path Path to the file to write to. Will be overwritten if the file
230
+ # already exists.
231
+ # @param [Numeric, nil] timeout_s Maximum time in seconds to wait for the export to finish.
232
+ # @return [DeepL::Resources::TranslationMemoryJob] The finished export job.
233
+
234
+ def export_to_filepath(translation_memory, output_path, timeout_s: nil)
235
+ created = create_export(translation_memory)
236
+ job = wait_until_job_done(created.job_id, timeout_s: timeout_s)
237
+ download_export(job, output_path)
238
+ job
239
+ end
240
+
241
+ private
242
+
243
+ def extract_translation_memory_id(translation_memory)
244
+ id = if translation_memory.is_a?(Resources::TranslationMemory)
245
+ translation_memory.translation_memory_id
246
+ else
247
+ translation_memory
248
+ end
249
+ raise Exceptions::Error, 'Translation memory ID must not be empty' if blank?(id)
250
+
251
+ id
252
+ end
253
+
254
+ def extract_job_id(job)
255
+ id = job.is_a?(Resources::TranslationMemoryJob) ? job.job_id : job
256
+ raise Exceptions::Error, 'Job ID must not be empty' if blank?(id)
257
+
258
+ id
259
+ end
260
+
261
+ def extract_upload_url(translation_memory_import)
262
+ if translation_memory_import.is_a?(Resources::TranslationMemoryImport)
263
+ translation_memory_import.upload_url
264
+ else
265
+ translation_memory_import
266
+ end
267
+ end
268
+
269
+ def extract_download_url(job)
270
+ return job unless job.is_a?(Resources::TranslationMemoryJob)
271
+
272
+ download_url = job.result&.download_url
273
+ if blank?(download_url)
274
+ raise Exceptions::Error, 'Translation memory export job has no download URL, ' \
275
+ 'it may not have completed yet'
276
+ end
277
+
278
+ download_url
279
+ end
280
+
281
+ def blank?(value)
282
+ value.nil? || value.empty?
283
+ end
284
+
285
+ def log_job_polling
286
+ @api.configuration.logger&.info('Rechecking translation memory job status after sleeping ' \
287
+ "for #{JOB_POLLING_INTERVAL_SECONDS} seconds.")
288
+ end
289
+
290
+ def monotonic_time
291
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
292
+ end
293
+
294
+ def timeout_exceeded?(started_at, timeout_s)
295
+ !timeout_s.nil? && (monotonic_time - started_at) > timeout_s
296
+ end
297
+
298
+ def raise_timeout_error(timeout_s)
299
+ raise Exceptions::Error,
300
+ "Manual timeout of #{timeout_s}s exceeded for the translation memory job"
301
+ end
302
+
303
+ def raise_job_error(job_status)
304
+ raise Exceptions::Error,
305
+ "Error occurred during the translation memory #{job_status.operation}: " \
306
+ "#{job_status.error_message || job_status.status}"
307
+ end
16
308
  end
17
309
  end
@@ -0,0 +1,26 @@
1
+ # Copyright 2026 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.md file.
4
+ # frozen_string_literal: true
5
+
6
+ require 'time'
7
+
8
+ module DeepL
9
+ module Utils
10
+ module TimeParser
11
+ extend self
12
+
13
+ ##
14
+ # Parses an optional timestamp returned by the API.
15
+ #
16
+ # @param [String, nil] time_string Timestamp in ISO 8601 format, or nil.
17
+ # @return [Time, nil] The parsed time, or nil if no timestamp was given.
18
+
19
+ def parse_optional_time(time_string)
20
+ return nil if time_string.nil? || time_string.empty?
21
+
22
+ Time.parse(time_string)
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/deepl.rb CHANGED
@@ -41,7 +41,17 @@ require_relative 'deepl/requests/style_rule/create_custom_instruction'
41
41
  require_relative 'deepl/requests/style_rule/find_custom_instruction'
42
42
  require_relative 'deepl/requests/style_rule/update_custom_instruction'
43
43
  require_relative 'deepl/requests/style_rule/destroy_custom_instruction'
44
+ require_relative 'deepl/requests/translation_memory/base'
45
+ require_relative 'deepl/requests/translation_memory/storage_base'
44
46
  require_relative 'deepl/requests/translation_memory/list'
47
+ require_relative 'deepl/requests/translation_memory/find'
48
+ require_relative 'deepl/requests/translation_memory/segments'
49
+ require_relative 'deepl/requests/translation_memory/destroy'
50
+ require_relative 'deepl/requests/translation_memory/create_import'
51
+ require_relative 'deepl/requests/translation_memory/upload_file'
52
+ require_relative 'deepl/requests/translation_memory/create_export'
53
+ require_relative 'deepl/requests/translation_memory/find_job'
54
+ require_relative 'deepl/requests/translation_memory/download_export'
45
55
  require_relative 'deepl/requests/languages'
46
56
  require_relative 'deepl/requests/translate'
47
57
  require_relative 'deepl/requests/usage'
@@ -54,6 +64,10 @@ require_relative 'deepl/resources/document_translation_status'
54
64
  require_relative 'deepl/resources/glossary'
55
65
  require_relative 'deepl/resources/style_rule'
56
66
  require_relative 'deepl/resources/translation_memory'
67
+ require_relative 'deepl/resources/translation_memory_segments'
68
+ require_relative 'deepl/resources/translation_memory_import'
69
+ require_relative 'deepl/resources/translation_memory_export'
70
+ require_relative 'deepl/resources/translation_memory_job'
57
71
  require_relative 'deepl/resources/language'
58
72
  require_relative 'deepl/resources/language_pair'
59
73
  require_relative 'deepl/resources/text'
@@ -62,6 +76,7 @@ require_relative 'deepl/resources/usage'
62
76
  # -- Utils
63
77
  require_relative 'deepl/utils/exception_builder'
64
78
  require_relative 'deepl/utils/backoff_timer'
79
+ require_relative 'deepl/utils/time_parser'
65
80
 
66
81
  # -- Constants
67
82
  require_relative 'deepl/constants/base_constant'
@@ -100,6 +115,22 @@ module DeepL
100
115
  Requests::Languages.new(api, options).request
101
116
  end
102
117
 
118
+ ##
119
+ # Translates +text+ from +source_lang+ into +target_lang+.
120
+ #
121
+ # @param [String, Array<String>] text Text(s) to translate.
122
+ # @param [String, nil] source_lang Source language. `nil` enables automatic detection.
123
+ # @param [String] target_lang Target language.
124
+ # @param [Hash] options Additional (body) options for the translation. Notable options:
125
+ # * +:glossary_id+ - A single glossary ID (string) to use for the translation. Requires
126
+ # +source_lang+ to be set. Cannot be combined with +:glossary_ids+.
127
+ # * +:glossary_ids+ - An array of up to 5 glossary IDs (strings or
128
+ # `DeepL::Resources::Glossary` objects) to use for the translation. Glossaries are applied
129
+ # in order (first match wins). Requires +source_lang+ to be set. Cannot be combined with
130
+ # +:glossary_id+. Raises `ArgumentError` if these rules are violated or more than 5 IDs are
131
+ # provided.
132
+ # @param [Hash] additional_headers Additional HTTP headers for the translation.
133
+ # @return [DeepL::Resources::Text, Array<DeepL::Resources::Text>] Translated text resource(s).
103
134
  def translate(text, source_lang, target_lang, options = {}, additional_headers = {})
104
135
  configure if @configuration.nil?
105
136
  Requests::Translate.new(api, text, source_lang, target_lang, options,
data/lib/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # frozen_string_literal: true
5
5
 
6
6
  module DeepL
7
- VERSION = '3.8.0'
7
+ VERSION = '3.9.0'
8
8
  end
@@ -140,6 +140,53 @@ describe DeepL::DocumentApi do
140
140
  expect(doc_status.status).to eq('done')
141
141
  end
142
142
 
143
+ it 'Translates a document with a style_id set', :mock_server_only do # rubocop:disable RSpec/ExampleLength
144
+ File.unlink(output_document_path)
145
+ # Mock's default style rule (dca2e053...) has language `en`, so the target
146
+ # language must be English.
147
+ source_lang = 'DE'
148
+ target_lang = 'EN-US'
149
+ example_doc_path = example_document_path(source_lang)
150
+ style_id = 'dca2e053-8ae5-45e6-a0d2-881156e7f4e4'
151
+ doc_status = nil
152
+ DeepL.with_session(DeepL::HTTPClientOptions.new({}, nil,
153
+ enable_ssl_verification: false)) do |_session|
154
+ handle = DeepL.document.upload(example_doc_path, source_lang, target_lang,
155
+ File.basename(example_doc_path),
156
+ { style_rule: style_id })
157
+ doc_status = handle.wait_until_document_translation_finished
158
+ DeepL.document.download(handle, output_document_path) if doc_status.status != 'error'
159
+ end
160
+ output_file_contents = File.read(output_document_path)
161
+
162
+ expect(example_document_translation(target_lang)).to eq(output_file_contents)
163
+ expect(doc_status.status).to eq('done')
164
+ end
165
+
166
+ it 'Translates a document with a translation_memory_id set', :mock_server_only do # rubocop:disable RSpec/ExampleLength
167
+ File.unlink(output_document_path)
168
+ # Mock's default translation memory (a74d88fb...) has source `de` and
169
+ # targets `en`/`es`/`fr`, so translate DE -> EN-US.
170
+ source_lang = 'DE'
171
+ target_lang = 'EN-US'
172
+ example_doc_path = example_document_path(source_lang)
173
+ translation_memory_id = 'a74d88fb-ed2a-4943-a664-a4512398b994'
174
+ doc_status = nil
175
+ DeepL.with_session(DeepL::HTTPClientOptions.new({}, nil,
176
+ enable_ssl_verification: false)) do |_session|
177
+ handle = DeepL.document.upload(example_doc_path, source_lang, target_lang,
178
+ File.basename(example_doc_path),
179
+ { translation_memory: translation_memory_id,
180
+ translation_memory_threshold: 80 })
181
+ doc_status = handle.wait_until_document_translation_finished
182
+ DeepL.document.download(handle, output_document_path) if doc_status.status != 'error'
183
+ end
184
+ output_file_contents = File.read(output_document_path)
185
+
186
+ expect(example_document_translation(target_lang)).to eq(output_file_contents)
187
+ expect(doc_status.status).to eq('done')
188
+ end
189
+
143
190
  it 'Translates a document with extra_body_parameters' do # rubocop:disable RSpec/ExampleLength
144
191
  File.unlink(output_document_path)
145
192
  source_lang = default_lang_args[:source_lang]
@@ -148,6 +148,14 @@ module IntegrationTestUtils # rubocop:disable Metrics/ModuleLength
148
148
  'mock-server-session' => SecureRandom.uuid }
149
149
  end
150
150
 
151
+ # Makes a translation memory import or export job report its non-terminal status, such as
152
+ # `awaiting_input` for an uploaded import, for the given number of status queries before it
153
+ # completes. Without it a job completes on the first query.
154
+ def tm_job_processing_polls_header(poll_count)
155
+ { 'mock-server-session-tm-job-processing-polls' => poll_count.to_s,
156
+ 'mock-server-session' => SecureRandom.uuid }
157
+ end
158
+
151
159
  def expect_proxy_header(response_count)
152
160
  { 'mock-server-session-expect-proxy' => response_count.to_s,
153
161
  'mock-server-session' => SecureRandom.uuid }
@@ -76,6 +76,25 @@ describe 'DeepL.translate' do # rubocop:disable RSpec/DescribeClass
76
76
  end
77
77
  end
78
78
 
79
+ it 'translates using multiple glossaries via glossary_ids' do # rubocop:disable RSpec/ExampleLength
80
+ with_managed_glossary(name: 'Translate Glossary IDs 1', source_lang: 'en', target_lang: 'de',
81
+ entries: [%w[Hello Hallo]]) do |glossary1|
82
+ with_managed_glossary(name: 'Translate Glossary IDs 2', source_lang: 'en',
83
+ target_lang: 'de', entries: [%w[World Welt]]) do |glossary2|
84
+ # Newly created glossaries can lag behind (eventual consistency), so
85
+ # ensure they are resolvable before translating and tolerate a
86
+ # transient "not found" on the translate call itself.
87
+ wait_until_glossaries_ready(glossary1, glossary2)
88
+ result = translate_retrying_missing_glossary(
89
+ 'Hello', 'EN', 'DE', { glossary_ids: [glossary1.id, glossary2.id] }
90
+ )
91
+
92
+ expect(result).to be_a(DeepL::Resources::Text)
93
+ expect(result.text).not_to be_empty
94
+ end
95
+ end
96
+ end
97
+
79
98
  %w[quality_optimized latency_optimized prefer_quality_optimized].each do |model_type|
80
99
  it "returns model_type_used for #{model_type}" do
81
100
  result = DeepL.translate(text, 'EN', 'DE', { model_type: model_type })