deepl-rb 3.7.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +4 -2
  3. data/CHANGELOG.md +39 -1
  4. data/Gemfile +0 -2
  5. data/README.md +225 -16
  6. data/VERSION +1 -1
  7. data/deepl-rb.gemspec +52 -19
  8. data/lib/deepl/document_api.rb +11 -1
  9. data/lib/deepl/requests/base.rb +55 -3
  10. data/lib/deepl/requests/document/upload.rb +50 -1
  11. data/lib/deepl/requests/rephrase.rb +3 -2
  12. data/lib/deepl/requests/translate.rb +6 -3
  13. data/lib/deepl/requests/translation_memory/base.rb +47 -0
  14. data/lib/deepl/requests/translation_memory/create_export.rb +41 -0
  15. data/lib/deepl/requests/translation_memory/create_import.rb +53 -0
  16. data/lib/deepl/requests/translation_memory/destroy.rb +37 -0
  17. data/lib/deepl/requests/translation_memory/download_export.rb +35 -0
  18. data/lib/deepl/requests/translation_memory/find.rb +38 -0
  19. data/lib/deepl/requests/translation_memory/find_job.rb +38 -0
  20. data/lib/deepl/requests/translation_memory/list.rb +5 -18
  21. data/lib/deepl/requests/translation_memory/segments.rb +46 -0
  22. data/lib/deepl/requests/translation_memory/storage_base.rb +47 -0
  23. data/lib/deepl/requests/translation_memory/upload_file.rb +47 -0
  24. data/lib/deepl/resources/translation_memory.rb +4 -1
  25. data/lib/deepl/resources/translation_memory_export.rb +37 -0
  26. data/lib/deepl/resources/translation_memory_import.rb +29 -0
  27. data/lib/deepl/resources/translation_memory_job.rb +142 -0
  28. data/lib/deepl/resources/translation_memory_segments.rb +78 -0
  29. data/lib/deepl/translation_memory_api.rb +293 -1
  30. data/lib/deepl/utils/time_parser.rb +26 -0
  31. data/lib/deepl.rb +38 -4
  32. data/lib/version.rb +1 -1
  33. data/spec/api/deepl_spec.rb +134 -332
  34. data/spec/integration_tests/document_api_spec.rb +51 -18
  35. data/spec/integration_tests/document_error_paths_spec.rb +33 -0
  36. data/spec/integration_tests/glossary_api_spec.rb +114 -0
  37. data/spec/integration_tests/glossary_error_paths_spec.rb +107 -0
  38. data/spec/integration_tests/integration_test_utils.rb +8 -0
  39. data/spec/integration_tests/languages_api_spec.rb +54 -0
  40. data/spec/integration_tests/languages_error_paths_spec.rb +25 -0
  41. data/spec/integration_tests/rephrase_api_spec.rb +90 -0
  42. data/spec/integration_tests/rephrase_error_paths_spec.rb +53 -0
  43. data/spec/integration_tests/smoke_test_spec.rb +24 -0
  44. data/spec/integration_tests/style_rule_api_spec.rb +1 -19
  45. data/spec/integration_tests/style_rule_error_paths_spec.rb +45 -0
  46. data/spec/integration_tests/translate_api_spec.rb +117 -0
  47. data/spec/integration_tests/translate_error_paths_spec.rb +48 -0
  48. data/spec/integration_tests/translation_memory_api_spec.rb +191 -17
  49. data/spec/integration_tests/translation_memory_error_paths_spec.rb +87 -0
  50. data/spec/integration_tests/usage_api_spec.rb +29 -0
  51. data/spec/integration_tests/usage_error_paths_spec.rb +18 -0
  52. data/spec/requests/document/upload_spec.rb +130 -0
  53. data/spec/requests/glossary/create_spec.rb +0 -21
  54. data/spec/requests/glossary/destroy_spec.rb +0 -39
  55. data/spec/requests/glossary/entries_spec.rb +0 -35
  56. data/spec/requests/glossary/find_spec.rb +0 -40
  57. data/spec/requests/glossary/language_pairs_spec.rb +0 -13
  58. data/spec/requests/glossary/list_spec.rb +0 -27
  59. data/spec/requests/languages_spec.rb +0 -41
  60. data/spec/requests/rephrase_spec.rb +13 -139
  61. data/spec/requests/style_rule/create_custom_instruction_spec.rb +0 -24
  62. data/spec/requests/style_rule/create_spec.rb +0 -16
  63. data/spec/requests/style_rule/destroy_custom_instruction_spec.rb +0 -26
  64. data/spec/requests/style_rule/destroy_spec.rb +0 -27
  65. data/spec/requests/style_rule/find_custom_instruction_spec.rb +0 -27
  66. data/spec/requests/style_rule/find_spec.rb +0 -28
  67. data/spec/requests/style_rule/list_spec.rb +0 -31
  68. data/spec/requests/style_rule/update_configured_rules_spec.rb +0 -21
  69. data/spec/requests/style_rule/update_custom_instruction_spec.rb +0 -26
  70. data/spec/requests/style_rule/update_spec.rb +0 -19
  71. data/spec/requests/translate_spec.rb +63 -217
  72. data/spec/requests/translation_memory/create_export_spec.rb +37 -0
  73. data/spec/requests/translation_memory/create_import_spec.rb +61 -0
  74. data/spec/requests/translation_memory/destroy_spec.rb +36 -0
  75. data/spec/requests/translation_memory/download_export_spec.rb +54 -0
  76. data/spec/requests/translation_memory/find_job_spec.rb +37 -0
  77. data/spec/requests/translation_memory/find_spec.rb +36 -0
  78. data/spec/requests/translation_memory/list_spec.rb +0 -34
  79. data/spec/requests/translation_memory/segments_spec.rb +58 -0
  80. data/spec/requests/translation_memory/upload_file_spec.rb +55 -0
  81. data/spec/requests/usage_spec.rb +0 -16
  82. data/spec/resources/custom_instruction_spec.rb +32 -0
  83. data/spec/resources/style_rule_spec.rb +68 -0
  84. data/spec/resources/translation_memory_export_spec.rb +38 -0
  85. data/spec/resources/translation_memory_import_spec.rb +30 -0
  86. data/spec/resources/translation_memory_job_spec.rb +109 -0
  87. data/spec/resources/translation_memory_segments_spec.rb +78 -0
  88. data/spec/resources/translation_memory_spec.rb +18 -1
  89. data/spec/spec_helper.rb +15 -45
  90. data/spec/support/live_mock_server.rb +12 -0
  91. data/spec/support/managed_glossary.rb +65 -0
  92. data/spec/support/managed_style_rule.rb +17 -0
  93. data/spec/support/managed_translation_memory.rb +55 -0
  94. metadata +50 -17
  95. data/spec/fixtures/vcr_cassettes/deepl_document.yml +0 -95
  96. data/spec/fixtures/vcr_cassettes/deepl_document_download.yml +0 -1214
  97. data/spec/fixtures/vcr_cassettes/deepl_glossaries.yml +0 -1163
  98. data/spec/fixtures/vcr_cassettes/deepl_languages.yml +0 -54
  99. data/spec/fixtures/vcr_cassettes/deepl_rephrase.yml +0 -87
  100. data/spec/fixtures/vcr_cassettes/deepl_translate.yml +0 -358
  101. data/spec/fixtures/vcr_cassettes/deepl_usage.yml +0 -129
  102. data/spec/fixtures/vcr_cassettes/glossaries.yml +0 -1702
  103. data/spec/fixtures/vcr_cassettes/languages.yml +0 -229
  104. data/spec/fixtures/vcr_cassettes/rephrase_texts.yml +0 -401
  105. data/spec/fixtures/vcr_cassettes/style_rules.yml +0 -92
  106. data/spec/fixtures/vcr_cassettes/style_rules_crud.yml +0 -926
  107. data/spec/fixtures/vcr_cassettes/translate_texts.yml +0 -10630
  108. data/spec/fixtures/vcr_cassettes/translation_memories.yml +0 -74
  109. data/spec/fixtures/vcr_cassettes/usage.yml +0 -171
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac9df781efec35e22812fb7d998386c19dd2f51afeb020d1850c4b2091e5ba20
4
- data.tar.gz: 70db2ae1bd08aa8e1dffb01198225ad29fc5c2514cf4f1a38da948bbe97c51f3
3
+ metadata.gz: 35f6c08b68a4b9aa8907b543ebbfbab38e9f9f35f7e2dc60decaa4bac57340f5
4
+ data.tar.gz: 975d1d68cd43fec41b93252b609bc67a619d08d4fd2e6d6bc9b822ca01fd78ac
5
5
  SHA512:
