deepl-rb 3.9.0 → 3.10.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 (237) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +70 -52
  3. data/.rubocop.yml +26 -0
  4. data/CHANGELOG.md +429 -18
  5. data/Gemfile +4 -0
  6. data/README.md +981 -223
  7. data/Rakefile +18 -5
  8. data/VERSION +1 -1
  9. data/deepl-rb.gemspec +6 -6
  10. data/lib/deepl/admin_client.rb +183 -0
  11. data/lib/deepl/api.rb +30 -6
  12. data/lib/deepl/app_info.rb +20 -0
  13. data/lib/deepl/client.rb +969 -0
  14. data/lib/deepl/configuration.rb +56 -10
  15. data/lib/deepl/constants/admin_usage_group_by.rb +13 -0
  16. data/lib/deepl/constants/custom_tag_usage_aggregate_by.rb +13 -0
  17. data/lib/deepl/constants/document_status_code.rb +17 -0
  18. data/lib/deepl/constants/language_include.rb +17 -0
  19. data/lib/deepl/constants/language_resource_name.rb +21 -0
  20. data/lib/deepl/constants/tag_handling_version.rb +14 -0
  21. data/lib/deepl/deprecation.rb +42 -0
  22. data/lib/deepl/document_api.rb +41 -22
  23. data/lib/deepl/exceptions/bad_request.rb +1 -3
  24. data/lib/deepl/exceptions/connection_error.rb +37 -0
  25. data/lib/deepl/exceptions/document_not_ready_error.rb +17 -0
  26. data/lib/deepl/exceptions/document_translation_error.rb +17 -1
  27. data/lib/deepl/exceptions/error.rb +13 -1
  28. data/lib/deepl/exceptions/not_found.rb +8 -3
  29. data/lib/deepl/exceptions/not_supported.rb +6 -0
  30. data/lib/deepl/exceptions/request_error.rb +19 -2
  31. data/lib/deepl/exceptions/server_error.rb +7 -1
  32. data/lib/deepl/exceptions/validation_error.rb +14 -0
  33. data/lib/deepl/requests/admin/base.rb +94 -0
  34. data/lib/deepl/requests/admin/create_developer_key.rb +38 -0
  35. data/lib/deepl/requests/admin/custom_tag_usage_analytics.rb +38 -0
  36. data/lib/deepl/requests/admin/list_developer_keys.rb +28 -0
  37. data/lib/deepl/requests/admin/update_developer_key.rb +35 -0
  38. data/lib/deepl/requests/admin/usage_analytics.rb +36 -0
  39. data/lib/deepl/requests/base.rb +229 -59
  40. data/lib/deepl/requests/correct.rb +57 -0
  41. data/lib/deepl/requests/document/download.rb +11 -3
  42. data/lib/deepl/requests/document/get_status.rb +2 -2
  43. data/lib/deepl/requests/document/upload.rb +43 -10
  44. data/lib/deepl/requests/glossary/create.rb +6 -2
  45. data/lib/deepl/requests/glossary/language_pairs.rb +20 -4
  46. data/lib/deepl/requests/language_resources.rb +41 -0
  47. data/lib/deepl/requests/languages.rb +28 -4
  48. data/lib/deepl/requests/languages_for_resource.rb +54 -0
  49. data/lib/deepl/requests/multilingual_glossary/create.rb +37 -0
  50. data/lib/deepl/requests/multilingual_glossary/destroy.rb +36 -0
  51. data/lib/deepl/requests/multilingual_glossary/destroy_dictionary.rb +51 -0
  52. data/lib/deepl/requests/multilingual_glossary/entries.rb +66 -0
  53. data/lib/deepl/requests/multilingual_glossary/find.rb +35 -0
  54. data/lib/deepl/requests/multilingual_glossary/helpers.rb +63 -0
  55. data/lib/deepl/requests/multilingual_glossary/list.rb +39 -0
  56. data/lib/deepl/requests/multilingual_glossary/replace_dictionary.rb +41 -0
  57. data/lib/deepl/requests/multilingual_glossary/update.rb +40 -0
  58. data/lib/deepl/requests/rephrase.rb +5 -9
  59. data/lib/deepl/requests/spoken_terms/create.rb +37 -0
  60. data/lib/deepl/requests/spoken_terms/destroy.rb +36 -0
  61. data/lib/deepl/requests/spoken_terms/destroy_term_list.rb +41 -0
  62. data/lib/deepl/requests/spoken_terms/entries.rb +46 -0
  63. data/lib/deepl/requests/spoken_terms/find.rb +35 -0
  64. data/lib/deepl/requests/spoken_terms/helpers.rb +67 -0
  65. data/lib/deepl/requests/spoken_terms/list.rb +35 -0
  66. data/lib/deepl/requests/spoken_terms/replace_term_list.rb +41 -0
  67. data/lib/deepl/requests/spoken_terms/update.rb +40 -0
  68. data/lib/deepl/requests/style_profile/base.rb +83 -0
  69. data/lib/deepl/requests/style_profile/create.rb +35 -0
  70. data/lib/deepl/requests/style_profile/destroy.rb +34 -0
  71. data/lib/deepl/requests/style_profile/find.rb +33 -0
  72. data/lib/deepl/requests/style_profile/list.rb +43 -0
  73. data/lib/deepl/requests/style_profile/update.rb +35 -0
  74. data/lib/deepl/requests/style_rule/create.rb +1 -1
  75. data/lib/deepl/requests/style_rule/create_custom_instruction.rb +4 -2
  76. data/lib/deepl/requests/style_rule/destroy.rb +4 -2
  77. data/lib/deepl/requests/style_rule/destroy_custom_instruction.rb +5 -3
  78. data/lib/deepl/requests/style_rule/find.rb +4 -2
  79. data/lib/deepl/requests/style_rule/find_custom_instruction.rb +5 -3
  80. data/lib/deepl/requests/style_rule/helpers.rb +35 -0
  81. data/lib/deepl/requests/style_rule/list.rb +1 -1
  82. data/lib/deepl/requests/style_rule/update.rb +4 -2
  83. data/lib/deepl/requests/style_rule/update_configured_rules.rb +4 -2
  84. data/lib/deepl/requests/style_rule/update_custom_instruction.rb +5 -3
  85. data/lib/deepl/requests/translate.rb +52 -20
  86. data/lib/deepl/requests/translation_memory/base.rb +1 -4
  87. data/lib/deepl/requests/translation_memory/create_export.rb +1 -1
  88. data/lib/deepl/requests/translation_memory/create_import.rb +1 -1
  89. data/lib/deepl/requests/translation_memory/destroy.rb +1 -1
  90. data/lib/deepl/requests/translation_memory/download_export.rb +4 -0
  91. data/lib/deepl/requests/translation_memory/find.rb +1 -1
  92. data/lib/deepl/requests/translation_memory/find_job.rb +1 -1
  93. data/lib/deepl/requests/translation_memory/list.rb +2 -1
  94. data/lib/deepl/requests/translation_memory/segments.rb +3 -2
  95. data/lib/deepl/requests/translation_memory/storage_base.rb +40 -8
  96. data/lib/deepl/requests/translation_memory/upload_file.rb +1 -1
  97. data/lib/deepl/requests/usage.rb +28 -1
  98. data/lib/deepl/requests/write_improvements.rb +35 -0
  99. data/lib/deepl/resources/admin_usage_report.rb +73 -0
  100. data/lib/deepl/resources/custom_tag_usage_report.rb +59 -0
  101. data/lib/deepl/resources/developer_key.rb +57 -0
  102. data/lib/deepl/resources/document_handle.rb +24 -25
  103. data/lib/deepl/resources/document_translation_status.rb +23 -4
  104. data/lib/deepl/resources/language.rb +10 -2
  105. data/lib/deepl/resources/language_resource.rb +57 -0
  106. data/lib/deepl/resources/language_support.rb +56 -0
  107. data/lib/deepl/resources/multilingual_glossary.rb +112 -0
  108. data/lib/deepl/resources/spoken_terms.rb +61 -0
  109. data/lib/deepl/resources/style_profile.rb +44 -0
  110. data/lib/deepl/resources/style_rule.rb +3 -0
  111. data/lib/deepl/resources/text.rb +18 -2
  112. data/lib/deepl/resources/translation_memory.rb +8 -2
  113. data/lib/deepl/resources/translation_memory_import.rb +1 -1
  114. data/lib/deepl/resources/translation_memory_job.rb +23 -3
  115. data/lib/deepl/resources/translation_memory_segments.rb +8 -6
  116. data/lib/deepl/resources/usage.rb +93 -4
  117. data/lib/deepl/style_rule_api.rb +11 -0
  118. data/lib/deepl/translation_memory_api.rb +104 -24
  119. data/lib/deepl/utils/client_builder.rb +54 -0
  120. data/lib/deepl/utils/document_poller.rb +70 -0
  121. data/lib/deepl/utils/exception_builder.rb +3 -2
  122. data/lib/deepl/utils/http_client_builder.rb +79 -0
  123. data/lib/deepl/utils/number_parser.rb +34 -0
  124. data/lib/deepl/utils/term_validator.rb +33 -0
  125. data/lib/deepl/utils/time_parser.rb +14 -0
  126. data/lib/deepl.rb +166 -41
  127. data/lib/http_client_options.rb +21 -1
  128. data/lib/version.rb +1 -1
  129. data/spec/fixtures.rb +33 -0
  130. data/spec/offline/admin_client_spec.rb +551 -0
  131. data/spec/offline/client_spec.rb +273 -0
  132. data/spec/{api → offline}/configuration_spec.rb +45 -3
  133. data/spec/{constants → offline}/constants_spec.rb +23 -1
  134. data/spec/offline/correct_text_spec.rb +50 -0
  135. data/spec/offline/deprecation_spec.rb +32 -0
  136. data/spec/offline/errors_spec.rb +92 -0
  137. data/spec/offline/glossaries_spec.rb +98 -0
  138. data/spec/offline/http_client_spec.rb +319 -0
  139. data/spec/offline/languages_spec.rb +155 -0
  140. data/spec/offline/module_methods_spec.rb +13 -0
  141. data/spec/offline/multilingual_glossaries_spec.rb +358 -0
  142. data/spec/offline/offline_helper.rb +71 -0
  143. data/spec/offline/rephrase_text_spec.rb +73 -0
  144. data/spec/offline/spoken_terms_spec.rb +215 -0
  145. data/spec/offline/style_profiles_spec.rb +266 -0
  146. data/spec/offline/style_rules_spec.rb +318 -0
  147. data/spec/offline/translate_document_spec.rb +606 -0
  148. data/spec/offline/translate_text_spec.rb +286 -0
  149. data/spec/offline/translation_memories_client_spec.rb +256 -0
  150. data/spec/offline/translation_memories_spec.rb +463 -0
  151. data/spec/offline/usage_spec.rb +151 -0
  152. data/spec/real_api/admin_client_spec.rb +77 -0
  153. data/spec/real_api/correct_text_spec.rb +20 -0
  154. data/spec/real_api/glossaries_spec.rb +28 -0
  155. data/spec/real_api/languages_spec.rb +27 -0
  156. data/spec/real_api/multilingual_glossaries_spec.rb +53 -0
  157. data/spec/real_api/proxy_spec.rb +41 -0
  158. data/spec/real_api/real_api_helper.rb +65 -0
  159. data/spec/real_api/rephrase_text_spec.rb +16 -0
  160. data/spec/real_api/spoken_terms_spec.rb +56 -0
  161. data/spec/real_api/style_profiles_spec.rb +46 -0
  162. data/spec/real_api/style_rules_spec.rb +46 -0
  163. data/spec/real_api/translate_document_spec.rb +18 -0
  164. data/spec/real_api/translate_text_spec.rb +16 -0
  165. data/spec/real_api/translation_memories_spec.rb +56 -0
  166. data/spec/real_api/usage_spec.rb +20 -0
  167. data/spec/spec_helper.rb +1 -50
  168. metadata +116 -76
  169. data/.circleci/config.yml +0 -27
  170. data/.github/workflows/add_issues_to_kanban.yml +0 -16
  171. data/spec/api/api_spec.rb +0 -20
  172. data/spec/api/deepl_spec.rb +0 -262
  173. data/spec/integration_tests/document_api_spec.rb +0 -211
  174. data/spec/integration_tests/document_error_paths_spec.rb +0 -33
  175. data/spec/integration_tests/glossary_api_spec.rb +0 -114
  176. data/spec/integration_tests/glossary_error_paths_spec.rb +0 -107
  177. data/spec/integration_tests/integration_test_utils.rb +0 -178
  178. data/spec/integration_tests/languages_api_spec.rb +0 -54
  179. data/spec/integration_tests/languages_error_paths_spec.rb +0 -25
  180. data/spec/integration_tests/rephrase_api_spec.rb +0 -90
  181. data/spec/integration_tests/rephrase_error_paths_spec.rb +0 -53
  182. data/spec/integration_tests/smoke_test_spec.rb +0 -24
  183. data/spec/integration_tests/style_rule_api_spec.rb +0 -117
  184. data/spec/integration_tests/style_rule_error_paths_spec.rb +0 -45
  185. data/spec/integration_tests/translate_api_spec.rb +0 -117
  186. data/spec/integration_tests/translate_error_paths_spec.rb +0 -48
  187. data/spec/integration_tests/translation_memory_api_spec.rb +0 -244
  188. data/spec/integration_tests/translation_memory_error_paths_spec.rb +0 -87
  189. data/spec/integration_tests/usage_api_spec.rb +0 -29
  190. data/spec/integration_tests/usage_error_paths_spec.rb +0 -18
  191. data/spec/requests/document/upload_spec.rb +0 -130
  192. data/spec/requests/extra_body_parameters_types_spec.rb +0 -82
  193. data/spec/requests/glossary/create_spec.rb +0 -44
  194. data/spec/requests/glossary/destroy_spec.rb +0 -27
  195. data/spec/requests/glossary/entries_spec.rb +0 -27
  196. data/spec/requests/glossary/find_spec.rb +0 -28
  197. data/spec/requests/glossary/language_pairs_spec.rb +0 -27
  198. data/spec/requests/glossary/list_spec.rb +0 -27
  199. data/spec/requests/languages_spec.rb +0 -27
  200. data/spec/requests/rephrase_spec.rb +0 -46
  201. data/spec/requests/style_rule/create_custom_instruction_spec.rb +0 -30
  202. data/spec/requests/style_rule/create_spec.rb +0 -29
  203. data/spec/requests/style_rule/destroy_custom_instruction_spec.rb +0 -28
  204. data/spec/requests/style_rule/destroy_spec.rb +0 -27
  205. data/spec/requests/style_rule/find_custom_instruction_spec.rb +0 -29
  206. data/spec/requests/style_rule/find_spec.rb +0 -28
  207. data/spec/requests/style_rule/list_spec.rb +0 -27
  208. data/spec/requests/style_rule/update_configured_rules_spec.rb +0 -31
  209. data/spec/requests/style_rule/update_custom_instruction_spec.rb +0 -32
  210. data/spec/requests/style_rule/update_spec.rb +0 -29
  211. data/spec/requests/translate_spec.rb +0 -338
  212. data/spec/requests/translation_memory/create_export_spec.rb +0 -37
  213. data/spec/requests/translation_memory/create_import_spec.rb +0 -61
  214. data/spec/requests/translation_memory/destroy_spec.rb +0 -36
  215. data/spec/requests/translation_memory/download_export_spec.rb +0 -54
  216. data/spec/requests/translation_memory/find_job_spec.rb +0 -37
  217. data/spec/requests/translation_memory/find_spec.rb +0 -36
  218. data/spec/requests/translation_memory/list_spec.rb +0 -27
  219. data/spec/requests/translation_memory/segments_spec.rb +0 -58
  220. data/spec/requests/translation_memory/upload_file_spec.rb +0 -55
  221. data/spec/requests/usage_spec.rb +0 -27
  222. data/spec/resources/custom_instruction_spec.rb +0 -32
  223. data/spec/resources/glossary_spec.rb +0 -38
  224. data/spec/resources/language_pair_spec.rb +0 -23
  225. data/spec/resources/language_spec.rb +0 -45
  226. data/spec/resources/style_rule_spec.rb +0 -68
  227. data/spec/resources/text_spec.rb +0 -23
  228. data/spec/resources/translation_memory_export_spec.rb +0 -38
  229. data/spec/resources/translation_memory_import_spec.rb +0 -30
  230. data/spec/resources/translation_memory_job_spec.rb +0 -109
  231. data/spec/resources/translation_memory_segments_spec.rb +0 -78
  232. data/spec/resources/translation_memory_spec.rb +0 -52
  233. data/spec/resources/usage_spec.rb +0 -35
  234. data/spec/support/live_mock_server.rb +0 -12
  235. data/spec/support/managed_glossary.rb +0 -65
  236. data/spec/support/managed_style_rule.rb +0 -17
  237. data/spec/support/managed_translation_memory.rb +0 -55
