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,63 @@
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
+ # Encoding shared by the v3 glossary requests.
10
+ module Helpers
11
+ private
12
+
13
+ def glossary_id(glossary)
14
+ return glossary.id if glossary.is_a?(DeepL::Resources::MultilingualGlossary)
15
+
16
+ glossary.to_s
17
+ end
18
+
19
+ # The path of a glossary, with its ID URL-encoded.
20
+ def glossary_path(id, suffix = nil)
21
+ ["glossaries/#{escape_path_segment(id)}", suffix].compact.join('/')
22
+ end
23
+
24
+ # Accepts a MultilingualGlossaryDictionaryEntries object or a hash with +:source_lang+,
25
+ # +:target_lang+, +:entries+ and optionally +:entries_format+ (default +tsv+). Only a CSV
26
+ # string is sent as CSV; all other entries are converted and sent as TSV.
27
+ def dictionary_payload(dictionary)
28
+ if dictionary.is_a?(DeepL::Resources::MultilingualGlossaryDictionaryEntries)
29
+ return { source_lang: dictionary.source_lang, target_lang: dictionary.target_lang,
30
+ entries: entries_to_string(dictionary.entries), entries_format: 'tsv' }
31
+ end
32
+
33
+ value = ->(key) { dictionary[key] || dictionary[key.to_s] }
34
+ entries = value.call(:entries)
35
+ # CSV content is sent as given; TSV content is parsed so its terms are checked too.
36
+ csv = value.call(:entries_format) == 'csv' && entries.is_a?(String)
37
+ { source_lang: value.call(:source_lang), target_lang: value.call(:target_lang),
38
+ entries: csv ? entries : entries_to_string(entries),
39
+ entries_format: csv ? 'csv' : 'tsv' }
40
+ end
41
+
42
+ # A hash, source/target pairs or a TSV string of them, checked and joined as TSV.
43
+ def entries_to_string(entries)
44
+ entries_class = Resources::MultilingualGlossaryDictionaryEntries
45
+ entries = entries.is_a?(String) ? entries_class.tsv_to_pairs(entries) : entries.to_a
46
+ entries_class.check_pairs!(entries).map do |entry|
47
+ source = Utils::TermValidator.validate!(entry.first)
48
+ target = Utils::TermValidator.validate!(entry.last)
49
+ "#{source}\t#{target}"
50
+ end.join("\n")
51
+ end
52
+
53
+ def build_glossary(request, response)
54
+ Resources::MultilingualGlossary.new(JSON.parse(response.body), request, response)
55
+ end
56
+
57
+ def uri
58
+ @uri ||= URI("#{host}/v3/#{path}")
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
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 List < Base
10
+ include Helpers
11
+
12
+ def initialize(api, options = {})
13
+ super
14
+ end
15
+
16
+ def request
17
+ build_glossary_list(*execute_request_with_retries(get_request))
18
+ end
19
+
20
+ def to_s
21
+ "GET #{uri.request_uri}"
22
+ end
23
+
24
+ private
25
+
26
+ def build_glossary_list(request, response)
27
+ data = JSON.parse(response.body)
28
+ data['glossaries'].map do |glossary|
29
+ Resources::MultilingualGlossary.new(glossary, request, response)
30
+ end
31
+ end
32
+
33
+ def path
34
+ 'glossaries'
35
+ end
36
+ end
37
+ end
38
+ end
39
+ 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.md file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ module MultilingualGlossary
9
+ class ReplaceDictionary < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :dictionary
13
+
14
+ def initialize(api, id, dictionary, options = {})
15
+ super(api, options)
16
+ @id = glossary_id(id)
17
+ @dictionary = dictionary_payload(dictionary)
18
+ end
19
+
20
+ def request
21
+ build_dictionary(*execute_request_with_retries(put_request(dictionary)))
22
+ end
23
+
24
+ def to_s
25
+ "PUT #{uri.request_uri}"
26
+ end
27
+
28
+ private
29
+
30
+ def build_dictionary(request, response)
31
+ data = JSON.parse(response.body)
32
+ Resources::MultilingualGlossaryDictionary.new(data, request, response)
33
+ end
34
+
35
+ def path
36
+ glossary_path(id, 'dictionaries')
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
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 Update < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :name, :dictionaries
13
+
14
+ def initialize(api, id, name, dictionaries, options = {})
15
+ super(api, options)
16
+ @id = glossary_id(id)
17
+ @name = name
18
+ @dictionaries = dictionaries&.map { |dictionary| dictionary_payload(dictionary) }
19
+ end
20
+
21
+ def request
22
+ payload = {}
23
+ payload[:name] = name unless name.nil?
24
+ payload[:dictionaries] = dictionaries unless dictionaries.nil?
25
+ build_glossary(*execute_request_with_retries(patch_request(payload)))
26
+ end
27
+
28
+ def to_s
29
+ "PATCH #{uri.request_uri}"
30
+ end
31
+
32
+ private
33
+
34
+ def path
35
+ glossary_path(id)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -6,6 +6,8 @@
6
6
  module DeepL