6
- metadata.gz: 5c55b0eb6a6ef528eb14f78acfb89591fa99de7c7d8a82fb322e5b189c6663ff5f5f915d46ac4e93fcebc5a1d1f71bcef3d94fb1cac36d5e5fcabec9eb656cb5
7
- data.tar.gz: a606b2405448a3598faf0d38e51830437d14019ab6b2cec4a794f93bc47f8329612fc524b1f2d5c71e48063ef4682a12831cada04d8b5c2a9f8ffd1f2012c3e6
6
+ metadata.gz: 749cd28143798f5e08ba848bc4eaa476254ef24656e690fcb3e5ca84314eb54f68ffc7f3675ae74a0cc24c01d194d9dfebcd2d819e0982fad44aba5869e37344
7
+ data.tar.gz: 4a3b7ca19832725c49b0708f544f9842de7733cc8777b41f66835e4dbead815994ca6dab8b8b3052d172417240dcf5fff5934fc3959ef226001e8ad4e0769735
data/.gitlab-ci.yml CHANGED
@@ -4,7 +4,7 @@ include:
4
4
  - project: '${CI_PROJECT_NAMESPACE}/ci-libs-for-client-libraries'
5
5
  file:
6
6
  - '/${CI_PROJECT_NAME}/.gitlab-ci.yml'
7
- - project: 'deepl/ops/ci-cd-infrastructure/gitlab-ci-lib'
7
+ - project: 'deepl-org/deepl/devex/gitlab-ci-lib'
8
8
  file:
9
9
  - '/templates/.buildkit.yml'
10
10
  - '/templates/.secret-detection.yml'
@@ -118,6 +118,9 @@ semgrep-sast:
118
118
  .test_base:
119
119
  stage: test
120
120
  extends: .test
121
+ variables:
122
+ VALIDATE_REQUESTS_FOR_DEEPL_MOCK: '1'
123
+ VALIDATE_RESPONSES_FOR_DEEPL_MOCK: '1'
121
124
  parallel:
122
125
  matrix:
123
126
  - DOCKER_IMAGE: 'ruby:2.7'