@@ -0,0 +1,57 @@
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 Requests
8
+ class Correct < Base
9
+ include WriteImprovements
10
+
11
+ attr_reader :text, :target_lang
12
+
13
+ def initialize(api, text, target_lang = nil, options = {}, additional_headers = {})
14
+ super(api, options, additional_headers)
15
+ @text = text
16
+ @target_lang = target_lang
17
+ validate_text!
18
+ end
19
+
20
+ def request
21
+ payload = { text: text_arrayified }
22
+ payload[:target_lang] = target_lang unless target_lang.nil?
23
+ build_texts(*execute_request_with_retries(post_request(payload)))
24
+ end
25
+
26
+ def details
27
+ "HTTP Headers: #{loggable_headers}\nPayload #{{ text: text, target_lang: target_lang }}"
28
+ end
29
+
30
+ def to_s
31
+ "POST #{uri.request_uri}"
32
+ end
33
+
34
+ private
35
+
36
+ def text_arrayified
37
+ text.is_a?(Array) ? text : [text]
38
+ end
39
+
40
+ def validate_text!
41
+ return unless text_arrayified.empty? || text_arrayified.any? { |t| t.to_s.empty? }
42
+
43
+ raise ArgumentError,
44
+ '`text` must be a non-empty string or a non-empty array of non-empty strings'
45
+ end
46
+
47
+ def build_texts(request, response)
48
+ texts = build_improvements(text_arrayified.size, request, response)
49
+ text.is_a?(Array) ? texts : texts.first
50
+ end
51
+
52
+ def path
53
+ 'write/correct'
54
+ end
55
+ end
56
+ end
57
+ end
@@ -22,7 +22,7 @@ module DeepL
22
22
  end
