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
@@ -6,31 +6,69 @@
6
6
  module DeepL
7
7
  class Configuration
8
8
  ATTRIBUTES = %i[auth_key host logger max_doc_status_queries max_network_retries user_agent
9
- version].freeze
9
+ version headers timeout].freeze
10
10
 
11
- attr_accessor(*ATTRIBUTES)
11
+ # max_doc_status_queries= is defined below, with its deprecation warning.
12
+ attr_accessor(*(ATTRIBUTES - %i[max_doc_status_queries]))
13
+ attr_reader :max_doc_status_queries
12
14
 
13
15
  DEEPL_SERVER_URL = 'https://api.deepl.com'
14
16
  DEEPL_SERVER_URL_FREE = 'https://api-free.deepl.com'
15
- private_constant :DEEPL_SERVER_URL, :DEEPL_SERVER_URL_FREE
17
+ # Net::HTTP's 60-second default, kept for the module-level API; 10 in the next major version.
18
+ DEFAULT_TIMEOUT = 60.0
19
+ private_constant :DEEPL_SERVER_URL, :DEEPL_SERVER_URL_FREE, :DEFAULT_TIMEOUT
16
20
 
17
21
  def initialize(data = {}, app_info_name = nil, app_info_version = nil, send_platform_info = true) # rubocop:disable all
18
22
  data.each { |key, value| send("#{key}=", value) }
19
23
  @auth_key ||= ENV.fetch('DEEPL_AUTH_KEY', nil)
20
24
  @host ||= ENV.fetch('DEEPL_SERVER_URL', nil)
21
- @host ||= if self.class.free_account_auth_key?(@auth_key)
22
- DEEPL_SERVER_URL_FREE
23
- else
24
- DEEPL_SERVER_URL
25
- end
26
25
  @version ||= 'v2'
27
26
  @user_agent ||= construct_user_agent(send_platform_info, app_info_name, app_info_version)
28
27
  @max_network_retries ||= 5
29
28
  @logger ||= nil
29
+ @headers ||= {}
30
+ @timeout ||= DEFAULT_TIMEOUT
31
+ end
32
+
33
+ # The host is resolved when it is read, so that an auth key set after the configuration was
34
+ # created (e.g. in a +DeepL.configure+ block) still selects the Free API.
35
+ def host
36
+ return @host unless @host.nil?
37
+
38
+ self.class.free_account_auth_key?(auth_key) ? DEEPL_SERVER_URL_FREE : DEEPL_SERVER_URL
39
+ end
40
+
41
+ ##
42
+ # @deprecated Use the +timeout_s:+ of DeepL::Client#translate_document or
43
+ # DeepL::Client#translate_document_wait_until_done instead.
44
+ def max_doc_status_queries=(value)
45
+ unless value.nil?
46
+ DeepL::Deprecation.warn_once_at_caller(
47
+ 'DeepL::Configuration#max_doc_status_queries=',
48
+ '[DEPRECATION] DeepL::Configuration#max_doc_status_queries= is deprecated, use the ' \
49
+ 'timeout_s: of DeepL::Client#translate_document instead.'
50
+ )
51
+ end
52
+ @max_doc_status_queries = value
53
+ end
54
+
55
+ ##
56
+ # @deprecated The API version is part of each endpoint; this setting will be removed in the
57
+ # next major version.
58
+ def version=(value)
59
+ unless value.nil? || value == 'v2'
60
+ DeepL::Deprecation.warn_once('DeepL::Configuration#version=',
61
+ '[DEPRECATION] DeepL::Configuration#version= is deprecated ' \
62
+ 'and will be removed in the next major version.')
63
+ end
64
+ @version = value
30
65
  end
31
66
 
32
67
  def validate!
33
- raise Exceptions::Error, 'auth_key not provided' if auth_key.nil? || auth_key.empty?
68
+ return unless auth_key.nil? || auth_key.to_s.strip.empty?
69
+
70
+ raise Exceptions::Error,
71
+ 'auth_key not provided'
34
72
  end
35
73
 
36
74
  def attributes
@@ -41,8 +79,16 @@ module DeepL
41
79
  attributes == other.attributes
42
80
  end
43
81
 