@@ -164,7 +167,6 @@ test_manual:
164
167
  rules:
165
168
  - if: $CI_PIPELINE_SOURCE != "schedule"
166
169
 
167
-
168
170
  # stage: publish ----------------------
169
171
 
170
172
  gitlab release:
data/CHANGELOG.md CHANGED
@@ -6,6 +6,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.9.0] - 2026-08-11
10
+ ### Added
11
+ - Added support for using multiple glossaries in text and document translation
12
+ via the `glossary_ids` option (up to 5 glossary IDs) in `translate` and
13
+ document upload.
14
+ - Added support for using a style rule in document translation via the
15
+ `style_id` option (the `style_rule` option) in document upload.
16
+ - Added support for using a translation memory in document translation via the
17
+ `translation_memory_id` option (the `translation_memory` and
18
+ `translation_memory_threshold` options) in document upload.
19
+ - Added support for translation memory management via `DeepL.translation_memories`:
20
+ `find()`, `segments()`, `destroy()`, `create_import()`, `upload_file()`,
21
+ `create_export()`, `find_job()`, `wait_until_job_done()`, `download_export()`,
22
+ and the convenience methods `import_from_filepath()` and `export_to_filepath()`.
23
+ Every method taking a translation memory accepts a string ID or a
24
+ `TranslationMemory` object. `wait_until_job_done()`, `import_from_filepath()`
25
+ and `export_to_filepath()` accept an optional `timeout_s` to stop waiting for
26
+ a job that does not finish. Please refer to the README for usage instructions.
27
+ - Added the `creation_time` and `updated_time` attributes to `TranslationMemory`.
28
+
29
+ ### Changed
30
+ - Reworked the test suite to run against the `deepl-mock` server with OpenAPI
31
+ request and response validation enabled in CI, replacing recorded VCR
32
+ cassettes. Added live integration and error-path specs across all endpoints,
33
+ with resource-level unit specs for response field mapping.
34
+
35
+ ### Removed
36
+ - Dropped VCR cassette playback and the `vcr` and `webmock` development
37
+ dependencies.
38
+
39
+ ## [3.8.0] - 2026-05-27
40
+ ### Added
41
+ - Added support for passing additional HTTP headers to `translate()` and
42
+ `rephrase()`. This can be used to send the `X-DeepL-Reporting-Tag` header
43
+ for usage reporting.
44
+
9
45
  ## [3.7.0] - 2026-05-14
10
46
  ### Added
11
47
  - Added support for listing translation memories via `DeepL.translation_memories.list`.
@@ -119,7 +155,9 @@ The change in major version is only due to the change in maintainership, there i
119
155
  ### Fixed
120
156
  - Make RequestEntityTooLarge error message more clear
121
157
 
122
- [Unreleased]: https://github.com/DeepLcom/deepl-rb/compare/v3.7.0...HEAD
158
+ [Unreleased]: https://github.com/DeepLcom/deepl-rb/compare/v3.9.0...HEAD
159
+ [3.9.0]: https://github.com/DeepLcom/deepl-rb/compare/v3.8.0...v3.9.0
160
+ [3.8.0]: https://github.com/DeepLcom/deepl-rb/compare/v3.7.0...v3.8.0
123
161
  [3.7.0]: https://github.com/DeepLcom/deepl-rb/compare/v3.6.1...v3.7.0
124
162
  [3.6.1]: https://github.com/DeepLcom/deepl-rb/compare/v3.6.0...v3.6.1
125
163
  [3.6.0]: https://github.com/DeepLcom/deepl-rb/compare/v3.5.1...v3.6.0
data/Gemfile CHANGED
@@ -19,6 +19,4 @@ group :test do
19
19
  gem 'rubocop-rspec'
20
20
  gem 'simplecov'
21
21
  gem 'simplecov-cobertura'
22
- gem 'vcr'
23
- gem 'webmock'
24
22
  end
data/README.md CHANGED
@@ -267,6 +267,25 @@ puts translation.text
267
267
  # => Ojalá tuviéramos un auto.
268
268
  ```
269
269
 
270
+ To use more than one glossary at once, specify the `glossary_ids` option with an
271
+ array of up to 5 glossary IDs (as strings or `DeepL::Resources::Glossary`
272
+ objects) instead of `glossary_id`. This works for both text and document
273
+ translation. `glossary_ids` requires `source_lang` to be set, cannot be combined
274
+ with `glossary_id`, and raises `ArgumentError` if these rules are violated or
275
+ more than 5 IDs are provided:
276
+
277
+ ```rb
278
+ # Text translation with multiple glossaries
279
+ translation = DeepL.translate 'Hello World', 'EN', 'ES',
280
+ glossary_ids: ['aa48c7f0-0d02-413e-8a06-d5bbf0ca7a6e',
281
+ 'bb59d8g1-1e13-524f-9b17-e6ccg1db8b7f']
282
+
283
+ # Document translation with multiple glossaries
284
+ handle = DeepL.document.upload 'my_document.docx', 'EN', 'ES', 'my_document.docx',
285
+ glossary_ids: ['aa48c7f0-0d02-413e-8a06-d5bbf0ca7a6e',
286
+ 'bb59d8g1-1e13-524f-9b17-e6ccg1db8b7f']
287
+ ```
288
+
270
289
  To list all the glossaries available, use the `glossaries.list` method:
271
290
 
272
291
  ```rb