7
7
  module Requests
8
8
  class Rephrase < Base
9
+ include WriteImprovements
10
+
9
11
  attr_reader :text, :target_lang, :writing_style, :tone
10
12
 
11
13
  def initialize(api, text, target_lang = nil, writing_style = nil, tone = nil, options = {}, # rubocop:disable Metrics/ParameterLists
@@ -27,8 +29,8 @@ module DeepL
27
29
  end
28
30
 
29
31
  def details
30
- "HTTP Headers: #{headers}\nPayload #{{ text: text, target_lang: target_lang,
31
- writing_style: writing_style, tone: tone }}"
32
+ "HTTP Headers: #{loggable_headers}\nPayload #{{ text: text, target_lang: target_lang,
33
+ writing_style: writing_style, tone: tone }}"
32
34
  end
33
35
 
34
36
  def to_s
@@ -38,13 +40,7 @@ module DeepL
38
40
  private
39
41
 
40
42
  def build_texts(request, response)
41
- data = JSON.parse(response.body)
42
-
43
- texts = data['improvements'].map do |improvement|
44
- Resources::Text.new(improvement['text'], improvement['detected_source_language'], nil,
45
- request, response)
46
- end
47
-
43
+ texts = build_improvements(text.is_a?(Array) ? text.size : 1, request, response)
48
44
  texts.size == 1 ? texts.first : texts
49
45
  end
50
46
 