82
+ # Redacts the auth key, so that it never shows in the inspect output of objects holding the
83
+ # configuration.
84
+ def inspect
85
+ shown = attributes.merge(auth_key: auth_key.nil? ? nil : '[REDACTED]')
86
+ "#<#{self.class.name} #{shown.map { |name, value| "#{name}=#{value.inspect}" }.join(', ')}>"
87
+ end
88
+
89
+ # The key is trimmed like the one sent in the Authorization header.
44
90
  def self.free_account_auth_key?(key)
45
- key&.end_with?(':fx')
91
+ key.to_s.strip.end_with?(':fx')
46
92
  end
47
93
 
48
94
  def construct_user_agent(send_platform_info, app_info_name, app_info_version)
@@ -0,0 +1,13 @@
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 Constants
8
+ class AdminUsageGroupBy < BaseConstant
9
+ KEY = 'key'
10
+ KEY_AND_DAY = 'key_and_day'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
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 Constants
8
+ class CustomTagUsageAggregateBy < BaseConstant
9
+ PERIOD = 'period'
10
+ DAY = 'day'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
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 Constants
8
+ # Values of +DeepL::Resources::DocumentTranslationStatus#status+. The status stays a plain
9
+ # string, so a value the API adds later is kept as it is.
10
+ class DocumentStatusCode < BaseConstant
11
+ QUEUED = 'queued'
12
+ TRANSLATING = 'translating'
13
+ DONE = 'done'
14
+ ERROR = 'error'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
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 Constants
8
+ # Values of the +include+ option of +DeepL::Client#get_languages_for_resource+. Other strings
9
+ # are accepted and sent unchanged.
10
+ class LanguageInclude < BaseConstant
11
+ # Also return beta languages and features.
12
+ BETA = 'beta'
13
+ # Also return features that rely on third-party providers.
14
+ EXTERNAL = 'external'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
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 Constants
8
+ # Values of the +resource+ argument of +DeepL::Client#get_languages_for_resource+ and of
9
+ # +DeepL::Resources::LanguageResource#name+. Other strings are accepted and sent unchanged, so
10
+ # a resource the API adds later can be queried before it is listed here.
11
+ class LanguageResourceName < BaseConstant
12
+ TRANSLATE_TEXT = 'translate_text'
13
+ TRANSLATE_DOCUMENT = 'translate_document'
14
+ GLOSSARY = 'glossary'
15
+ VOICE = 'voice'
16
+ WRITE = 'write'
17
+ STYLE_RULES = 'style_rules'
18
+ TRANSLATION_MEMORY = 'translation_memory'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
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 Constants
8
+ # Values of the +tag_handling_version+ option. Any other string is sent as given.
9
+ class TagHandlingVersion < BaseConstant
10
+ V1 = 'v1'
11
+ V2 = 'v2'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,42 @@
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
+ # @api private
8
+ module Deprecation
9
+ @warned = {}
10
+ @mutex = Mutex.new
11
+
12
+ # Prints +message+ once per process for +key+, only while Ruby's deprecation warnings are on
13
+ # (+ruby -W:deprecated+). +uplevel+ 2 points the warning at the caller of the deprecated
14
+ # method.
15
+ def self.warn_once(key, message, uplevel: 2)
16
+ return unless enabled?
17
+
18
+ first = @mutex.synchronize { @warned[key] ? false : (@warned[key] = true) }
19
+ return unless first
20
+
21
+ if RUBY_VERSION >= '3.0'
22
+ Kernel.warn(message, uplevel: uplevel, category: :deprecated)
23
+ else
24
+ Kernel.warn(message, uplevel: uplevel)
25
+ end
26
+ end
27
+
28
+ # Ruby 2.6 has no Warning.[] and prints no deprecation warnings.
29
+ def self.enabled?
30
+ Warning.respond_to?(:[]) && Warning[:deprecated]
31
+ end
32
+
33
+ # Like +warn_once+, but points the warning at the first caller outside the library, for
34
+ # deprecated options that are read deep inside a request.
35
+ def self.warn_once_at_caller(key, message)
36
+ lib_dir = File.expand_path('..', __dir__)
37
+ locations = caller_locations(1) || []
38
+ index = locations.index { |loc| !loc.absolute_path.to_s.start_with?(lib_dir) }
39
+ warn_once(key, message, uplevel: (index || 0) + 2)
40
+ end
41
+ end
42
+ end
@@ -26,16 +26,23 @@ module DeepL
26
26
  # the different kinds of documents (PDFs, etc). If nil, will use
27
27
  # the filename of +input_file_path+.
28
28
  # @param [Hash] options Additional (body) options for the upload. Notable glossary options:
29
- # +:glossary_id+ (a single glossary ID) or +:glossary_ids+ (an array of
30
- # up to 5 glossary IDs, as strings or `DeepL::Resources::Glossary`
31
- # objects). +:glossary_ids+ requires +source_lang+ to be set, cannot be
32
- # combined with +:glossary_id+, and raises `ArgumentError` if these rules
33
- # are violated or more than 5 IDs are provided. Notable style/translation
34
- # memory options (mirroring text translation): +:style_rule+ (a style rule
35
- # ID string or a `DeepL::Resources::StyleRule` object),
29
+ # +:glossary+ (a glossary ID, `DeepL::Resources::Glossary` or
30
+ # `DeepL::Resources::MultilingualGlossary`; replaces the deprecated
31
+ # +:glossary_id+) or +:glossary_ids+ (an array of up to 5 glossary IDs
32
+ # or objects).
33
+ # +:glossary_ids+ requires +source_lang+ to be set, cannot be combined
34
+ # with +:glossary+ or +:glossary_id+, and raises `ArgumentError` if
35
+ # these rules are violated or more than 5 IDs are provided. Notable
36
+ # style/translation memory options (mirroring text translation):
37
+ # +:style_rule+ (a style rule ID string or a
38
+ # `DeepL::Resources::StyleRule` object),
39
+ # +:style_profile+ (a style profile ID string or a
40
+ # `DeepL::Resources::StyleProfile` object),
36
41
  # +:translation_memory+ (a translation memory ID string or a
37
42
  # `DeepL::Resources::TranslationMemory` object), and
38
43
  # +:translation_memory_threshold+ (integer 0-100, recommended minimum 75).
44
+ # +:enable_watermark+ (boolean) adds a "Translated by DeepL" watermark;
45
+ # the API applies it to +docx+ and +pdf+ output only.
39
46
  # @param [Hash] additional_headers Additional HTTP headers for the upload.
40
47
  # @return [DeepL::Resources::DocumentHandle] Document handle for the uploaded document.
41
48
 
@@ -76,16 +83,17 @@ module DeepL
76
83
  end
77
84
 
78
85
  ##
79
- # Translates a document with the DeepL API, `sleep`ing during waiting periods. Returns the
80
- # status that was queried last. This can be either because the document translation terminated
81
- # (successfully or with an error) or because the maximum number of status requests have been
82
- # made. See the parameter `max_doc_status_queries` for details.
86
+ # Translates a document with the DeepL API, `sleep`ing during waiting periods, and returns
87
+ # the number of bytes written once the translated document is downloaded. Polls until the
88
+ # translation is done or failed; the deprecated `max_doc_status_queries` limits the number of
89
+ # status requests if it is configured.
83
90
  #
84
- # @raise [DocumentTranslationError] If any error occurs during the process.
91
+ # @raise [DocumentTranslationError] If any error occurs during the process, also when the
92
+ # translation fails or is not done after `max_doc_status_queries`.
85
93
  #
86
94
  # @param [String] input_file Path to the file to be translated
87
- # @param [String] output_file Path to the file to write to. Will be overwritten if the file
88
- # already exists.
95
+ # @param [String] output_file Path to the file to write to. Must not exist; if it does,
96
+ # DocumentTranslationError is raised and the file is kept.
89
97
  # @param [String, nil] source_lang Source language to use for the translation. `nil` will cause
90
98
  # automatic source langauge detection to be used. Must be
91
99
  # formatted as ISO 639-1, 2-letter language codes.
@@ -97,11 +105,13 @@ module DeepL
97
105
  # the filename of +input_file_path+.
98
106
  # @param [Hash] options Additional options for the upload.
99
107
  # @param [Hash] additional_headers Additional headers for the upload.
100
- # @return [DeepL::Resources::DocumentTranslationStatus] Status of the document translation.
108
+ # @return [Integer] Number of bytes written
101
109
 
102
110
  def translate_document(input_file, output_file, source_lang, target_lang, # rubocop:disable Metrics/MethodLength,Metrics/ParameterLists
103
111
  filename = nil, options = {}, additional_headers = {})
104
- raise IOError 'File already exists at output path' if File.exist?(output_file)
112
+ if File.exist?(output_file)
113
+ raise Exceptions::DocumentTranslationError.new('File already exists at output path', nil)
114
+ end
105
115
 
106
116
  begin