@@ -457,6 +476,15 @@ style_rules = DeepL.style_rules.list
457
476
  translation = DeepL.translate 'Hello World', 'EN', 'ES', style_rule: style_rules.first
458
477
  ```
459
478
 
479
+ The same `style_rule` option can be passed to document translation via
480
+ `DeepL.document.upload` (or `DeepL.document.translate_document`), accepting either
481
+ a style rule ID string or a `StyleRule` object:
482
+
483
+ ```rb
484
+ handle = DeepL.document.upload 'my_document.docx', 'EN', 'ES', 'my_document.docx',
485
+ style_rule: 'dca2e053-8ae5-45e6-a0d2-881156e7f4e4'
486
+ ```
487
+
460
488
  ### Translation Memories
461
489
 
462
490
  Translation memories allow you to store and reuse previously created translations.
@@ -464,11 +492,11 @@ They can be used in text translation requests to improve consistency by matching
464
492
  against stored segments. Multiple translation memories can be stored with your
465
493
  account, each with a source language and one or more target languages.
466
494
 
467
- #### Uploading and managing translation memories
495
+ Translation memories can also be managed in the DeepL UI via
496
+ https://www.deepl.com/translation-memory.
468
497
 
469
- Currently translation memories must be uploaded and managed in the DeepL UI via
470
- https://www.deepl.com/translation-memory. Full translation memory management via
471
- the API will come shortly.
498
+ Every method that takes a translation memory accepts either a string containing
499
+ the translation memory ID or a `TranslationMemory` object.
472
500
 
473
501
  #### Listing translation memories
474
502
 
@@ -490,6 +518,176 @@ end
490
518
  translation_memories = DeepL.translation_memories.list(page: 0, page_size: 10)
491
519
  ```
492
520
 
521
+ #### Retrieving a single translation memory
522
+
523
+ `translation_memories.find` retrieves one translation memory by ID. In addition
524
+ to the fields returned by `list`, the resource carries the `creation_time` and
525
+ `updated_time` timestamps.
526
+
527
+ ```rb
528
+ tm = DeepL.translation_memories.find 'YOUR_TM_ID'
529
+
530
+ puts tm.class
531
+ # => DeepL::Resources::TranslationMemory
532
+ puts tm.name
533
+ # => 'Legal'
534
+ puts tm.segment_count
535
+ # => 12
536
+ puts tm.creation_time.class
537
+ # => Time
538
+ ```
539
+
540
+ #### Listing the segments of a translation memory
541
+
542
+ `translation_memories.segments` returns one page of the segments of a
543
+ translation memory as a `TranslationMemorySegments` object. Each segment holds
544
+ the source text and one target per target language of the translation memory.
545
+
546
+ Pagination is cursor-based: omit `page_cursor` on the first call, then pass the
547
+ `next_page_cursor` of the previous response until `next_page?` is `false`. The
548
+ method also accepts `page_size` (1-100, defaults to 50), `filter_text` (a
549
+ substring matched against the source and target texts, at least 2 characters)
550
+ and `filter_case_sensitive` (defaults to `false`).
551
+
552
+ Note that `segment_count` is the number of segments stored in the translation
553
+ memory; a text filter does not reduce it.
554
+
555
+ ```rb
556
+ page = DeepL.translation_memories.segments 'YOUR_TM_ID', page_size: 50
557
+
558
+ puts page.class
559
+ # => DeepL::Resources::TranslationMemorySegments
560
+ puts page.segment_count
561
+ # => 12
562
+ puts page.segments.first.source_text
563
+ # => 'Quelltext Nummer 0'
564
+ puts page.segments.first.targets.first.target_text
565
+ # => 'Source text number 0'
566
+
567
+ # Walk through every page of segments
568
+ while page.next_page?
569
+ page = DeepL.translation_memories.segments 'YOUR_TM_ID',
570
+ page_size: 50,
571
+ page_cursor: page.next_page_cursor
572
+ end
573
+
574
+ # Only the segments matching a text
575
+ page = DeepL.translation_memories.segments 'YOUR_TM_ID',
576
+ filter_text: 'Nummer 1',
577
+ filter_case_sensitive: true
578
+ ```
579
+
580
+ #### Importing a translation memory
581
+
582
+ `translation_memories.import_from_filepath` creates a new translation memory
583
+ from a TMX file. It creates the import job, uploads the file and waits for the
584
+ processing to finish, and returns the finished `TranslationMemoryJob`. Its
585
+ result carries the ID of the newly created translation memory.
586
+
587
+ ```rb
588
+ job = DeepL.translation_memories.import_from_filepath 'legal.tmx',
589
+ display_name: 'Legal',
590
+ timeout_s: 300
591
+
592
+ puts job.class
593
+ # => DeepL::Resources::TranslationMemoryJob
594
+ puts job.status
595
+ # => 'completed'
596
+ puts job.result.translation_memory_id
597
+ # => 'a74d88fb-ed2a-4943-a664-a4512398b994'
598
+ puts job.result.skipped_segment_count
599
+ # => 0
600
+ ```
601
+
602
+ The three steps can also be performed separately, for example to upload a file
603
+ that is not available on the local file system. The upload URL is a pre-signed
604
+ storage URL outside of the DeepL API, so no authorization header is sent with
605
+ the upload.
606
+
607
+ ```rb
608
+ content = File.binread 'legal.tmx'
609
+ created = DeepL.translation_memories.create_import 'legal.tmx', content.bytesize,
610
+ content_type: 'application/xml',
611
+ display_name: 'Legal'
612
+
613
+ puts created.upload_url
614
+ # => 'https://...'
615
+
616
+ DeepL.translation_memories.upload_file created, content
617
+ job = DeepL.translation_memories.wait_until_job_done created.job_id
618
+ ```
619
+
620
+ Until the file is uploaded the job stays in the `awaiting_input` status and
621
+ `result.required_action` describes what is missing. The API detects the upload
622
+ asynchronously, so the job keeps reporting `awaiting_input` for a while
623
+ afterwards, typically around half a minute, before it completes.
624
+ `wait_until_job_done` therefore polls through that status like any other
625
+ non-terminal one. A job whose file is never uploaded does not finish on its own,
626
+ so pass `timeout_s` when that is a possibility.
627
+
628
+ #### Exporting a translation memory
629
+
630
+ `translation_memories.export_to_filepath` writes a translation memory to a TMX
631
+ file. It creates the export job, waits for it to finish and downloads the
632
+ result, overwriting the output file if it already exists.
633
+
634
+ ```rb
635
+ job = DeepL.translation_memories.export_to_filepath 'YOUR_TM_ID', 'export.tmx'
636
+
637
+ puts job.status
638
+ # => 'completed'
639
+ ```
640
+
641
+ The steps can be performed separately as well. Repeating the export of an
642
+ unchanged translation memory reuses the previously completed job instead of
643
+ starting a new one, which `reused_existing?` reports. Just like the upload URL,
644
+ the download URL is a pre-signed storage URL and is requested without an
645
+ authorization header.
646
+
647
+ ```rb
648
+ created = DeepL.translation_memories.create_export 'YOUR_TM_ID'
649
+
650
+ puts created.reused_existing?
651
+ # => false
652
+
653
+ job = DeepL.translation_memories.wait_until_job_done created.job_id
654
+
655
+ puts job.result.download_url
656
+ # => 'https://...'
657
+
658
+ DeepL.translation_memories.download_export job, 'export.tmx'
659
+ ```
660
+
661
+ #### Tracking import and export jobs
662
+
663
+ `translation_memories.find_job` returns the current status of an import or
664
+ export job, and `translation_memories.wait_until_job_done` polls it every five
665
+ seconds until it finished, raising if the job failed or expired. Pass
666
+ `timeout_s` to give up after a number of seconds instead of waiting forever.
667
+
668
+ ```rb
669
+ job = DeepL.translation_memories.find_job 'YOUR_JOB_ID'
670
+
671
+ puts job.operation
672
+ # => 'import'
673
+ puts job.status
674
+ # => 'processing'
675
+ puts job.finished?
676
+ # => false
677
+ ```
678
+
679
+ The status is one of `awaiting_input`, `processing`, `completed`, `downloaded`,
680
+ `failed` or `expired`.
681
+
682
+ #### Deleting a translation memory
683
+
684
+ `translation_memories.destroy` deletes a translation memory and returns its ID.
685
+
686
+ ```rb
687
+ DeepL.translation_memories.destroy 'YOUR_TM_ID'
688
+ # => 'YOUR_TM_ID'
689
+ ```
690
+
493
691
  #### Using a translation memory in translations
