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,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 StyleProfile
9
+ class Create < Base
10
+ attr_reader :name, :fields
11
+
12
+ def initialize(api, name, fields, options = {})
13
+ super(api, options)
14
+ @name = name
15
+ @fields = fields
16
+ end
17
+
18
+ def request
19
+ payload = { name: name }.merge(optional_fields_payload(fields))
20
+ build_style_profile(*execute_request_with_retries(post_request(payload)))
21
+ end
22
+
23
+ def to_s
24
+ "POST #{uri.request_uri}"
25
+ end
26
+
27
+ private
28
+
29
+ def path
30
+ 'style_profiles'
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
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 StyleProfile
9
+ class Destroy < Base
10
+ attr_reader :id
11
+
12
+ def initialize(api, id, options = {})
13
+ super(api, options)
14
+ @id = style_profile_id(id)
15
+ end
16
+
17
+ def request
18
+ execute_request_with_retries(delete_request)
19
+ id
20
+ end
21
+
22
+ def to_s
23
+ "DELETE #{uri.request_uri}"
24
+ end
25
+
26
+ private
27
+
28
+ def path
29
+ "style_profiles/#{escaped_id}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
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 StyleProfile
9
+ class Find < Base
10
+ attr_reader :id
11
+
12
+ def initialize(api, id, options = {})
13
+ super(api, options)
14
+ @id = style_profile_id(id)
15
+ end
16
+
17
+ def request
18
+ build_style_profile(*execute_request_with_retries(get_request))
19
+ end
20
+
21
+ def to_s
22
+ "GET #{uri.request_uri}"
23
+ end
24
+
25
+ private
26
+
27
+ def path
28
+ "style_profiles/#{escaped_id}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
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 StyleProfile
9
+ class List < Base
10
+ SUPPORTED_OPTIONS = %w[page page_size].freeze
11
+
12
+ def request
13
+ build_style_profile_list(*execute_request_with_retries(get_request))
14
+ end
15
+
16
+ def to_s
17
+ "GET #{uri.request_uri}"
18
+ end
19
+
20
+ private
21
+
22
+ def build_style_profile_list(request, response)
23
+ profiles = JSON.parse(response.body)['style_profiles']
24
+ return [] unless profiles.is_a?(Array)
25
+
26
+ profiles.grep(Hash).map do |profile|
27
+ Resources::StyleProfile.new(profile, request, response)
28
+ end
29
+ end
30
+
31
+ def query_params
32
+ SUPPORTED_OPTIONS.each_with_object({}) do |option_name, params|
33
+ params[option_name] = option(option_name).to_s if option?(option_name)
34
+ end
35
+ end
36
+
37
+ def path
38
+ 'style_profiles'
39
+ end
40
+ end
41
+ end
42
+ end
43
+ 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 StyleProfile
9
+ class Update < Base
10
+ attr_reader :id, :fields
11
+
12
+ def initialize(api, id, fields, options = {})
13
+ super(api, options)
14
+ @id = style_profile_id(id)
15
+ @fields = fields
16
+ end
17
+
18
+ def request
19
+ payload = optional_fields_payload(fields)
20
+ build_style_profile(*execute_request_with_retries(patch_request(payload)))
21
+ end
22
+
23
+ def to_s
24
+ "PATCH #{uri.request_uri}"
25
+ end
26
+
27
+ private
28
+
29
+ def path
30
+ "style_profiles/#{escaped_id}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -8,7 +8,7 @@ module DeepL
8
8
  module StyleRule
9
9
  class Create < Base
10
10
  def initialize(api, name, language, options = {})
11
- super(api, options)
11
+ super(api, options.dup)
12
12
  @name = name
13
13
  @language = language
14
14
  @configured_rules = delete_option(:configured_rules)
@@ -7,9 +7,11 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class CreateCustomInstruction < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, label, prompt, source_language = nil, options = {})
11
13
  super(api, options)
12
- @style_id = style_id
14
+ @style_id = style_rule_id(style_id)
13
15
  @label = label