107
117
  handle = upload(input_file, source_lang, target_lang, filename, options,
@@ -110,7 +120,7 @@ module DeepL
110
120
  rescue StandardError => e
111
121
  FileUtils.rm_f(output_file)
112
122
  raise Exceptions::DocumentTranslationError.new(
113
- "Error occurred during document translation: #{e.message}", handle
123
+ "Error occurred during document translation: #{e.message}", handle, e
114
124
  )
115
125
  end
116
126
  end
@@ -118,13 +128,22 @@ module DeepL
118
128
  private
119
129
 
120
130
  def translate_document_wait_and_download(document_handle, output_file)
121
- doc_status = document_handle.wait_until_document_translation_finished
131
+ doc_status = Utils::DocumentPoller.wait_until_finished(
132
+ @api, document_handle, max_queries: @api.configuration.max_doc_status_queries
133
+ )
134
+ unless doc_status.done?
135
+ raise Exceptions::DocumentTranslationError.new(not_done_message(doc_status),
136
+ document_handle)
137
+ end
138
+
139
+ download(document_handle, output_file)
140
+ end
141
+
142
+ def not_done_message(doc_status)
122
143
  if doc_status.error?
123
- raise Exceptions::DocumentTranslationError.new(
124
- "Exception when querying document status #{doc_status.error_message}", document_handle
125
- )
144
+ "Exception when querying document status #{doc_status.error_message}"
126
145
  else
127
- download(document_handle, output_file)
146
+ "Document translation not done after max_doc_status_queries (#{doc_status.status})"
128
147
  end
129
148
  end
130
149
  end
@@ -7,9 +7,7 @@ module DeepL
7
7
  module Exceptions
8
8
  class BadRequest < RequestError
9
9
  def to_s
10
- JSON.parse(response.body)['message']
11
- rescue JSON::ParserError
12
- response.body
10
+ body_message.to_s
13
11
  end
14
12
  end
15
13
  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 Exceptions
8
+ ##
9
+ # Raised by DeepL::Client and DeepL::AdminClient for a network failure without a complete
10
+ # HTTP response from the API (DNS, connection, TLS, proxy, timeout or a body cut off
11
+ # mid-read), after the retries are used up. Transient failures (DNS, connection, timeout,
12
+ # cut-off body) are retried and have +should_retry?+ true; TLS failures, proxy failures and
13
+ # malformed responses are raised at once and have +should_retry?+ false. The original
14
+ # exception is available as +cause+. The module-level API raises the original exception.
15
+ class ConnectionError < Error
16
+ # Raised from a +rescue+ block, so the original exception becomes +cause+.
17
+ # +should_retry: false+ marks a request that is unsafe to repeat once sent.
18
+ def self.from_network_error(error, should_retry: true)
19
+ new("Connection to the DeepL API failed: #{error.message}",
20
+ should_retry: should_retry && transient?(error))
21
+ end
22
+
23
+ def self.transient?(error)
24
+ Requests::Base::RETRYABLE_NETWORK_ERRORS.any? { |type| error.is_a?(type) }
25
+ end
26
+
27
+ def initialize(message = nil, should_retry: true)
28
+ super(message)
29
+ @should_retry = should_retry
30
+ end
31
+
32
+ def should_retry?
33
+ @should_retry
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
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 Exceptions
8
+ # HTTP 503 on a document download: the translated document is not ready yet. Raised after
9
+ # the retries.
10
+ class DocumentNotReadyError < ServerError
11
+ def to_s
12
+ message = body_message
13
+ message ? "Document not ready: #{message}" : 'Document not ready.'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -6,9 +6,25 @@
6
6
  module DeepL
7
7
  module Exceptions
8
8
  class DocumentTranslationError < Error
9
- def initialize(message, handle)
9
+ # @return [DeepL::Resources::DocumentHandle, nil] Handle of the uploaded document, +nil+ if
10
+ # the upload failed. Use it to query the status or download the document later.
11
+ attr_reader :document_handle
12
+
13
+ # +error+ is the failure being wrapped; the HTTP status and retryability of a DeepL error
14
+ # are copied from it.
15
+ def initialize(message, handle, error = nil)
10
16
  super(message)
11
17
  @handle = handle
18
+ @document_handle = handle
19
+ return unless error.is_a?(Error)
20
+
21
+ @http_status_code = error.http_status_code
22
+ @should_retry = error.should_retry?
23
+ end
24
+
25
+ # @return [Boolean] +should_retry?+ of the wrapped DeepL error, +false+ without one.
26
+ def should_retry?
27
+ @should_retry || false
12
28
  end
13
29
  end
14
30
  end
@@ -6,8 +6,20 @@
6
6
  module DeepL
7
7
  module Exceptions
8
8
  class Error < StandardError
9
+ # @return [Integer, nil] HTTP status of the failed response, +nil+ if there was none.
10
+ attr_reader :http_status_code
11
+
12
+ # @param [String, nil] message
13
+ # @param [Integer, nil] http_status_code HTTP status of the failed response.
14
+ # @param [Boolean] should_retry Whether repeating the request may succeed.
15
+ def initialize(message = nil, http_status_code: nil, should_retry: false)
16
+ super(message)
17
+ @http_status_code = http_status_code
18
+ @should_retry = should_retry
19
+ end
20
+
9
21
  def should_retry?
10
- false
22
+ @should_retry || false
11
23
  end
12
24
  end
13
25
  end
@@ -6,10 +6,15 @@
6
6
  module DeepL
7
7
  module Exceptions
8
8
  class NotFound < RequestError
9
+ # @param [String, nil] message Replaces the API's message, for a not-found condition the
10
+ # SDK detects in a successful response.
11
+ def initialize(response, request = nil, message = nil)
12
+ super(response, request)
13
+ @message = message
14
+ end
15
+
9
16
  def to_s
10
- JSON.parse(response.body)['message']
11
- rescue JSON::ParserError
12
- response.body
17
+ (@message || body_message).to_s
13
18
  end
14
19
  end
15
20
  end
@@ -5,8 +5,14 @@
5
5
 
6
6
  module DeepL
7
7
  module Exceptions
8
+ # @deprecated Only the module-level API raises it, from
9
+ # +DeepL::Resources::Language#supports_formality?+ for a source language; DeepL::Client
10
+ # returns +nil+ instead.
8
11
  class NotSupported < Error
9
12
  def initialize(msg = 'The feature is not supported')
13
+ Deprecation.warn_once('DeepL::Exceptions::NotSupported',
14
+ '[DEPRECATION] DeepL::Exceptions::NotSupported is deprecated, ' \
15
+ 'only the module-level API raises it.')
10
16
  super
11
17
  end
12
18
  end
@@ -8,13 +8,30 @@ module DeepL
8
8
  class RequestError < Error
9
9
  attr_reader :request, :response
10
10
 
11
- def initialize(response)
11
+ def initialize(response, request = nil)
12
12
  super()
13
13
  @response = response
14
+ @request = request
15
+ @http_status_code = response.code.to_i if response.respond_to?(:code)
14
16
  end
15
17
 
16
18
  def to_s
17
- 'Unknown error.'
19
+ message = body_message
20
+ status = "HTTP status #{http_status_code}"
21
+ message ? "Request failed with #{status}: #{message}" : "Request failed with #{status}."
22
+ end
23
+
24
+ private
25
+
26
+ # The +message+ of a JSON object body, else the raw body; +nil+ for an empty body.
27
+ def body_message
28
+ body = response&.body
29
+ return nil if body.nil? || body.to_s.strip.empty?
30
+
31
+ data = JSON.parse(body)
32
+ data.is_a?(Hash) && data['message'].is_a?(String) ? data['message'] : body
33
+ rescue JSON::ParserError
34
+ body
18
35
  end
19
36
  end
20
37
  end
@@ -6,12 +6,18 @@
6
6
  module DeepL
7
7
  module Exceptions
8
8
  class ServerError < RequestError
9
+ # @param [Boolean] should_retry False for a request that is unsafe to repeat once sent.
10
+ def initialize(response, request = nil, should_retry: true)
11
+ super(response, request)
12
+ @should_retry = should_retry
13
+ end
14
+
9
15
  def to_s
10
16
  'An internal server error occured. Try again after waiting a short period.'
11
17
  end
12
18
 
13
19
  def should_retry?
14
- true
20
+ @should_retry
15
21
  end
16
22
  end
17
23
  end
@@ -0,0 +1,14 @@
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 Exceptions
8
+ ##
9
+ # An invalid argument detected before any request is sent: an invalid glossary or spoken
10
+ # terms term, a malformed glossary TSV line, or an unsupported +DeepL.languages+ type.
11
+ class ValidationError < Error
12
+ end
13
+ end
14
+ end