494
692
 
495
693
  Pass the `translation_memory` parameter to `translate` to use a translation
@@ -510,6 +708,17 @@ translation = DeepL.translate 'Hello, world!', 'EN', 'DE',
510
708
  translation_memory: translation_memories.first
511
709
  ```
512
710
 
711
+ The same `translation_memory` and `translation_memory_threshold` options can be
712
+ passed to document translation via `DeepL.document.upload` (or
713
+ `DeepL.document.translate_document`). The `translation_memory` option accepts
714
+ either a translation memory ID string or a `TranslationMemory` object:
715
+
716
+ ```rb
717
+ handle = DeepL.document.upload 'my_document.docx', 'EN', 'DE', 'my_document.docx',
718
+ translation_memory: 'YOUR_TM_ID',
719
+ translation_memory_threshold: 80
720
+ ```
721
+
513
722
  ### Monitor usage
514
723
 
515
724
  To check current API usage, use:
@@ -551,6 +760,18 @@ doc_handle = DeepL.document.upload('/path/to/spanish_document.pdf', 'ES', 'EN',
551
760
 
552
761
  The `extra_body_parameters` option allows you to pass arbitrary parameters in the request body. This can be used to access beta features by adding new parameters, or to override built-in parameters (such as `target_lang`, `source_lang`, etc.) for testing purposes.
553
762
 
763
+ ### Sending additional HTTP headers
764
+
765
+ You can pass additional HTTP headers to `translate`, `rephrase`, and the
766
+ `document` methods. For example, to send the `X-DeepL-Reporting-Tag` header
767
+ for usage reporting (see the [cookbook entry](https://developers.deepl.com/docs/learning-how-tos/cookbook/sending-custom-reporting-tags-from-client-libraries)):
768
+
769
+ ```rb
770
+ additional_headers = { 'X-DeepL-Reporting-Tag' => 'my-tag' }
771
+ translation = DeepL.translate 'Hello, world!', 'EN', 'DE', {}, additional_headers
772
+ rephrased = DeepL.rephrase 'Hello, world!', 'EN', nil, nil, {}, additional_headers
773
+ ```
774
+
554
775
  ### Handle exceptions
555
776
 
556
777
  You can capture and process exceptions that may be raised during API calls. These are all the possible exceptions:
@@ -712,18 +933,6 @@ To run tests (rspec and rubocop), use
712
933
  bundle exec rake test
713
934
  ```