14
16
  @prompt = prompt
15
17
  @source_language = source_language
@@ -37,7 +39,7 @@ module DeepL
37
39
  end
38
40
 
39
41
  def path
40
- "style_rules/#{@style_id}/custom_instructions"
42
+ style_rule_path(@style_id, 'custom_instructions')
41
43
  end
42
44
  end
43
45
  end
@@ -7,9 +7,11 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class Destroy < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, options = {})
11
13
  super(api, options)
12
- @style_id = style_id
14
+ @style_id = style_rule_id(style_id)
13
15
  end
14
16
 
15
17
  def request
@@ -31,7 +33,7 @@ module DeepL
31
33
  end
32
34
 
33
35
  def path
34
- "style_rules/#{@style_id}"
36
+ style_rule_path(@style_id)
35
37
  end
36
38
  end
37
39
  end
@@ -7,10 +7,12 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class DestroyCustomInstruction < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, instruction_id, options = {})
11
13
  super(api, options)
12
- @style_id = style_id
13
- @instruction_id = instruction_id
14
+ @style_id = style_rule_id(style_id)
15
+ @instruction_id = custom_instruction_id(instruction_id)
14
16
  end
15
17
 
16
18
  def request
@@ -32,7 +34,7 @@ module DeepL
32
34
  end
33
35
 
34
36
  def path
35
- "style_rules/#{@style_id}/custom_instructions/#{@instruction_id}"
37
+ style_rule_path(@style_id, 'custom_instructions', @instruction_id)
36
38
  end
37
39
  end
38
40
  end
@@ -7,9 +7,11 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class Find < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, options = {})
11
13
  super(api, options)
12
- @style_id = style_id
14
+ @style_id = style_rule_id(style_id)
13
15
  end
14
16
 
15
17
  def request
@@ -32,7 +34,7 @@ module DeepL
32
34
  end
33
35
 
34
36
  def path
35
- "style_rules/#{@style_id}"
37
+ style_rule_path(@style_id)
36
38
  end
37
39
  end
38
40
  end
@@ -7,10 +7,12 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class FindCustomInstruction < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, instruction_id, options = {})
11
13
  super(api, options)
12
- @style_id = style_id
13
- @instruction_id = instruction_id
14
+ @style_id = style_rule_id(style_id)
15
+ @instruction_id = custom_instruction_id(instruction_id)
14
16
  end
15
17
 
16
18
  def request
@@ -33,7 +35,7 @@ module DeepL
33
35
  end
34
36
 
35
37
  def path
36
- "style_rules/#{@style_id}/custom_instructions/#{@instruction_id}"
38
+ style_rule_path(@style_id, 'custom_instructions', @instruction_id)
37
39
  end
38
40
  end
39
41
  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 StyleRule
9
+ # IDs and paths shared by the style rule requests. A style rule list and a custom
10
+ # instruction are given as their ID or as the resource object.
11
+ module Helpers
12
+ private
13
+
14
+ def style_rule_id(style_rule)
15
+ return style_rule.style_id if style_rule.is_a?(DeepL::Resources::StyleRule)
16
+
17
+ style_rule
18
+ end
19
+
20
+ def custom_instruction_id(instruction)
21
+ return instruction.id if instruction.is_a?(DeepL::Resources::CustomInstruction)
22
+
23
+ instruction
24
+ end
25
+
26
+ # The path of a style rule list, with its ID and the custom instruction ID URL-encoded.
27
+ def style_rule_path(style_id, suffix = nil, instruction_id = nil)
28
+ segments = ["style_rules/#{escape_path_segment(style_id)}", suffix]
29
+ segments << escape_path_segment(instruction_id) unless instruction_id.nil?
30
+ segments.compact.join('/')
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -47,7 +47,7 @@ module DeepL
47
47
  params['page'] = option(:page).to_s if option?(:page)
48
48
  params['page_size'] = option(:page_size).to_s if option?(:page_size)
49
49
  params['detailed'] = option(:detailed).to_s.downcase if option?(:detailed)