23
23
 
24
24
  def details
25
- "HTTP Headers: #{headers}\nPayload #{{ document_key: document_key }}"
25
+ "HTTP Headers: #{loggable_headers}\nPayload #{{ document_key: document_key }}"
26
26
  end
27
27
 
28
28
  def to_s
@@ -31,12 +31,20 @@ module DeepL
31
31
 
32
32
  private
33
33
 
34
+ # Binary mode: text mode would convert newlines on Windows and transcode the bytes when
35
+ # Encoding.default_internal is set.
34
36
  def extract_file(_request, response)
35
- File.write(@output_file, response.body)
37
+ File.binwrite(@output_file, response.body)
38
+ end
39
+
40
+ def validate_response!(response, request = nil)
41
+ raise Exceptions::DocumentNotReadyError.new(response, request) if response.code == '503'
42
+
43
+ super
36
44
  end
37
45
 
38
46
  def path
39
- "document/#{document_id}/result"
47
+ "document/#{escape_path_segment(document_id)}/result"
40
48
  end
41
49
  end
42
50
  end
@@ -21,7 +21,7 @@ module DeepL
21
21
  end
22
22
 
23
23
  def details
24
- "HTTP Headers: #{headers}\nPayload #{{ document_key: document_key }}"
24
+ "HTTP Headers: #{loggable_headers}\nPayload #{{ document_key: document_key }}"
25
25
  end