714
935
 
715
- ### Caution: Changing VCR Tests
716
-
717
- If you need to rerecord some of the VCR tests, simply setting `record: :new_episodes` and rerunning `rspec` won't be enough in some cases, specifically around document translation (due to its statefulness) and glossaries (since a glossary ID is associated with a specific API account).
718
- For example, there are document translations tests that split up the `upload`, `get_status` and `download` calls into separate test cases. You need to first rerecord the `upload` call, you can do execute a single test like this (the line should be where the `it` block of the test starts):
719
-
720
- ```sh
721
- rspec ./spec/api/deepl_spec.rb:152
722
- ```
723
-
724
- This will return a `document_id` and a `document_key`, you will need to update the values in the `get_status` and `download` tests accordingly. You can find examples for this in the git history.
725
- Similarly, for the glossary tests you will need to delete the recorded HTTP requests for certain glossary IDs so that `rspec` will create the glossaries on your account instead. Feel free to reach out on our discord if you run into any trouble here.
726
-
727
936
  ## Acknowledgements
728
937
 
729
938
  This library was originally developed by [Daniel Herzog](mailto:info@danielherzog.es), we are grateful for his contributions. Beginning with v3.0.0, DeepL took over development and officially supports and maintains the library together with Daniel.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.7.0
1
+ 3.9.0
data/deepl-rb.gemspec CHANGED
@@ -2,17 +2,17 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: deepl-rb 3.7.0 ruby lib
5
+ # stub: deepl-rb 3.9.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "deepl-rb".freeze
9
- s.version = "3.7.0"
9
+ s.version = "3.9.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.metadata = { "bug_tracker_uri" => "https://github.com/DeepLcom/deepl-rb/issues", "changelog_uri" => "https://github.com/DeepLcom/deepl-rb/blob/main/CHANGELOG.md", "documentation_uri" => "https://github.com/DeepLcom/deepl-rb/blob/main/README.md", "homepage_uri" => "https://github.com/DeepLcom/deepl-rb" } if s.respond_to? :metadata=
13
13
  s.require_paths = ["lib".freeze]
14
14
  s.authors = ["DeepL SE".freeze]
15
- s.date = "2026-05-14"
15
+ s.date = "2026-08-12"
16
16
  s.description = "Official Ruby library for the DeepL language translation API (v2). For more information, check this: https://www.deepl.com/docs/api-reference.html".freeze
17
17
  s.email = "open-source@deepl.com".freeze