50
- URI.encode_www_form(params)
50
+ encode_query(params)
51
51
  end
52
52
 
53
53
  def path
@@ -7,9 +7,11 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class Update < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, name, options = {})
11
13
  super(api, options)
12
- @style_id = style_id
14
+ @style_id = style_rule_id(style_id)
13
15
  @name = name
14
16
  end
15
17
 
@@ -34,7 +36,7 @@ module DeepL
34
36
  end
35
37
 
36
38
  def path
37
- "style_rules/#{@style_id}"
39
+ style_rule_path(@style_id)
38
40
  end
39
41
  end
40
42
  end
@@ -7,9 +7,11 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class UpdateConfiguredRules < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, configured_rules, options = {})
11
13
  super(api, options)
12
- @style_id = style_id
14
+ @style_id = style_rule_id(style_id)
13
15
  @configured_rules = configured_rules
14
16
  end
15
17
 
@@ -33,7 +35,7 @@ module DeepL
33
35
  end
34
36
 
35
37
  def path
36
- "style_rules/#{@style_id}/configured_rules"
38
+ style_rule_path(@style_id, 'configured_rules')
37
39
  end
38
40
  end
39
41
  end
@@ -7,11 +7,13 @@ module DeepL
7
7
  module Requests
8
8
  module StyleRule
9
9
  class UpdateCustomInstruction < Base
10
+ include Helpers
11
+
10
12
  def initialize(api, style_id, instruction_id, label, prompt, # rubocop:disable Metrics/ParameterLists
11
13
  source_language = nil, options = {})
12
14
  super(api, options)
13
- @style_id = style_id
14
- @instruction_id = instruction_id
15
+ @style_id = style_rule_id(style_id)
16
+ @instruction_id = custom_instruction_id(instruction_id)
15
17
  @label = label
16
18
  @prompt = prompt
17
19
  @source_language = source_language
@@ -39,7 +41,7 @@ module DeepL
39
41
  end
40
42
 
41
43
  def path
42
- "style_rules/#{@style_id}/custom_instructions/#{@instruction_id}"
44
+ style_rule_path(@style_id, 'custom_instructions', @instruction_id)
43
45
  end
44
46
  end
45
47
  end
@@ -5,7 +5,7 @@
5
5
 
6
6
  module DeepL
7
7
  module Requests
8
- class Translate < Base
8
+ class Translate < Base # rubocop:disable Metrics/ClassLength
9
9
  STRING_TO_BOOLEAN_MAP = { '1' => true, '0' => false }.freeze
10
10
  BOOLEAN_TO_STRING_MAP = { true => '1', false => '0' }.freeze
11
11
  STRING_TO_BOOLEAN_CONVERSION = ->(value) { STRING_TO_BOOLEAN_MAP[value] }
@@ -17,6 +17,10 @@ module DeepL
17
17
  (value.is_a?(Array) ? value : value.split(','))
18
18
  end
19
19
  }.freeze
20
+ # A custom instruction may contain commas, so a single string is one instruction.
21
+ STRING_TO_SINGLE_ELEMENT_ARRAY_CONVERSION = lambda { |value|
22
+ value.is_a?(String) ? [value] : value
23
+ }.freeze
20
24
  OPTIONS_CONVERSIONS = {
21
25
  split_sentences: BOOLEAN_TO_STRING_CONVERSION,
22
26
  preserve_formatting: STRING_TO_BOOLEAN_CONVERSION,
@@ -24,14 +28,15 @@ module DeepL
24
28
  splitting_tags: STRING_TO_ARRAY_CONVERSION,
25
29
  non_splitting_tags: STRING_TO_ARRAY_CONVERSION,
26
30
  ignore_tags: STRING_TO_ARRAY_CONVERSION,
27
- custom_instructions: STRING_TO_ARRAY_CONVERSION
31
+ custom_instructions: STRING_TO_SINGLE_ELEMENT_ARRAY_CONVERSION
28
32
  }.freeze