26
26
 
27
27
  def to_s
@@ -36,7 +36,7 @@ module DeepL
36
36
  end
37
37
 
38
38
  def path
39
- "document/#{document_id}"
39
+ "document/#{escape_path_segment(document_id)}"
40
40
  end
41
41
  end
42
42
  end
@@ -9,11 +9,12 @@ module DeepL
9
9
  class Upload < Base
10
10
  attr_reader :input_file_path, :source_lang, :target_lang, :filename
11
11
 
12
- SUPPORTED_OPTIONS = %w[formality glossary_id output_format].freeze
12
+ SUPPORTED_OPTIONS = %w[formality glossary_id output_format enable_watermark].freeze
13
13
 
14
14
  def initialize(api, input_file_path, source_lang, target_lang, filename = nil, # rubocop:disable Metrics/ParameterLists
15
15
  options = {}, additional_headers = {})
16
- super(api, options, additional_headers)
16
+ # Options set to nil are unset and not sent.
17
+ super(api, options.compact, additional_headers)
17
18
  @input_file_path = input_file_path
18
19
  @source_lang = source_lang
19
20
  @target_lang = target_lang
@@ -30,7 +31,7 @@ module DeepL
30
31
  end
31
32
 
32
33
  def details
33
- "HTTP Headers: #{headers}\nPayload #{[
34
+ "HTTP Headers: #{loggable_headers}\nPayload #{[
34
35
  ['file', "File at #{input_file_path} opened in binary mode"],