18
18
  s.extra_rdoc_files = [
@@ -82,7 +82,17 @@ Gem::Specification.new do |s|
82
82
  "lib/deepl/requests/style_rule/update_configured_rules.rb",
83
83
  "lib/deepl/requests/style_rule/update_custom_instruction.rb",
84
84
  "lib/deepl/requests/translate.rb",
85
+ "lib/deepl/requests/translation_memory/base.rb",
86
+ "lib/deepl/requests/translation_memory/create_export.rb",
87
+ "lib/deepl/requests/translation_memory/create_import.rb",
88
+ "lib/deepl/requests/translation_memory/destroy.rb",
89
+ "lib/deepl/requests/translation_memory/download_export.rb",
90
+ "lib/deepl/requests/translation_memory/find.rb",
91
+ "lib/deepl/requests/translation_memory/find_job.rb",
85
92
  "lib/deepl/requests/translation_memory/list.rb",
93
+ "lib/deepl/requests/translation_memory/segments.rb",
94
+ "lib/deepl/requests/translation_memory/storage_base.rb",
95
+ "lib/deepl/requests/translation_memory/upload_file.rb",
86
96
  "lib/deepl/requests/usage.rb",
87
97
  "lib/deepl/resources/base.rb",
88
98
  "lib/deepl/resources/document_handle.rb",
@@ -93,11 +103,16 @@ Gem::Specification.new do |s|
93
103
  "lib/deepl/resources/style_rule.rb",
94
104
  "lib/deepl/resources/text.rb",
95
105
  "lib/deepl/resources/translation_memory.rb",
106
+ "lib/deepl/resources/translation_memory_export.rb",
107
+ "lib/deepl/resources/translation_memory_import.rb",
108
+ "lib/deepl/resources/translation_memory_job.rb",
109
+ "lib/deepl/resources/translation_memory_segments.rb",
96
110
  "lib/deepl/resources/usage.rb",
97
111
  "lib/deepl/style_rule_api.rb",
98
112
  "lib/deepl/translation_memory_api.rb",
99
113
  "lib/deepl/utils/backoff_timer.rb",
100
114
  "lib/deepl/utils/exception_builder.rb",
115
+ "lib/deepl/utils/time_parser.rb",
101
116
  "lib/http_client_options.rb",
102
117
  "lib/version.rb",
103
118
  "license_checker.sh",
@@ -105,25 +120,25 @@ Gem::Specification.new do |s|
105
120
  "spec/api/configuration_spec.rb",
106
121
  "spec/api/deepl_spec.rb",
107
122
  "spec/constants/constants_spec.rb",
108
- "spec/fixtures/vcr_cassettes/deepl_document.yml",
109
- "spec/fixtures/vcr_cassettes/deepl_document_download.yml",
110
- "spec/fixtures/vcr_cassettes/deepl_glossaries.yml",
111
- "spec/fixtures/vcr_cassettes/deepl_languages.yml",
112
- "spec/fixtures/vcr_cassettes/deepl_rephrase.yml",
113
- "spec/fixtures/vcr_cassettes/deepl_translate.yml",
114
- "spec/fixtures/vcr_cassettes/deepl_usage.yml",
115
- "spec/fixtures/vcr_cassettes/glossaries.yml",
116
- "spec/fixtures/vcr_cassettes/languages.yml",
117
- "spec/fixtures/vcr_cassettes/rephrase_texts.yml",
118
- "spec/fixtures/vcr_cassettes/style_rules.yml",
119
- "spec/fixtures/vcr_cassettes/style_rules_crud.yml",
120
- "spec/fixtures/vcr_cassettes/translate_texts.yml",
121
- "spec/fixtures/vcr_cassettes/translation_memories.yml",
122
- "spec/fixtures/vcr_cassettes/usage.yml",
123
123
  "spec/integration_tests/document_api_spec.rb",
124
+ "spec/integration_tests/document_error_paths_spec.rb",
125
+ "spec/integration_tests/glossary_api_spec.rb",
126
+ "spec/integration_tests/glossary_error_paths_spec.rb",
124
127
  "spec/integration_tests/integration_test_utils.rb",
128
+ "spec/integration_tests/languages_api_spec.rb",
129
+ "spec/integration_tests/languages_error_paths_spec.rb",
130
+ "spec/integration_tests/rephrase_api_spec.rb",
131
+ "spec/integration_tests/rephrase_error_paths_spec.rb",
132
+ "spec/integration_tests/smoke_test_spec.rb",
125
133
  "spec/integration_tests/style_rule_api_spec.rb",
134
+ "spec/integration_tests/style_rule_error_paths_spec.rb",
135
+ "spec/integration_tests/translate_api_spec.rb",
136
+ "spec/integration_tests/translate_error_paths_spec.rb",
126
137
  "spec/integration_tests/translation_memory_api_spec.rb",
138
+ "spec/integration_tests/translation_memory_error_paths_spec.rb",
139
+ "spec/integration_tests/usage_api_spec.rb",
140
+ "spec/integration_tests/usage_error_paths_spec.rb",
141
+ "spec/requests/document/upload_spec.rb",
127
142
  "spec/requests/extra_body_parameters_types_spec.rb",
128
143
  "spec/requests/glossary/create_spec.rb",
129
144
  "spec/requests/glossary/destroy_spec.rb",
@@ -144,15 +159,33 @@ Gem::Specification.new do |s|
144
159
  "spec/requests/style_rule/update_custom_instruction_spec.rb",
145
160
  "spec/requests/style_rule/update_spec.rb",
146
161
  "spec/requests/translate_spec.rb",
162
+ "spec/requests/translation_memory/create_export_spec.rb",
163
+ "spec/requests/translation_memory/create_import_spec.rb",
164
+ "spec/requests/translation_memory/destroy_spec.rb",
165
+ "spec/requests/translation_memory/download_export_spec.rb",
166
+ "spec/requests/translation_memory/find_job_spec.rb",
167
+ "spec/requests/translation_memory/find_spec.rb",
147
168
  "spec/requests/translation_memory/list_spec.rb",
169
+ "spec/requests/translation_memory/segments_spec.rb",
170
+ "spec/requests/translation_memory/upload_file_spec.rb",
148
171
  "spec/requests/usage_spec.rb",
172
+ "spec/resources/custom_instruction_spec.rb",
149
173
  "spec/resources/glossary_spec.rb",
150
174
  "spec/resources/language_pair_spec.rb",
151
175
  "spec/resources/language_spec.rb",
176
+ "spec/resources/style_rule_spec.rb",
152
177
  "spec/resources/text_spec.rb",
178
+ "spec/resources/translation_memory_export_spec.rb",
179
+ "spec/resources/translation_memory_import_spec.rb",
180
+ "spec/resources/translation_memory_job_spec.rb",
181
+ "spec/resources/translation_memory_segments_spec.rb",
153
182
  "spec/resources/translation_memory_spec.rb",
154
183
  "spec/resources/usage_spec.rb",
155
- "spec/spec_helper.rb"
184
+ "spec/spec_helper.rb",
185
+ "spec/support/live_mock_server.rb",
186
+ "spec/support/managed_glossary.rb",
187
+ "spec/support/managed_style_rule.rb",
188
+ "spec/support/managed_translation_memory.rb"
156
189
  ]
157
190
  s.homepage = "https://github.com/DeepLcom/deepl-rb".freeze
158
191
  s.licenses = ["MIT".freeze]
@@ -25,7 +25,17 @@ module DeepL
25
25
  # @param [String, nil] filename The filename of the file, including its extension. Used to open
26
26
  # the different kinds of documents (PDFs, etc). If nil, will use
27
27
  # the filename of +input_file_path+.
28
- # @param [Hash] options Additional (body) options for the upload.
28
+ # @param [Hash] options Additional (body) options for the upload. Notable glossary options:
29
+ # +:glossary_id+ (a single glossary ID) or +:glossary_ids+ (an array of
30
+ # up to 5 glossary IDs, as strings or `DeepL::Resources::Glossary`
31
+ # objects). +:glossary_ids+ requires +source_lang+ to be set, cannot be
32
+ # combined with +:glossary_id+, and raises `ArgumentError` if these rules
33
+ # are violated or more than 5 IDs are provided. Notable style/translation
34
+ # memory options (mirroring text translation): +:style_rule+ (a style rule
35
+ # ID string or a `DeepL::Resources::StyleRule` object),
36
+ # +:translation_memory+ (a translation memory ID string or a
37
+ # `DeepL::Resources::TranslationMemory` object), and
38
+ # +:translation_memory_threshold+ (integer 0-100, recommended minimum 75).
29
39
  # @param [Hash] additional_headers Additional HTTP headers for the upload.