29
33
 
30
34
  attr_reader :text, :source_lang, :target_lang, :ignore_tags, :splitting_tags,
31
35
  :non_splitting_tags, :model_type, :custom_instructions, :tag_handling_version
32
36
 
33
37
  def initialize(api, text, source_lang, target_lang, options = {}, additional_headers = {})
34
- super(api, options, additional_headers)
38
+ # Options set to nil are unset and not sent.
39
+ super(api, options.compact, additional_headers)
35
40
  @text = text
36
41
  @source_lang = source_lang
37
42
  @target_lang = target_lang
@@ -41,39 +46,58 @@ module DeepL
41
46
 
42
47
  def request # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
43
48
  text_arrayified = text.is_a?(Array) ? text : [text]
44
- payload = { text: text_arrayified, source_lang: source_lang, target_lang: target_lang }
49
+ payload = { text: text_arrayified, target_lang: target_lang, show_billed_characters: true }
50
+ payload[:source_lang] = source_lang unless source_lang.nil?
45
51
 
46
52
  if option?(:style_rule)
47
53
  style_rule = delete_option(:style_rule)
48
- payload[:style_id] = if style_rule.is_a?(DeepL::Resources::StyleRule)
49
- style_rule.style_id
50
- else
51
- style_rule.to_s
52
- end
54
+ unless style_rule.nil?
55
+ payload[:style_id] = if style_rule.is_a?(DeepL::Resources::StyleRule)
56
+ style_rule.style_id
57
+ else
58
+ style_rule.to_s
59
+ end
60
+ end
61
+ end
62
+
63
+ if option?(:style_profile)
64
+ profile = delete_option(:style_profile)
65
+ unless profile.nil?
66
+ payload[:style_profile_id] = if profile.is_a?(DeepL::Resources::StyleProfile)
67
+ profile.style_profile_id
68
+ else
69
+ profile.to_s
70
+ end
71
+ end
53
72
  end
54
73
 
55
74
  if option?(:translation_memory)
56
75
  tm = delete_option(:translation_memory)
57
- payload[:translation_memory_id] = if tm.is_a?(DeepL::Resources::TranslationMemory)
58
- tm.translation_memory_id
59
- else
60
- tm.to_s
61
- end
76
+ unless tm.nil?
77
+ payload[:translation_memory_id] = if tm.is_a?(DeepL::Resources::TranslationMemory)
78
+ tm.translation_memory_id
79
+ else
80
+ tm.to_s
81
+ end
82
+ end
62
83
  end
63
84
 
64
85
  if option?(:translation_memory_threshold)
65
- payload[:translation_memory_threshold] = delete_option(:translation_memory_threshold)
86
+ threshold = delete_option(:translation_memory_threshold)
87
+ payload[:translation_memory_threshold] = threshold unless threshold.nil?
66
88
  end
67
89
 
68
- glossary_ids = build_glossary_ids_param(source_lang)
90
+ glossary_ids = build_glossary_ids_param
69
91
  payload[:glossary_ids] = glossary_ids unless glossary_ids.nil?
92
+ glossary_id = build_glossary_param
93
+ payload[:glossary_id] = glossary_id unless glossary_id.nil?
70
94
 
71
95
  build_texts(*execute_request_with_retries(post_request(payload)))
72
96
  end
73
97
 
74
98
  def details
75
- "HTTP Headers: #{headers}\nPayload #{{ text: text, source_lang: source_lang,
76
- target_lang: target_lang }}"
99
+ "HTTP Headers: #{loggable_headers}\nPayload #{{ text: text, source_lang: source_lang,
100
+ target_lang: target_lang }}"
77
101
  end
78
102
 
79
103
  def to_s
@@ -94,13 +118,21 @@ module DeepL
94
118
  data = JSON.parse(response.body)
95
119
 
96
120
  texts = data['translations'].map do |translation|