35
36
  ['source_lang', source_lang], ['target_lang', target_lang], ['filename', filename]
36
37
  ]}"
@@ -43,10 +44,9 @@ module DeepL
43
44
  private
44
45
 
45
46
  def build_base_form_data(input_file)
46
- form_data = [
47
- ['file', input_file], ['source_lang', source_lang],
48
- ['target_lang', target_lang]
49
- ]
47
+ form_data = [['file', input_file]]
48
+ form_data.push(['source_lang', source_lang]) unless source_lang.nil?
49
+ form_data.push(['target_lang', target_lang])
50
50
  filename_param = filename || File.basename(input_file_path)
51
51
  form_data.push(['filename', filename_param]) unless filename_param.nil?
52
52
  add_extra_options_to_form(form_data)
@@ -56,7 +56,9 @@ module DeepL
56
56
  def add_extra_options_to_form(form_data)
57
57
  add_supported_options_to_form(form_data)
58
58
  add_glossary_ids_to_form(form_data)
59
+ add_glossary_to_form(form_data)
59
60
  add_style_rule_to_form(form_data)
61
+ add_style_profile_to_form(form_data)
60
62
  add_translation_memory_to_form(form_data)
61
63
  add_translation_memory_threshold_to_form(form_data)
62
64
  end
@@ -66,10 +68,22 @@ module DeepL
66
68
  # comma-separated `glossary_ids` field. Requires `source_lang`, cannot be combined with
67
69
  # the singular `glossary_id` option, and allows at most 5 IDs.
68
70
  def add_glossary_ids_to_form(form_data)
69
- glossary_ids = build_glossary_ids_param(source_lang)
71
+ glossary_ids = build_glossary_ids_param
72
+ # Text translation detects the source language for glossaries; documents do not.
73
+ if !glossary_ids.nil? && source_lang.nil?
74
+ raise ArgumentError, 'The `glossary_ids` option requires `source_lang` to be set'
75
+ end
76
+
70
77
  form_data.push(['glossary_ids', glossary_ids.join(',')]) unless glossary_ids.nil?
71
78
  end
72
79
 
80
+ # Resolves the `glossary` option (an ID string, a `DeepL::Resources::Glossary` or a
81
+ # `DeepL::Resources::MultilingualGlossary`) and appends it as a `glossary_id` field.
82
+ def add_glossary_to_form(form_data)
83
+ glossary_id = build_glossary_param
84
+ form_data.push(['glossary_id', glossary_id]) unless glossary_id.nil?
85
+ end
86
+
73
87
  # Serializes the `style_rule` option and appends it to the form data as a `style_id`