@@ -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 SpokenTerms
9
+ class Create < Base
10
+ include Helpers
11
+
12
+ attr_reader :name, :term_lists
13
+
14
+ def initialize(api, name, term_lists, options = {})
15
+ super(api, options)
16
+ @name = name
17
+ @term_lists = term_lists
18
+ end
19
+
20
+ def request
21
+ payload = { name: name, term_lists: term_lists_payload(term_lists) }
22
+ build_spoken_terms(*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
+ 'spoken-terms'
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 SpokenTerms
9
+ class Destroy < Base
10
+ include Helpers
11
+
12
+ attr_reader :id
13
+
14
+ def initialize(api, id)
15
+ super(api)
16
+ @id = spoken_terms_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
+ spoken_terms_path(id)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ 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.md file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ module SpokenTerms
9
+ class DestroyTermList < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :lang
13
+
14
+ def initialize(api, id, lang)
15
+ super(api)
16
+ @id = spoken_terms_id(id)
17
+ @lang = lang
18
+ end
19
+
20
+ def request
21
+ execute_request_with_retries(delete_request)
22
+ nil
23
+ end
24
+
25
+ def to_s
26
+ "DELETE #{uri.request_uri}"
27
+ end
28
+
29
+ private
30
+
31
+ def uri
32
+ @uri ||= URI("#{host}/v3/#{path}?#{encode_query(lang: lang)}")
33
+ end
34
+
35
+ def path
36
+ spoken_terms_path(id, 'term-lists')
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
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 SpokenTerms
9
+ class Entries < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :lang
13
+
14
+ def initialize(api, id, lang)
15
+ super(api)
16
+ @id = spoken_terms_id(id)
17
+ @lang = lang
18
+ end
19
+
20
+ def request
21
+ build_entries(*execute_request_with_retries(get_request))
22
+ end
23
+
24
+ def to_s
25
+ "GET #{uri.request_uri}"
26
+ end
27
+
28
+ private
29
+
30
+ def build_entries(request, response)
31
+ data = JSON.parse(response.body)
32
+ Resources::SpokenTermsEntries.new(data['lang'], entries_from_string(data['entries']),
33
+ request, response)
34
+ end
35
+
36
+ def uri
37
+ @uri ||= URI("#{host}/v3/#{path}?#{encode_query(lang: lang)}")
38
+ end
39
+
40
+ def path
41
+ spoken_terms_path(id, 'entries')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ 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 SpokenTerms
9
+ class Find < Base
10
+ include Helpers
11
+
12
+ attr_reader :id
13
+
14
+ def initialize(api, id)
15
+ super(api)
16
+ @id = spoken_terms_id(id)
17
+ end
18
+
19
+ def request
20
+ build_spoken_terms(*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
+ spoken_terms_path(id)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
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 SpokenTerms
9
+ # Encoding shared by the spoken terms requests. The API sends and returns the terms of a
10
+ # language as one newline-separated string; the SDK exposes them as an array.
11
+ module Helpers
12
+ private
13
+
14
+ def spoken_terms_id(spoken_terms)
15
+ return spoken_terms.spoken_terms_id if spoken_terms.is_a?(DeepL::Resources::SpokenTerms)
16
+
17
+ spoken_terms.to_s
18
+ end
19
+
20
+ # The path of a collection, with its ID URL-encoded.
21
+ def spoken_terms_path(id, suffix = nil)
22
+ ["spoken-terms/#{escape_path_segment(id)}", suffix].compact.join('/')
23
+ end
24
+
25
+ def entries_to_string(entries)
26
+ entries.map { |entry| Utils::TermValidator.validate!(entry) }.join("\n")
27
+ end
28
+
29
+ def entries_from_string(entries)
30
+ (entries || '').split("\n").reject(&:empty?)
31
+ end
32
+
33
+ def term_lists_payload(term_lists)
34
+ term_lists.map { |term_list| term_list_payload(term_list) }
35
+ end
36
+
37
+ # Accepts a SpokenTermsEntries object or a hash with +:lang+ and +:entries+.
38
+ def term_list_payload(term_list)
39
+ lang, entries = if term_list.is_a?(DeepL::Resources::SpokenTermsEntries)
40
+ [term_list.lang, term_list.entries]
41
+ else
42
+ [term_list[:lang] || term_list['lang'],
43
+ term_list[:entries] || term_list['entries']]
44
+ end
45
+ { lang: lang, entries: entries_to_string(entries) }
46
+ end
47
+
48
+ def build_spoken_terms(request, response)
49
+ Resources::SpokenTerms.new(JSON.parse(response.body), request, response)
50
+ end
51
+
52
+ def uri
53
+ @uri ||= URI("#{host}/v3/#{path}")
54
+ end
55
+
56
+ # GET and DELETE send no body.
57
+ def get_request # rubocop:disable Naming/AccessorMethodName
58
+ Net::HTTP::Get.new(uri.request_uri, add_json_content_type(headers))
59
+ end
60
+
61
+ def delete_request
62
+ Net::HTTP::Delete.new(uri.request_uri, add_json_content_type(headers))
63
+ end
64
+ end
65
+ end
66
+ end
67
+ 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 SpokenTerms
9
+ class List < Base
10
+ include Helpers
11
+
12
+ def request
13
+ build_spoken_terms_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_spoken_terms_list(request, response)
23
+ data = JSON.parse(response.body)
24
+ (data['spoken_terms'] || []).map do |spoken_terms|
25
+ Resources::SpokenTerms.new(spoken_terms, request, response)
26
+ end
27
+ end
28
+
29
+ def path
30
+ 'spoken-terms'
31
+ end
32
+ end
33
+ end
34
+ end
35
+ 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.md file.
4
+ # frozen_string_literal: true
5
+
6
+ module DeepL
7
+ module Requests
8
+ module SpokenTerms
9
+ class ReplaceTermList < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :entries
13
+
14
+ def initialize(api, id, entries, options = {})
15
+ super(api, options)
16
+ @id = spoken_terms_id(id)
17
+ @entries = entries
18
+ end
19
+
20
+ def request
21
+ payload = term_list_payload(entries)
22
+ build_term_list(*execute_request_with_retries(put_request(payload)))
23
+ end
24
+
25
+ def to_s
26
+ "PUT #{uri.request_uri}"
27
+ end
28
+
29
+ private
30
+
31
+ def build_term_list(request, response)
32
+ Resources::SpokenTermsList.new(JSON.parse(response.body), request, response)
33
+ end
34
+
35
+ def path
36
+ spoken_terms_path(id, 'term-lists')
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
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 SpokenTerms
9
+ class Update < Base
10
+ include Helpers
11
+
12
+ attr_reader :id, :name, :term_lists
13
+
14
+ def initialize(api, id, name, term_lists, options = {})
15
+ super(api, options)
16
+ @id = spoken_terms_id(id)
17
+ @name = name
18
+ @term_lists = term_lists
19
+ end
20
+
21
+ def request
22
+ payload = {}
23
+ payload[:name] = name unless name.nil?
24
+ payload[:term_lists] = term_lists_payload(term_lists) unless term_lists.nil?
25
+ build_spoken_terms(*execute_request_with_retries(patch_request(payload)))
26
+ end
27
+
28
+ def to_s
29
+ "PATCH #{uri.request_uri}"
30
+ end
31
+
32
+ private
33
+
34
+ def path
35
+ spoken_terms_path(id)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,83 @@
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
+ # Common behaviour of the style profile requests: served under +v3+, with the ID escaped
10
+ # into the path and GET/DELETE sent without a body.
11
+ class Base < DeepL::Requests::Base
12
+ COMPONENT_ID_FIELDS = %i[glossary_ids style_rule_ids translation_memory_ids].freeze
13
+
14
+ private
15
+
16
+ def style_profile_id(style_profile)
17
+ id = if style_profile.is_a?(DeepL::Resources::StyleProfile)
18
+ style_profile.style_profile_id.to_s
19
+ else
20
+ style_profile.to_s
21
+ end
22
+ raise ArgumentError, 'style_profile_id must not be empty' if id.empty?
23
+
24
+ id
25
+ end
26
+
27
+ def escaped_id
28
+ URI.encode_www_form_component(id).gsub('+', '%20')
29
+ end
30
+
31
+ # Adds the optional fields that are set. nil is not sent; '' and [] are sent as given.
32
+ def optional_fields_payload(fields)
33
+ fields.each_with_object({}) do |(key, value), payload|
34
+ next if value.nil?
35
+
36
+ payload[key] = COMPONENT_ID_FIELDS.include?(key) ? component_ids(value) : value
37
+ end
38
+ end
39
+
40
+ # Replaces resource objects in a component list by their IDs; other entries are kept.
41
+ def component_ids(value)
42
+ return value unless value.is_a?(Array)
43
+
44
+ value.map { |component| component_id(component) }
45
+ end
46
+
47
+ def component_id(component)
48
+ case component
49
+ when DeepL::Resources::Glossary, DeepL::Resources::MultilingualGlossary then component.id
50
+ when DeepL::Resources::StyleRule then component.style_id
51
+ when DeepL::Resources::TranslationMemory then component.translation_memory_id
52
+ else component
53
+ end
54
+ end
55
+
56
+ def build_style_profile(request, response)
57
+ Resources::StyleProfile.new(JSON.parse(response.body), request, response)
58
+ end
59
+
60
+ def uri
61
+ @uri ||= begin
62
+ base_uri = URI("#{host}/v3/#{path}")
63
+ query_string = encode_query(query_params)
64
+ base_uri.query = query_string unless query_string.empty?
65
+ base_uri
66
+ end
67
+ end
68
+
69
+ def query_params
70
+ {}
71
+ end
72
+
73
+ def get_request # rubocop:disable Naming/AccessorMethodName
74
+ Net::HTTP::Get.new(uri.request_uri, add_json_content_type(headers))
75
+ end
76
+
77
+ def delete_request
78
+ Net::HTTP::Delete.new(uri.request_uri, add_json_content_type(headers))
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end