97
- Resources::Text.new(translation['text'], translation['detected_source_language'],
98
- translation['model_type_used'], request, response)
121
+ Resources::Text.new(
122
+ translation['text'], translation['detected_source_language'],
123
+ translation['model_type_used'], request, response,
124
+ Utils::NumberParser.optional_count(translation['billed_characters']),
125
+ optional_string(translation['tag_handling_version'])
126
+ )
99
127
  end
100
128
 
101
129
  texts.size == 1 ? texts.first : texts
102
130
  end
103
131
 
132
+ def optional_string(value)
133
+ value if value.is_a?(String)
134
+ end
135
+
104
136
  def path
105
137
  'translate'
106
138
  end
@@ -24,10 +24,7 @@ module DeepL
24
24
  end
25
25
 
26
26
  def build_query_string
27
- # encode_www_form encodes a space as `+`, which is correct for a form body but not for
28
- # a URI query string. A literal `+` is already escaped as %2B, so every remaining `+`
29
- # is a space.
30
- URI.encode_www_form(query_params).gsub('+', '%20')
27
+ encode_query(query_params)
31
28
  end
32
29
 
33
30
  def query_params
@@ -33,7 +33,7 @@ module DeepL
33
33
  end
34
34
 
35
35
  def path
36
- "translation_memories/#{translation_memory_id}/export"
36
+ "translation_memories/#{escape_path_segment(translation_memory_id)}/export"
37
37
  end
38
38
  end
39
39
  end
@@ -22,7 +22,7 @@ module DeepL
22
22
  end
23
23
 
24
24
  def details
25
- "HTTP Headers: #{headers}\nPayload #{payload}"
25
+ "HTTP Headers: #{loggable_headers}\nPayload #{payload}"
26
26
  end
27
27
 
28
28
  def to_s
@@ -29,7 +29,7 @@ module DeepL
29
29
  end
30
30
 
31
31
  def path
32
- "translation_memories/#{translation_memory_id}"
32
+ "translation_memories/#{escape_path_segment(translation_memory_id)}"
33
33
  end
34
34
  end
35
35
  end
@@ -28,6 +28,10 @@ module DeepL
28
28
 
29
29
  def extract_file(_request, response)
30
30
  File.binwrite(@output_file, response.body)
31
+ rescue SystemCallError, IOError => e
32
+ raise e unless api.client?
33
+
34
+ raise Exceptions::Error, "Could not write the export to #{@output_file}: #{e.message}"
31
35
  end
32
36
  end
33
37
  end
@@ -30,7 +30,7 @@ module DeepL
30
30
  end
31
31
 
32
32
  def path
33
- "translation_memories/#{translation_memory_id}"
33
+ "translation_memories/#{escape_path_segment(translation_memory_id)}"
34
34
  end
35
35
  end
36
36
  end
@@ -30,7 +30,7 @@ module DeepL
30
30
  end
31
31
 
32
32
  def path
33
- "translation_memories/jobs/#{job_id}"
33
+ "translation_memories/jobs/#{escape_path_segment(job_id)}"
34
34
  end
35
35
  end
36
36
  end
@@ -32,7 +32,8 @@ module DeepL
32
32
 
33
33
  def query_params
34
34
  SUPPORTED_OPTIONS.each_with_object({}) do |option_name, params|
35
- params[option_name] = option(option_name).to_s if option?(option_name)
35
+ value = option(option_name)
36
+ params[option_name] = value.to_s unless value.nil?
36
37
  end
37
38
  end
38
39
 
@@ -33,12 +33,13 @@ module DeepL
33
33
 
34
34
  def query_params
35
35
  SUPPORTED_OPTIONS.each_with_object({}) do |option_name, params|
36
- params[option_name] = option(option_name).to_s if option?(option_name)
36
+ value = option(option_name)
37
+ params[option_name] = value.to_s unless value.nil?
37
38
  end
38
39
  end
39
40
 
40
41
  def path
41
- "translation_memories/#{translation_memory_id}/segments"
42
+ "translation_memories/#{escape_path_segment(translation_memory_id)}/segments"
42
43
  end
43
44
  end
44
45
  end