74
88
  # field. Mirrors text translation: accepts either a style rule ID string or a
75
89
  # `DeepL::Resources::StyleRule` object.
@@ -77,10 +91,24 @@ module DeepL
77
91
  return unless option?(:style_rule)
78
92
 
79
93
  rule = delete_option(:style_rule)
94
+ return if rule.nil?
95
+
80
96
  style_id = rule.is_a?(DeepL::Resources::StyleRule) ? rule.style_id : rule
81
97
  form_data.push(['style_id', style_id.to_s])
82
98
  end
83
99
 
100
+ # Serializes the `style_profile` option (a style profile ID string or a
101
+ # `DeepL::Resources::StyleProfile` object) as a `style_profile_id` field.
102
+ def add_style_profile_to_form(form_data)
103
+ return unless option?(:style_profile)
104
+
105
+ profile = delete_option(:style_profile)
106
+ return if profile.nil?
107
+
108
+ profile = profile.style_profile_id if profile.is_a?(DeepL::Resources::StyleProfile)
109
+ form_data.push(['style_profile_id', profile.to_s])
110
+ end
111
+
84
112
  # Serializes the `translation_memory` option and appends it to the form data as a
85
113
  # `translation_memory_id` field. Mirrors text translation: the `translation_memory`
86
114
  # option accepts either a translation memory ID string or a
@@ -89,6 +117,8 @@ module DeepL
89
117
  return unless option?(:translation_memory)
90
118
 
91
119
  tm = delete_option(:translation_memory)
120
+ return if tm.nil?
121
+
92
122
  tm_id = tm.is_a?(DeepL::Resources::TranslationMemory) ? tm.translation_memory_id : tm
93
123
  form_data.push(['translation_memory_id', tm_id.to_s])
94
124
  end
@@ -99,20 +129,23 @@ module DeepL
99
129
  return unless option?(:translation_memory_threshold)
100
130
 
101
131
  threshold = delete_option(:translation_memory_threshold)
132
+ return if threshold.nil?
133
+
102
134
  form_data.push(['translation_memory_threshold', threshold.to_s])
103
135
  end
104
136
 
105
137
  def add_supported_options_to_form(form_data)
106
138
  SUPPORTED_OPTIONS.each do |option_name|
107
139
  option_value = option(option_name)
108
- form_data.push([option_name, option_value]) unless option_value.nil?
140
+ form_data.push([option_name, option_value.to_s]) unless option_value.nil?
109
141
  end
110
142
  end
111
143
 
112
144
  def build_doc_handle(request, response)
113
145
  parsed_response = JSON.parse(response.body)
114
146
  Resources::DocumentHandle.new(parsed_response['document_id'],
115
- parsed_response['document_key'], request, response)
147
+ parsed_response['document_key'], request, response,
148
+ api: api)
116
149
  end
117
150
 
118
151
  def path
@@ -27,7 +27,7 @@ module DeepL
27
27
  end
28
28
 
29
29
  def details
30
- "HTTP Headers: #{headers}\nPayload #{{
30
+ "HTTP Headers: #{loggable_headers}\nPayload #{{
31
31
  name: name, source_lang: source_lang, target_lang: target_lang, entries: entries_to_tsv,
32
32
  entries_format: entries_format
33
33
  }}"
@@ -42,7 +42,11 @@ module DeepL
42
42
  def entries_to_tsv
43
43
  return entries if entries.is_a?(String)
44
44
 
45
- entries.reduce('') { |tsv, entry| "#{tsv}#{entry.first}\t#{entry.last}\n" }
45
+ entries.reduce('') do |tsv, entry|
46
+ source = Utils::TermValidator.validate!(entry.first)
47
+ target = Utils::TermValidator.validate!(entry.last)
48
+ "#{tsv}#{source}\t#{target}\n"
49
+ end
46
50
  end
47
51
 
48
52
  def build_glossary(request, response)
@@ -23,14 +23,30 @@ module DeepL
23
23
 
24
24
  def build_language_pair_list(request, response)
25
25
  data = JSON.parse(response.body)
26
- data['supported_languages'].map do |language_pair|
27
- Resources::LanguagePair.new(language_pair['source_lang'], language_pair['target_lang'],
28
- request, response)
26
+ sources = langs_where(data, 'usable_as_source')
27
+ targets = langs_where(data, 'usable_as_target')
28
+
29
+ sources.product(targets).each_with_object([]) do |(source, target), pairs|
30
+ next if source == target
31
+
32
+ pairs << Resources::LanguagePair.new(source, target, request, response)
29
33
  end
30
34
  end
31
35
 
