deepl-rb 3.7.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +4 -2
- data/CHANGELOG.md +39 -1
- data/Gemfile +0 -2
- data/README.md +225 -16
- data/VERSION +1 -1
- data/deepl-rb.gemspec +52 -19
- data/lib/deepl/document_api.rb +11 -1
- data/lib/deepl/requests/base.rb +55 -3
- data/lib/deepl/requests/document/upload.rb +50 -1
- data/lib/deepl/requests/rephrase.rb +3 -2
- data/lib/deepl/requests/translate.rb +6 -3
- data/lib/deepl/requests/translation_memory/base.rb +47 -0
- data/lib/deepl/requests/translation_memory/create_export.rb +41 -0
- data/lib/deepl/requests/translation_memory/create_import.rb +53 -0
- data/lib/deepl/requests/translation_memory/destroy.rb +37 -0
- data/lib/deepl/requests/translation_memory/download_export.rb +35 -0
- data/lib/deepl/requests/translation_memory/find.rb +38 -0
- data/lib/deepl/requests/translation_memory/find_job.rb +38 -0
- data/lib/deepl/requests/translation_memory/list.rb +5 -18
- data/lib/deepl/requests/translation_memory/segments.rb +46 -0
- data/lib/deepl/requests/translation_memory/storage_base.rb +47 -0
- data/lib/deepl/requests/translation_memory/upload_file.rb +47 -0
- data/lib/deepl/resources/translation_memory.rb +4 -1
- data/lib/deepl/resources/translation_memory_export.rb +37 -0
- data/lib/deepl/resources/translation_memory_import.rb +29 -0
- data/lib/deepl/resources/translation_memory_job.rb +142 -0
- data/lib/deepl/resources/translation_memory_segments.rb +78 -0
- data/lib/deepl/translation_memory_api.rb +293 -1
- data/lib/deepl/utils/time_parser.rb +26 -0
- data/lib/deepl.rb +38 -4
- data/lib/version.rb +1 -1
- data/spec/api/deepl_spec.rb +134 -332
- data/spec/integration_tests/document_api_spec.rb +51 -18
- data/spec/integration_tests/document_error_paths_spec.rb +33 -0
- data/spec/integration_tests/glossary_api_spec.rb +114 -0
- data/spec/integration_tests/glossary_error_paths_spec.rb +107 -0
- data/spec/integration_tests/integration_test_utils.rb +8 -0
- data/spec/integration_tests/languages_api_spec.rb +54 -0
- data/spec/integration_tests/languages_error_paths_spec.rb +25 -0
- data/spec/integration_tests/rephrase_api_spec.rb +90 -0
- data/spec/integration_tests/rephrase_error_paths_spec.rb +53 -0
- data/spec/integration_tests/smoke_test_spec.rb +24 -0
- data/spec/integration_tests/style_rule_api_spec.rb +1 -19
- data/spec/integration_tests/style_rule_error_paths_spec.rb +45 -0
- data/spec/integration_tests/translate_api_spec.rb +117 -0
- data/spec/integration_tests/translate_error_paths_spec.rb +48 -0
- data/spec/integration_tests/translation_memory_api_spec.rb +191 -17
- data/spec/integration_tests/translation_memory_error_paths_spec.rb +87 -0
- data/spec/integration_tests/usage_api_spec.rb +29 -0
- data/spec/integration_tests/usage_error_paths_spec.rb +18 -0
- data/spec/requests/document/upload_spec.rb +130 -0
- data/spec/requests/glossary/create_spec.rb +0 -21
- data/spec/requests/glossary/destroy_spec.rb +0 -39
- data/spec/requests/glossary/entries_spec.rb +0 -35
- data/spec/requests/glossary/find_spec.rb +0 -40
- data/spec/requests/glossary/language_pairs_spec.rb +0 -13
- data/spec/requests/glossary/list_spec.rb +0 -27
- data/spec/requests/languages_spec.rb +0 -41
- data/spec/requests/rephrase_spec.rb +13 -139
- data/spec/requests/style_rule/create_custom_instruction_spec.rb +0 -24
- data/spec/requests/style_rule/create_spec.rb +0 -16
- data/spec/requests/style_rule/destroy_custom_instruction_spec.rb +0 -26
- data/spec/requests/style_rule/destroy_spec.rb +0 -27
- data/spec/requests/style_rule/find_custom_instruction_spec.rb +0 -27
- data/spec/requests/style_rule/find_spec.rb +0 -28
- data/spec/requests/style_rule/list_spec.rb +0 -31
- data/spec/requests/style_rule/update_configured_rules_spec.rb +0 -21
- data/spec/requests/style_rule/update_custom_instruction_spec.rb +0 -26
- data/spec/requests/style_rule/update_spec.rb +0 -19
- data/spec/requests/translate_spec.rb +63 -217
- data/spec/requests/translation_memory/create_export_spec.rb +37 -0
- data/spec/requests/translation_memory/create_import_spec.rb +61 -0
- data/spec/requests/translation_memory/destroy_spec.rb +36 -0
- data/spec/requests/translation_memory/download_export_spec.rb +54 -0
- data/spec/requests/translation_memory/find_job_spec.rb +37 -0
- data/spec/requests/translation_memory/find_spec.rb +36 -0
- data/spec/requests/translation_memory/list_spec.rb +0 -34
- data/spec/requests/translation_memory/segments_spec.rb +58 -0
- data/spec/requests/translation_memory/upload_file_spec.rb +55 -0
- data/spec/requests/usage_spec.rb +0 -16
- data/spec/resources/custom_instruction_spec.rb +32 -0
- data/spec/resources/style_rule_spec.rb +68 -0
- data/spec/resources/translation_memory_export_spec.rb +38 -0
- data/spec/resources/translation_memory_import_spec.rb +30 -0
- data/spec/resources/translation_memory_job_spec.rb +109 -0
- data/spec/resources/translation_memory_segments_spec.rb +78 -0
- data/spec/resources/translation_memory_spec.rb +18 -1
- data/spec/spec_helper.rb +15 -45
- data/spec/support/live_mock_server.rb +12 -0
- data/spec/support/managed_glossary.rb +65 -0
- data/spec/support/managed_style_rule.rb +17 -0
- data/spec/support/managed_translation_memory.rb +55 -0
- metadata +50 -17
- data/spec/fixtures/vcr_cassettes/deepl_document.yml +0 -95
- data/spec/fixtures/vcr_cassettes/deepl_document_download.yml +0 -1214
- data/spec/fixtures/vcr_cassettes/deepl_glossaries.yml +0 -1163
- data/spec/fixtures/vcr_cassettes/deepl_languages.yml +0 -54
- data/spec/fixtures/vcr_cassettes/deepl_rephrase.yml +0 -87
- data/spec/fixtures/vcr_cassettes/deepl_translate.yml +0 -358
- data/spec/fixtures/vcr_cassettes/deepl_usage.yml +0 -129
- data/spec/fixtures/vcr_cassettes/glossaries.yml +0 -1702
- data/spec/fixtures/vcr_cassettes/languages.yml +0 -229
- data/spec/fixtures/vcr_cassettes/rephrase_texts.yml +0 -401
- data/spec/fixtures/vcr_cassettes/style_rules.yml +0 -92
- data/spec/fixtures/vcr_cassettes/style_rules_crud.yml +0 -926
- data/spec/fixtures/vcr_cassettes/translate_texts.yml +0 -10630
- data/spec/fixtures/vcr_cassettes/translation_memories.yml +0 -74
- data/spec/fixtures/vcr_cassettes/usage.yml +0 -171
|
@@ -0,0 +1,142 @@
|
|
|
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 Resources
|
|
8
|
+
##
|
|
9
|
+
# The outcome of a translation memory import or export job.
|
|
10
|
+
|
|
11
|
+
class TranslationMemoryJobResult
|
|
12
|
+
STATUS_AWAITING_INPUT = 'awaiting_input'
|
|
13
|
+
STATUS_PROCESSING = 'processing'
|
|
14
|
+
STATUS_COMPLETED = 'completed'
|
|
15
|
+
STATUS_DOWNLOADED = 'downloaded'
|
|
16
|
+
STATUS_FAILED = 'failed'
|
|
17
|
+
STATUS_EXPIRED = 'expired'
|
|
18
|
+
|
|
19
|
+
attr_reader :status, :required_action, :download_url, :expires_at, :error_message,
|
|
20
|
+
:translation_memory_id, :skipped_segment_count
|
|
21
|
+
|
|
22
|
+
def initialize(result)
|
|
23
|
+
status_metadata = result['status_metadata'] || {}
|
|
24
|
+
error = result['error'] || {}
|
|
25
|
+
|
|
26
|
+
@status = result['status']
|
|
27
|
+
@required_action = status_metadata['required_action']
|
|
28
|
+
@download_url = result['download_url']
|
|
29
|
+
@expires_at = Utils::TimeParser.parse_optional_time(result['expires_at'])
|
|
30
|
+
@error_message = error['message']
|
|
31
|
+
@translation_memory_id = result['translation_memory_id']
|
|
32
|
+
@skipped_segment_count = result['skipped_segment_count']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Checks if the job terminated. Note that this could be due to an error as well, but means
|
|
37
|
+
# no further waiting is necessary.
|
|
38
|
+
#
|
|
39
|
+
# @return [true] if so
|
|
40
|
+
|
|
41
|
+
def finished?
|
|
42
|
+
[STATUS_COMPLETED, STATUS_DOWNLOADED, STATUS_FAILED, STATUS_EXPIRED].include?(status)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Checks if the job is still waiting for the TMX file to be uploaded. Note that the API
|
|
47
|
+
# detects an upload asynchronously, so a job keeps reporting this status for a while after
|
|
48
|
+
# its file has been uploaded.
|
|
49
|
+
#
|
|
50
|
+
# @return [true] if so
|
|
51
|
+
|
|
52
|
+
def awaiting_input?
|
|
53
|
+
status == STATUS_AWAITING_INPUT
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Checks if there was an error during the job, including it having expired.
|
|
58
|
+
#
|
|
59
|
+
# @return [true] if so
|
|
60
|
+
|
|
61
|
+
def error?
|
|
62
|
+
[STATUS_FAILED, STATUS_EXPIRED].include?(status)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_s
|
|
66
|
+
"TranslationMemoryJobResult: Status: #{status} - Error message: #{error_message}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Status of a translation memory import or export job. The API returns exactly one result.
|
|
72
|
+
|
|
73
|
+
class TranslationMemoryJob < Base
|
|
74
|
+
OPERATION_IMPORT = 'import'
|
|
75
|
+
OPERATION_EXPORT = 'export'
|
|
76
|
+
|
|
77
|
+
attr_reader :job_id, :product, :operation, :creation_time, :updated_time, :results,
|
|
78
|
+
:translation_memory_id, :display_name, :source_content_type,
|
|
79
|
+
:source_content_length
|
|
80
|
+
|
|
81
|
+
def initialize(job, *args)
|
|
82
|
+
super(*args)
|
|
83
|
+
extract_basic_fields(job)
|
|
84
|
+
extract_parameters(job)
|
|
85
|
+
@results = (job['results'] || []).map { |result| TranslationMemoryJobResult.new(result) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# The single result of the job, or nil if the API returned none.
|
|
90
|
+
#
|
|
91
|
+
# @return [DeepL::Resources::TranslationMemoryJobResult, nil]
|
|
92
|
+
|
|
93
|
+
def result
|
|
94
|
+
results.first
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def status
|
|
98
|
+
result&.status
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def finished?
|
|
102
|
+
result ? result.finished? : false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def awaiting_input?
|
|
106
|
+
result ? result.awaiting_input? : false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def error?
|
|
110
|
+
result ? result.error? : false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def error_message
|
|
114
|
+
result&.error_message
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_s
|
|
118
|
+
"TranslationMemoryJob: #{operation} - ID: #{job_id} - Status: #{status}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def extract_basic_fields(job)
|
|
124
|
+
@job_id = job['job_id']
|
|
125
|
+
@product = job['product']
|
|
126
|
+
@operation = job['operation']
|
|
127
|
+
@creation_time = Utils::TimeParser.parse_optional_time(job['creation_time'])
|
|
128
|
+
@updated_time = Utils::TimeParser.parse_optional_time(job['updated_time'])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def extract_parameters(job)
|
|
132
|
+
parameters = job['parameters'] || {}
|
|
133
|
+
source_file = job['source_file'] || {}
|
|
134
|
+
|
|
135
|
+
@translation_memory_id = parameters['translation_memory_id']
|
|
136
|
+
@display_name = parameters['display_name']
|
|
137
|
+
@source_content_type = source_file['content_type']
|
|
138
|
+
@source_content_length = source_file['content_length']
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
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 Resources
|
|
8
|
+
class TranslationMemoryTargetSegment
|
|
9
|
+
attr_reader :target_segment_id, :target_language, :target_text, :creation_time,
|
|
10
|
+
:updated_time, :last_used_time
|
|
11
|
+
|
|
12
|
+
def initialize(target_segment)
|
|
13
|
+
@target_segment_id = target_segment['target_segment_id']
|
|
14
|
+
@target_language = target_segment['target_language']
|
|
15
|
+
@target_text = target_segment['target_text']
|
|
16
|
+
@creation_time = Utils::TimeParser.parse_optional_time(target_segment['creation_time'])
|
|
17
|
+
@updated_time = Utils::TimeParser.parse_optional_time(target_segment['updated_time'])
|
|
18
|
+
@last_used_time = Utils::TimeParser.parse_optional_time(target_segment['last_used_time'])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
"#{target_language}: #{target_text}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class TranslationMemorySegment
|
|
27
|
+
attr_reader :source_segment_id, :source_text, :targets, :creation_time, :updated_time,
|
|
28
|
+
:last_used_time
|
|
29
|
+
|
|
30
|
+
def initialize(segment)
|
|
31
|
+
@source_segment_id = segment['source_segment_id']
|
|
32
|
+
@source_text = segment['source_text']
|
|
33
|
+
@targets = (segment['targets'] || []).map do |target|
|
|
34
|
+
TranslationMemoryTargetSegment.new(target)
|
|
35
|
+
end
|
|
36
|
+
@creation_time = Utils::TimeParser.parse_optional_time(segment['creation_time'])
|
|
37
|
+
@updated_time = Utils::TimeParser.parse_optional_time(segment['updated_time'])
|
|
38
|
+
@last_used_time = Utils::TimeParser.parse_optional_time(segment['last_used_time'])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_s
|
|
42
|
+
"#{source_segment_id} - #{source_text}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# One page of the segments of a translation memory. Pagination is cursor-based: pass
|
|
48
|
+
# `next_page_cursor` as the `page_cursor` option of the next request until it is nil.
|
|
49
|
+
|
|
50
|
+
class TranslationMemorySegments < Base
|
|
51
|
+
attr_reader :segments, :segment_count, :next_page_cursor
|
|
52
|
+
|
|
53
|
+
def initialize(segments_response, *args)
|
|
54
|
+
super(*args)
|
|
55
|
+
@segments = (segments_response['segments'] || []).map do |segment|
|
|
56
|
+
TranslationMemorySegment.new(segment)
|
|
57
|
+
end
|
|
58
|
+
# Note that this is the number of segments stored in the translation memory, it is not
|
|
59
|
+
# reduced by a text filter.
|
|
60
|
+
@segment_count = segments_response['segment_count'] || 0
|
|
61
|
+
@next_page_cursor = segments_response['next_page_cursor']
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Checks whether another page of segments can be requested.
|
|
66
|
+
#
|
|
67
|
+
# @return [true] if so
|
|
68
|
+
|
|
69
|
+
def next_page?
|
|
70
|
+
!next_page_cursor.nil?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_s
|
|
74
|
+
"#{segments.size} of #{segment_count} segment(s)"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -4,14 +4,306 @@
|
|
|
4
4
|
# frozen_string_literal: true
|
|
5
5
|
|
|
6
6
|
module DeepL
|
|
7
|
-
class TranslationMemoryApi
|
|
7
|
+
class TranslationMemoryApi # rubocop:disable Metrics/ClassLength
|
|
8
|
+
# Time to wait between two status queries of an import or export job.
|
|
9
|
+
JOB_POLLING_INTERVAL_SECONDS = 5
|
|
10
|
+
|
|
8
11
|
def initialize(api, options = {})
|
|
9
12
|
@api = api
|
|
10
13
|
@options = options
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
##
|
|
17
|
+
# Lists the translation memories of the account.
|
|
18
|
+
#
|
|
19
|
+
# @param [Hash] options Additional options for the request. Supports `page` (page number for
|
|
20
|
+
# pagination, 0-indexed) and `page_size` (number of items per page).
|
|
21
|
+
# @return [Array<DeepL::Resources::TranslationMemory>] The translation memories of the page.
|
|
22
|
+
|
|
13
23
|
def list(options = {})
|
|
14
24
|
DeepL::Requests::TranslationMemory::List.new(@api, options).request
|
|
15
25
|
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Retrieves a single translation memory.
|
|
29
|
+
#
|
|
30
|
+
# @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
|
|
31
|
+
# ID or object.
|
|
32
|
+
# @param [Hash] options Additional options for the request.
|
|
33
|
+
# @return [DeepL::Resources::TranslationMemory] The requested translation memory.
|
|
34
|
+
|
|
35
|
+
def find(translation_memory, options = {})
|
|
36
|
+
DeepL::Requests::TranslationMemory::Find.new(
|
|
37
|
+
@api, extract_translation_memory_id(translation_memory), options
|
|
38
|
+
).request
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Retrieves one page of the segments of a translation memory. Pagination is cursor-based:
|
|
43
|
+
# omit `page_cursor` on the first call, then pass the `next_page_cursor` of the previous
|
|
44
|
+
# response until it is nil.
|
|
45
|
+
#
|
|
46
|
+
# @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
|
|
47
|
+
# ID or object.
|
|
48
|
+
# @param [Hash] options Additional options for the request. Supports `page_size` (maximum
|
|
49
|
+
# number of segments per page, 1-100, defaults to 50), `page_cursor`
|
|
50
|
+
# (cursor of a previous response), `filter_text` (substring filter
|
|
51
|
+
# across source and target text, at least 2 characters) and
|
|
52
|
+
# `filter_case_sensitive` (whether the filter is case-sensitive,
|
|
53
|
+
# defaults to false).
|
|
54
|
+
# @return [DeepL::Resources::TranslationMemorySegments] The requested page of segments.
|
|
55
|
+
|
|
56
|
+
def segments(translation_memory, options = {})
|
|
57
|
+
DeepL::Requests::TranslationMemory::Segments.new(
|
|
58
|
+
@api, extract_translation_memory_id(translation_memory), options
|
|
59
|
+
).request
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Deletes a translation memory.
|
|
64
|
+
#
|
|
65
|
+
# @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
|
|
66
|
+
# ID or object.
|
|
67
|
+
# @param [Hash] options Additional options for the request.
|
|
68
|
+
# @return [String] The ID of the deleted translation memory.
|
|
69
|
+
|
|
70
|
+
def destroy(translation_memory, options = {})
|
|
71
|
+
DeepL::Requests::TranslationMemory::Destroy.new(
|
|
72
|
+
@api, extract_translation_memory_id(translation_memory), options
|
|
73
|
+
).request
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Creates an import job for a new translation memory. The job only declares the file, upload
|
|
78
|
+
# the TMX file itself to the returned upload URL with `upload_file`, then poll `find_job` for
|
|
79
|
+
# the outcome. Use `import_from_filepath` to do all three steps at once.
|
|
80
|
+
#
|
|
81
|
+
# @param [String] file_name Name of the TMX file to import, for example "legal.tmx".
|
|
82
|
+
# @param [Integer] content_length Size of the TMX file in bytes.
|
|
83
|
+
# @param [String, nil] content_type MIME type of the file, defaults to "application/xml".
|
|
84
|
+
# @param [String, nil] display_name Name of the resulting translation memory, defaults to the
|
|
85
|
+
# file name.
|
|
86
|
+
# @param [Hash] additional_headers Additional HTTP headers for the request.
|
|
87
|
+
# @return [DeepL::Resources::TranslationMemoryImport] The job ID and the upload URL.
|
|
88
|
+
|
|
89
|
+
def create_import(file_name, content_length, content_type: nil, display_name: nil,
|
|
90
|
+
additional_headers: {})
|
|
91
|
+
DeepL::Requests::TranslationMemory::CreateImport.new(
|
|
92
|
+
@api, file_name, content_length,
|
|
93
|
+
{ content_type: content_type, display_name: display_name }.compact, additional_headers
|
|
94
|
+
).request
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Uploads a TMX file to the upload URL of an import job, which starts the processing. The
|
|
99
|
+
# upload URL is a pre-signed storage URL outside of the DeepL API, so no authorization header
|
|
100
|
+
# is sent with this request.
|
|
101
|
+
#
|
|
102
|
+
# @param [String, DeepL::Resources::TranslationMemoryImport] translation_memory_import Import
|
|
103
|
+
# returned by `create_import`, or its upload URL.
|
|
104
|
+
# @param [String] file_content Content of the TMX file.
|
|
105
|
+
# @param [String] content_type MIME type of the file. Must match the `content_type` declared
|
|
106
|
+
# when the import job was created.
|
|
107
|
+
# @return [nil]
|
|
108
|
+
|
|
109
|
+
def upload_file(translation_memory_import, file_content,
|
|
110
|
+
content_type: Requests::TranslationMemory::UploadFile::DEFAULT_CONTENT_TYPE)
|
|
111
|
+
DeepL::Requests::TranslationMemory::UploadFile.new(
|
|
112
|
+
@api, extract_upload_url(translation_memory_import), file_content, content_type
|
|
113
|
+
).request
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Creates an export job for a translation memory. Poll `find_job` for the download URL of the
|
|
118
|
+
# exported TMX file. Use `export_to_filepath` to do both steps and write the file at once.
|
|
119
|
+
#
|
|
120
|
+
# @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
|
|
121
|
+
# ID or object.
|
|
122
|
+
# @param [Hash] options Additional options for the request.
|
|
123
|
+
# @return [DeepL::Resources::TranslationMemoryExport] The job ID, and whether the API reused a
|
|
124
|
+
# previously completed export.
|
|
125
|
+
|
|
126
|
+
def create_export(translation_memory, options = {})
|
|
127
|
+
DeepL::Requests::TranslationMemory::CreateExport.new(
|
|
128
|
+
@api, extract_translation_memory_id(translation_memory), options
|
|
129
|
+
).request
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# Retrieves the status of a translation memory import or export job.
|
|
134
|
+
#
|
|
135
|
+
# @param [String, DeepL::Resources::TranslationMemoryJob] job Job ID or object.
|
|
136
|
+
# @param [Hash] options Additional options for the request.
|
|
137
|
+
# @return [DeepL::Resources::TranslationMemoryJob] The current status of the job.
|
|
138
|
+
|
|
139
|
+
def find_job(job, options = {})
|
|
140
|
+
DeepL::Requests::TranslationMemory::FindJob.new(@api, extract_job_id(job), options).request
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Polls a translation memory import or export job until it is finished, `sleep`ing between
|
|
145
|
+
# the status queries, and returns the final status.
|
|
146
|
+
#
|
|
147
|
+
# Note that an import job keeps reporting `awaiting_input` for a while after its file has
|
|
148
|
+
# been uploaded, because the API detects the upload asynchronously. That status is therefore
|
|
149
|
+
# polled through like any other non-terminal one. A job whose file is never uploaded does not
|
|
150
|
+
# finish on its own, so pass `timeout_s` when that is a possibility.
|
|
151
|
+
#
|
|
152
|
+
# @raise [DeepL::Exceptions::Error] If the job failed or expired, or if `timeout_s` elapsed
|
|
153
|
+
# before the job finished.
|
|
154
|
+
#
|
|
155
|
+
# @param [String, DeepL::Resources::TranslationMemoryJob] job Job ID or object.
|
|
156
|
+
# @param [Hash] options Additional options for the status queries.
|
|
157
|
+
# @param [Numeric, nil] timeout_s Maximum time in seconds to wait for the job to finish. Note
|
|
158
|
+
# that this is not accurate to the second, the status is only
|
|
159
|
+
# queried every five seconds.
|
|
160
|
+
# @return [DeepL::Resources::TranslationMemoryJob] The finished job.
|
|
161
|
+
|
|
162
|
+
def wait_until_job_done(job, options = {}, timeout_s: nil)
|
|
163
|
+
job_status = find_job(job, options)
|
|
164
|
+
started_at = monotonic_time
|
|
165
|
+
until job_status.finished?
|
|
166
|
+
raise_timeout_error(timeout_s) if timeout_exceeded?(started_at, timeout_s)
|
|
167
|
+
|
|
168
|
+
log_job_polling
|
|
169
|
+
sleep(JOB_POLLING_INTERVAL_SECONDS)
|
|
170
|
+
job_status = find_job(job, options)
|
|
171
|
+
end
|
|
172
|
+
raise_job_error(job_status) if job_status.error?
|
|
173
|
+
|
|
174
|
+
job_status
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Downloads the TMX file of a completed export job. The download URL is a pre-signed storage
|
|
179
|
+
# URL outside of the DeepL API, so no authorization header is sent with this request.
|
|
180
|
+
#
|
|
181
|
+
# @raise [DeepL::Exceptions::Error] If the job carries no download URL, for example because
|
|
182
|
+
# it has not completed yet.
|
|
183
|
+
#
|
|
184
|
+
# @param [DeepL::Resources::TranslationMemoryJob, String] job Completed export job carrying
|
|
185
|
+
# the download URL, or the download URL itself.
|
|
186
|
+
# @param [String] output_path Path to the file to write to. Will be overwritten if the file
|
|
187
|
+
# already exists.
|
|
188
|
+
|
|
189
|
+
def download_export(job, output_path)
|
|
190
|
+
DeepL::Requests::TranslationMemory::DownloadExport.new(@api, extract_download_url(job),
|
|
191
|
+
output_path).request
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
##
|
|
195
|
+
# Imports a TMX file as a new translation memory: creates the import job, uploads the file
|
|
196
|
+
# and waits for the processing to finish.
|
|
197
|
+
#
|
|
198
|
+
# @raise [DeepL::Exceptions::Error] If the import fails.
|
|
199
|
+
#
|
|
200
|
+
# @param [String] input_file_path Path to the TMX file to import.
|
|
201
|
+
# @param [String, nil] display_name Name of the resulting translation memory, defaults to the
|
|
202
|
+
# file name.
|
|
203
|
+
# @param [Numeric, nil] timeout_s Maximum time in seconds to wait for the import to finish.
|
|
204
|
+
# Note that the API keeps reporting `awaiting_input` for a
|
|
205
|
+
# while after the upload, so allow for a generous timeout.
|
|
206
|
+
# @return [DeepL::Resources::TranslationMemoryJob] The finished import job, its result carries
|
|
207
|
+
# the ID of the new translation memory.
|
|
208
|
+
|
|
209
|
+
def import_from_filepath(input_file_path, display_name: nil, timeout_s: nil)
|
|
210
|
+
unless File.exist?(input_file_path)
|
|
211
|
+
raise Exceptions::Error, "No file found at #{input_file_path}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
file_content = File.binread(input_file_path)
|
|
215
|
+
created = create_import(File.basename(input_file_path), file_content.bytesize,
|
|
216
|
+
display_name: display_name)
|
|
217
|
+
upload_file(created, file_content)
|
|
218
|
+
wait_until_job_done(created.job_id, timeout_s: timeout_s)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
##
|
|
222
|
+
# Exports a translation memory to a TMX file: creates the export job, waits for it to finish
|
|
223
|
+
# and writes the result to +output_path+.
|
|
224
|
+
#
|
|
225
|
+
# @raise [DeepL::Exceptions::Error] If the export fails.
|
|
226
|
+
#
|
|
227
|
+
# @param [String, DeepL::Resources::TranslationMemory] translation_memory Translation memory
|
|
228
|
+
# ID or object.
|
|
229
|
+
# @param [String] output_path Path to the file to write to. Will be overwritten if the file
|
|
230
|
+
# already exists.
|
|
231
|
+
# @param [Numeric, nil] timeout_s Maximum time in seconds to wait for the export to finish.
|
|
232
|
+
# @return [DeepL::Resources::TranslationMemoryJob] The finished export job.
|
|
233
|
+
|
|
234
|
+
def export_to_filepath(translation_memory, output_path, timeout_s: nil)
|
|
235
|
+
created = create_export(translation_memory)
|
|
236
|
+
job = wait_until_job_done(created.job_id, timeout_s: timeout_s)
|
|
237
|
+
download_export(job, output_path)
|
|
238
|
+
job
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
private
|
|
242
|
+
|
|
243
|
+
def extract_translation_memory_id(translation_memory)
|
|
244
|
+
id = if translation_memory.is_a?(Resources::TranslationMemory)
|
|
245
|
+
translation_memory.translation_memory_id
|
|
246
|
+
else
|
|
247
|
+
translation_memory
|
|
248
|
+
end
|
|
249
|
+
raise Exceptions::Error, 'Translation memory ID must not be empty' if blank?(id)
|
|
250
|
+
|
|
251
|
+
id
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def extract_job_id(job)
|
|
255
|
+
id = job.is_a?(Resources::TranslationMemoryJob) ? job.job_id : job
|
|
256
|
+
raise Exceptions::Error, 'Job ID must not be empty' if blank?(id)
|
|
257
|
+
|
|
258
|
+
id
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def extract_upload_url(translation_memory_import)
|
|
262
|
+
if translation_memory_import.is_a?(Resources::TranslationMemoryImport)
|
|
263
|
+
translation_memory_import.upload_url
|
|
264
|
+
else
|
|
265
|
+
translation_memory_import
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def extract_download_url(job)
|
|
270
|
+
return job unless job.is_a?(Resources::TranslationMemoryJob)
|
|
271
|
+
|
|
272
|
+
download_url = job.result&.download_url
|
|
273
|
+
if blank?(download_url)
|
|
274
|
+
raise Exceptions::Error, 'Translation memory export job has no download URL, ' \
|
|
275
|
+
'it may not have completed yet'
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
download_url
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def blank?(value)
|
|
282
|
+
value.nil? || value.empty?
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def log_job_polling
|
|
286
|
+
@api.configuration.logger&.info('Rechecking translation memory job status after sleeping ' \
|
|
287
|
+
"for #{JOB_POLLING_INTERVAL_SECONDS} seconds.")
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def monotonic_time
|
|
291
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def timeout_exceeded?(started_at, timeout_s)
|
|
295
|
+
!timeout_s.nil? && (monotonic_time - started_at) > timeout_s
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def raise_timeout_error(timeout_s)
|
|
299
|
+
raise Exceptions::Error,
|
|
300
|
+
"Manual timeout of #{timeout_s}s exceeded for the translation memory job"
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def raise_job_error(job_status)
|
|
304
|
+
raise Exceptions::Error,
|
|
305
|
+
"Error occurred during the translation memory #{job_status.operation}: " \
|
|
306
|
+
"#{job_status.error_message || job_status.status}"
|
|
307
|
+
end
|
|
16
308
|
end
|
|
17
309
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
require 'time'
|
|
7
|
+
|
|
8
|
+
module DeepL
|
|
9
|
+
module Utils
|
|
10
|
+
module TimeParser
|
|
11
|
+
extend self
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Parses an optional timestamp returned by the API.
|
|
15
|
+
#
|
|
16
|
+
# @param [String, nil] time_string Timestamp in ISO 8601 format, or nil.
|
|
17
|
+
# @return [Time, nil] The parsed time, or nil if no timestamp was given.
|
|
18
|
+
|
|
19
|
+
def parse_optional_time(time_string)
|
|
20
|
+
return nil if time_string.nil? || time_string.empty?
|
|
21
|
+
|
|
22
|
+
Time.parse(time_string)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/deepl.rb
CHANGED
|
@@ -41,7 +41,17 @@ require_relative 'deepl/requests/style_rule/create_custom_instruction'
|
|
|
41
41
|
require_relative 'deepl/requests/style_rule/find_custom_instruction'
|
|
42
42
|
require_relative 'deepl/requests/style_rule/update_custom_instruction'
|
|
43
43
|
require_relative 'deepl/requests/style_rule/destroy_custom_instruction'
|
|
44
|
+
require_relative 'deepl/requests/translation_memory/base'
|
|
45
|
+
require_relative 'deepl/requests/translation_memory/storage_base'
|
|
44
46
|
require_relative 'deepl/requests/translation_memory/list'
|
|
47
|
+
require_relative 'deepl/requests/translation_memory/find'
|
|
48
|
+
require_relative 'deepl/requests/translation_memory/segments'
|
|
49
|
+
require_relative 'deepl/requests/translation_memory/destroy'
|
|
50
|
+
require_relative 'deepl/requests/translation_memory/create_import'
|
|
51
|
+
require_relative 'deepl/requests/translation_memory/upload_file'
|
|
52
|
+
require_relative 'deepl/requests/translation_memory/create_export'
|
|
53
|
+
require_relative 'deepl/requests/translation_memory/find_job'
|
|
54
|
+
require_relative 'deepl/requests/translation_memory/download_export'
|
|
45
55
|
require_relative 'deepl/requests/languages'
|
|
46
56
|
require_relative 'deepl/requests/translate'
|
|
47
57
|
require_relative 'deepl/requests/usage'
|
|
@@ -54,6 +64,10 @@ require_relative 'deepl/resources/document_translation_status'
|
|
|
54
64
|
require_relative 'deepl/resources/glossary'
|
|
55
65
|
require_relative 'deepl/resources/style_rule'
|
|
56
66
|
require_relative 'deepl/resources/translation_memory'
|
|
67
|
+
require_relative 'deepl/resources/translation_memory_segments'
|
|
68
|
+
require_relative 'deepl/resources/translation_memory_import'
|
|
69
|
+
require_relative 'deepl/resources/translation_memory_export'
|
|
70
|
+
require_relative 'deepl/resources/translation_memory_job'
|
|
57
71
|
require_relative 'deepl/resources/language'
|
|
58
72
|
require_relative 'deepl/resources/language_pair'
|
|
59
73
|
require_relative 'deepl/resources/text'
|
|
@@ -62,6 +76,7 @@ require_relative 'deepl/resources/usage'
|
|
|
62
76
|
# -- Utils
|
|
63
77
|
require_relative 'deepl/utils/exception_builder'
|
|
64
78
|
require_relative 'deepl/utils/backoff_timer'
|
|
79
|
+
require_relative 'deepl/utils/time_parser'
|
|
65
80
|
|
|
66
81
|
# -- Constants
|
|
67
82
|
require_relative 'deepl/constants/base_constant'
|
|
@@ -100,9 +115,26 @@ module DeepL
|
|
|
100
115
|
Requests::Languages.new(api, options).request
|
|
101
116
|
end
|
|
102
117
|
|
|
103
|
-
|
|
118
|
+
##
|
|
119
|
+
# Translates +text+ from +source_lang+ into +target_lang+.
|
|
120
|
+
#
|
|
121
|
+
# @param [String, Array<String>] text Text(s) to translate.
|
|
122
|
+
# @param [String, nil] source_lang Source language. `nil` enables automatic detection.
|
|
123
|
+
# @param [String] target_lang Target language.
|
|
124
|
+
# @param [Hash] options Additional (body) options for the translation. Notable options:
|
|
125
|
+
# * +:glossary_id+ - A single glossary ID (string) to use for the translation. Requires
|
|
126
|
+
# +source_lang+ to be set. Cannot be combined with +:glossary_ids+.
|
|
127
|
+
# * +:glossary_ids+ - An array of up to 5 glossary IDs (strings or
|
|
128
|
+
# `DeepL::Resources::Glossary` objects) to use for the translation. Glossaries are applied
|
|
129
|
+
# in order (first match wins). Requires +source_lang+ to be set. Cannot be combined with
|
|
130
|
+
# +:glossary_id+. Raises `ArgumentError` if these rules are violated or more than 5 IDs are
|
|
131
|
+
# provided.
|
|
132
|
+
# @param [Hash] additional_headers Additional HTTP headers for the translation.
|
|
133
|
+
# @return [DeepL::Resources::Text, Array<DeepL::Resources::Text>] Translated text resource(s).
|
|
134
|
+
def translate(text, source_lang, target_lang, options = {}, additional_headers = {})
|
|
104
135
|
configure if @configuration.nil?
|
|
105
|
-
Requests::Translate.new(api, text, source_lang, target_lang, options
|
|
136
|
+
Requests::Translate.new(api, text, source_lang, target_lang, options,
|
|
137
|
+
additional_headers).request
|
|
106
138
|
end
|
|
107
139
|
|
|
108
140
|
def document(options = {})
|
|
@@ -125,9 +157,11 @@ module DeepL
|
|
|
125
157
|
TranslationMemoryApi.new(api, options)
|
|
126
158
|
end
|
|
127
159
|
|
|
128
|
-
def rephrase(text, target_lang = nil, writing_style = nil, tone = nil, options = {}
|
|
160
|
+
def rephrase(text, target_lang = nil, writing_style = nil, tone = nil, options = {}, # rubocop:disable Metrics/ParameterLists
|
|
161
|
+
additional_headers = {})
|
|
129
162
|
configure if @configuration.nil?
|
|
130
|
-
Requests::Rephrase.new(api, text, target_lang, writing_style, tone, options
|
|
163
|
+
Requests::Rephrase.new(api, text, target_lang, writing_style, tone, options,
|
|
164
|
+
additional_headers).request
|
|
131
165
|
end
|
|
132
166
|
|
|
133
167
|
def usage(options = {})
|
data/lib/version.rb
CHANGED