30
40
  # @return [DeepL::Resources::DocumentHandle] Document handle for the uploaded document.
31
41
 
@@ -6,6 +6,9 @@
6
6
  module DeepL
7
7
  module Requests
8
8
  class Base # rubocop:disable Metrics/ClassLength
9
+ # Maximum number of glossaries that may be provided via the +glossary_ids+ option.
10
+ MAX_GLOSSARY_IDS = 5
11
+
9
12
  attr_reader :api, :response, :options
10
13
 
11
14
  def initialize(api, options = {}, additional_headers = {})
@@ -85,7 +88,7 @@ module DeepL
85
88
  api.configuration.logger&.info("Request to the DeepL API: #{self}")
86
89
  api.configuration.logger&.debug("Request details: #{details}")
87
90
  loop do
88
- resp = api.http_client.request(req)
91
+ resp = http_client.request(req)
89
92
  validate_response!(resp)
90
93
  return [req, resp]
91
94
  rescue DeepL::Exceptions::Error => e
@@ -95,7 +98,7 @@ module DeepL
95
98
  api.configuration.logger&.info("Encountered a retryable exception: #{e.message}")
96
99
  end
97
100
  api.configuration.logger&.info("Starting retry #{@backoff_timer.num_retries + 1} for " \
98
- "request #{request} after sleeping for " \
101
+ "request #{self} after sleeping for " \
99
102
  "#{format('%.2f', @backoff_timer.time_until_deadline)}")
100
103
  files_to_reset.each(&:rewind)
101
104
  @backoff_timer.sleep_until_deadline
@@ -106,7 +109,7 @@ module DeepL
106
109
  api.configuration.logger&.info("Encountered a retryable exception: #{e.message}")
107
110
  end
108
111
  api.configuration.logger&.info("Starting retry #{@backoff_timer.num_retries + 1} for " \
109
- "request #{request} after sleeping for " \
112
+ "request #{self} after sleeping for " \
110
113
  "#{format('%.2f', @backoff_timer.time_until_deadline)}")
111
114
  files_to_reset.each(&:rewind)
112
115
  @backoff_timer.sleep_until_deadline
@@ -172,6 +175,49 @@ module DeepL
172
175
  raise Utils::ExceptionBuilder.new(response).build
173
176
  end
174
177
 
178
+ # Validates the +glossary_ids+ option and resolves it to an array of glossary ID strings.
179
+ #
180
+ # The +glossary_ids+ option accepts an array of up to `MAX_GLOSSARY_IDS` glossary
181
+ # identifiers. Each entry may be a glossary ID string or a
182
+ # `DeepL::Resources::Glossary` object. It:
183
+ # * requires +source_lang+ to be set,
184
+ # * cannot be combined with the singular +glossary_id+ option,
185
+ # * allows at most `MAX_GLOSSARY_IDS` identifiers.
186
+ #
187
+ # The API expects +glossary_ids+ as an array (a JSON array in the JSON translate body).
188
+ # Callers sending multipart/form-data (e.g. document upload) should join the returned
189
+ # array into a comma-separated string.
190
+ #
191
+ # @param [String, nil] source_lang The source language for the request.
192
+ # @return [Array<String>, nil] The list of glossary IDs, or nil if the +glossary_ids+
193
+ # option was not provided.
194
+ # @raise [ArgumentError] If any of the validation rules above are violated.
195
+ def build_glossary_ids_param(source_lang)
196
+ return nil unless option?(:glossary_ids)
197
+
198
+ ids = Array(delete_option(:glossary_ids))
199
+ return nil if ids.empty?
200
+
201
+ validate_glossary_ids!(ids, source_lang)
202
+ ids.map do |glossary|
203
+ glossary.is_a?(DeepL::Resources::Glossary) ? glossary.id : glossary.to_s
204
+ end
205
+ end
206
+
207
+ def validate_glossary_ids!(ids, source_lang)
208
+ if option?(:glossary_id)
209
+ raise ArgumentError,
210
+ 'The `glossary_ids` option cannot be used together with the `glossary_id` option'
211
+ end
212
+ if source_lang.nil?
213
+ raise ArgumentError, 'The `glossary_ids` option requires `source_lang` to be set'
214
+ end
215
+ return unless ids.size > MAX_GLOSSARY_IDS
216
+
217
+ raise ArgumentError,
218
+ "A maximum of #{MAX_GLOSSARY_IDS} glossary IDs may be provided via `glossary_ids`"
219
+ end
220
+
175
221
  def path
176
222
  raise NotImplementedError
177
223
  end
@@ -189,6 +235,12 @@ module DeepL
189
235
  api.configuration.host
190
236
  end
191
237
 
238
+ # Subclasses that talk to a host other than the DeepL API, such as a pre-signed storage
239
+ # URL, override this to request through their own connection.
240
+ def http_client
241
+ api.http_client
242
+ end
243
+
192
244
  def query_params
193
245
  options
194
246
  end