36
+ def langs_where(data, usable_key)
37
+ data.select { |language| language[usable_key] }.map { |language| language['lang'] }
38
+ end
39
+
40
+ def uri
41
+ @uri ||= URI("#{host}/v3/#{path}?resource=glossary")
42
+ end
43
+
44
+ def get_request # rubocop:disable Naming/AccessorMethodName
45
+ Net::HTTP::Get.new(uri.request_uri, add_json_content_type(headers))
46
+ end
47
+
32
48
  def path
33
- 'glossary-language-pairs'
49
+ 'languages'
34
50
  end
35
51
  end
36
52
  end
@@ -0,0 +1,41 @@
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 file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ class LanguageResources < Base
9
+ def initialize(api, options = {})
10
+ super
11
+ end
12
+
13
+ def request
14
+ build_language_resource_list(*execute_request_with_retries(get_request))
15
+ end
16
+
17
+ def to_s
18
+ "GET #{uri.request_uri}"
19
+ end
20
+
21
+ private
22
+
23
+ def build_language_resource_list(request, response)
24
+ data = JSON.parse(response.body)
25
+ data.map { |resource| Resources::LanguageResource.new(resource, request, response) }
26
+ end
27
+
28
+ def uri
29
+ @uri ||= URI("#{host}/v3/#{path}")
30
+ end
31
+
32
+ def get_request # rubocop:disable Naming/AccessorMethodName
33
+ Net::HTTP::Get.new(uri.request_uri, add_json_content_type(headers))
34
+ end
35
+
36
+ def path
37
+ 'languages/resources'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -6,8 +6,15 @@
6
6
  module DeepL
7
7
  module Requests
8
8
  class Languages < Base
9
+ SUPPORTED_TYPES = %w[source target].freeze
10
+
9
11
  def initialize(api, options = {})
10
12
  super
13
+ @type = (delete_option(:type) || :source).to_s
14
+ return if SUPPORTED_TYPES.include?(@type)
15
+
16
+ raise Exceptions::ValidationError, "Unsupported `type` option: #{@type.inspect} " \
17
+ "(supported: #{SUPPORTED_TYPES.join(', ')})"
11
18
  end
12
19
 
13
20
  def request
@@ -20,15 +27,32 @@ module DeepL
20
27
 
21
28
  private
22
29
 
30
+ def target?
31
+ @type == 'target'
32
+ end
33
+
23
34
  def build_languages(request, response)
24
35
  data = JSON.parse(response.body)
25
- data.map do |language|
26
- Resources::Language.new(language['language'], language['name'],
27
- language['supports_formality'],
28
- request, response)
36
+ usable_key = target? ? 'usable_as_target' : 'usable_as_source'
37
+ data.select { |language| language[usable_key] }.map do |language|
38
+ build_language(language, request, response)
29
39
  end
30
40
  end
31
41
 
42
+ def build_language(language, request, response)
43
+ supports_formality = target? ? language.fetch('features', {}).to_h.key?('formality') : nil
44
+ Resources::Language.new(language['lang'].upcase, language['name'], supports_formality,
45
+ request, response, client: api.client?)
46
+ end
47
+
48
+ def uri
49
+ @uri ||= URI("#{host}/v3/#{path}?resource=translate_text")
50
+ end
51
+
52
+ def get_request # rubocop:disable Naming/AccessorMethodName
53
+ Net::HTTP::Get.new(uri.request_uri, add_json_content_type(headers))
54
+ end
55
+
32
56
  def path
33
57
  'languages'
34
58
  end
@@ -0,0 +1,54 @@
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 file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ class LanguagesForResource < Base
9
+ def initialize(api, resource, options = {})
10
+ super(api, options)
11
+ @resource = resource.to_s
12
+ raise ArgumentError, 'resource must not be empty' if @resource.empty?
13
+ end
14
+
15
+ def request
16
+ build_language_support_list(*execute_request_with_retries(get_request))
17
+ end
18
+
19
+ def to_s
20
+ "GET #{uri.request_uri}"
21
+ end
22
+
23
+ private
24
+
25
+ def build_language_support_list(request, response)
26
+ data = JSON.parse(response.body)
27
+ data.map { |language| Resources::LanguageSupport.new(language, request, response) }
28
+ end
29
+
30
+ def uri
31
+ @uri ||= begin
32
+ base_uri = URI("#{host}/v3/#{path}")
33
+ base_uri.query = build_query_string
34
+ base_uri
35
+ end
36
+ end
37
+
38
+ # include is sent as repeated include=<value> parameters
39
+ def build_query_string
40
+ params = [['resource', @resource]]
41
+ Array(option(:include)).each { |value| params << ['include', value.to_s] }
42
+ encode_query(params)
43
+ end
44
+
45
+ def get_request # rubocop:disable Naming/AccessorMethodName
46
+ Net::HTTP::Get.new(uri.request_uri, add_json_content_type(headers))
47
+ end
48
+
49
+ def path
50
+ 'languages'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
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 Requests
8
+ module MultilingualGlossary
9
+ class Create < Base
10
+ include Helpers
11
+
12
+ attr_reader :name, :dictionaries
13
+
14
+ def initialize(api, name, dictionaries, options = {})
15
+ super(api, options)
16
+ @name = name
17
+ @dictionaries = dictionaries.map { |dictionary| dictionary_payload(dictionary) }
18
+ end
19
+
20
+ def request
21
+ payload = { name: name, dictionaries: dictionaries }
22
+ build_glossary(*execute_request_with_retries(post_request(payload)))
23
+ end
24
+
25
+ def to_s
26
+ "POST #{uri.request_uri}"
27
+ end
28
+
29
+ private
30
+
31
+ def path
32
+ 'glossaries'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
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 Requests
8
+ module MultilingualGlossary
9
+ class Destroy < Base
10
+ include Helpers
11
+
12
+ attr_reader :id
13
+
14
+ def initialize(api, id, options = {})
15
+ super(api, options)
16
+ @id = glossary_id(id)
17
+ end
18
+
19
+ def request
20
+ execute_request_with_retries(delete_request)
21
+ nil
22
+ end
23
+
24
+ def to_s
25
+ "DELETE #{uri.request_uri}"
26
+ end
27
+
28
+ private
29
+
30
+ def path
31
+ glossary_path(id)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,51 @@
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 Requests
8
+ module MultilingualGlossary
9
+ class DestroyDictionary < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :source_lang, :target_lang
13
+
14
+ def initialize(api, id, source_lang, target_lang, options = {})
15
+ super(api, options)
16
+ @id = glossary_id(id)
17
+ @source_lang = source_lang
18
+ @target_lang = target_lang
19
+ end
20
+
21
+ def request
22
+ execute_request_with_retries(delete_request)
23
+ nil
24
+ end
25
+
26
+ def to_s
27
+ "DELETE #{uri.request_uri}"
28
+ end
29
+
30
+ private
31
+
32
+ def delete_request
33
+ http_headers = add_json_content_type(headers)
34
+ Net::HTTP::Delete.new(uri.request_uri, http_headers)
35
+ end
36
+
37
+ def uri
38
+ @uri ||= begin
39
+ base_uri = URI("#{host}/v3/#{path}")
40
+ base_uri.query = encode_query(source_lang: source_lang, target_lang: target_lang)
41
+ base_uri
42
+ end
43
+ end
44
+
45
+ def path
46
+ glossary_path(id, 'dictionaries')
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,66 @@
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 Requests
8
+ module MultilingualGlossary
9
+ class Entries < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :source_lang, :target_lang
13
+
14
+ def initialize(api, id, source_lang, target_lang, options = {})
15
+ super(api, options)
16
+ @id = glossary_id(id)
17
+ @source_lang = source_lang
18
+ @target_lang = target_lang
19
+ end
20
+
21
+ def request
22
+ build_entries(*execute_request_with_retries(get_request))
23
+ end
24
+
25
+ def to_s
26
+ "GET #{uri.request_uri}"
27
+ end
28
+
29
+ private
30
+
31
+ def get_request # rubocop:disable Naming/AccessorMethodName
32
+ http_headers = add_json_content_type(headers)
33
+ Net::HTTP::Get.new(uri.request_uri, http_headers)
34
+ end
35
+
36
+ # The API wraps the dictionary in a list, which is empty if the glossary has no
37
+ # dictionary for the language pair.
38
+ def build_entries(request, response)
39
+ dictionary = (JSON.parse(response.body)['dictionaries'] || []).first
40
+ if dictionary.nil?
41
+ raise Exceptions::NotFound.new(
42
+ response, request,
43
+ "Glossary #{id} has no dictionary for #{source_lang} -> #{target_lang}"
44
+ )
45
+ end
46
+
47
+ Resources::MultilingualGlossaryDictionaryEntries.from_response(
48
+ dictionary, request, response
49
+ )
50
+ end
51
+
52
+ def uri
53
+ @uri ||= begin
54
+ base_uri = URI("#{host}/v3/#{path}")
55
+ base_uri.query = encode_query(source_lang: source_lang, target_lang: target_lang)
56
+ base_uri
57
+ end
58
+ end
59
+
60
+ def path
61
+ glossary_path(id, 'entries')
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,35 @@
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 Requests
8
+ module MultilingualGlossary
9
+ class Find < Base
10
+ include Helpers
11
+
12
+ attr_reader :id
13
+
14
+ def initialize(api, id, options = {})
15
+ super(api, options)
16
+ @id = glossary_id(id)
17
+ end
18
+
19
+ def request
20
+ build_glossary(*execute_request_with_retries(get_request))
21
+ end
22
+
23
+ def to_s
24
+ "GET #{uri.request_uri}"
25
+ end
26
+
27
+ private
28
+
29
+ def path
30
+ glossary_path(id)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end