lara-sdk 1.7.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07f0f176e933b058cb2389a2f42b9eca44f340697f90f7249071ca7db164da86
4
- data.tar.gz: f44fd4c0ec64c2051a77273eab0d4fdff5f1f47f4188c8fe6b21f6f3dfd492a6
3
+ metadata.gz: 6441d4e8ad5cc6064ed3367644ed969d3c91e2dc5d5bcf2645680b2389d491b6
4
+ data.tar.gz: 1bbdb83ca6fb7f330ebb3adf69b8126facb9c1045875f083196580a2915d8b67
5
5
  SHA512:
6
- metadata.gz: f70a02eabf9588e9b6348efcffed1e4e051cfa11fea586c804ffc47e36c8a65fc9da2c1cfa466539a09b8dea0547727ce9d26dde0259056c1ed993409289380d
7
- data.tar.gz: 96d6199390210e3b8c8628c9f048668981870671d78e08ee59f9bc1b08b8f856d8cd90070cf9150a2964fcfa3f8ce5198edda88f437e6609a718f5782cd5054d
6
+ metadata.gz: c8b12451198b5a52a076766e310e4622cab58c1a1ce6173d6f3852291c183835b3111fcded6aee2ce60b0631727066d9b3dd443644ea03d12856f2db01314f07
7
+ data.tar.gz: b7dd274d9cdf562a91d0fcb496c9ce2f4053d9be3a99bb4fe5bfdb9d09d93d271ef46e82e2f8538b9b4fb24da8489401ef40bf79f6eeb29c1bc68bbbce3add2f
data/lib/lara/client.rb CHANGED
@@ -92,6 +92,15 @@ module Lara
92
92
 
93
93
  def request(method, path, body: nil, files: nil, headers: nil, params: nil,
94
94
  raw_response: false, &callback)
95
+ file_positions = (files || {}).each_with_object({}) do |(name, file), positions|
96
+ next unless file.respond_to?(:pos) && file.respond_to?(:seek)
97
+
98
+ begin
99
+ positions[name] = file.pos
100
+ rescue IOError, SystemCallError
101
+ # Non-seekable uploads can still be sent once.
102
+ end
103
+ end
95
104
  ensure_valid_token
96
105
 
97
106
  make_request(method, path, body: body, files: files, headers: headers, params: params,
@@ -100,6 +109,8 @@ module Lara
100
109
  raise unless e.status_code == 401
101
110
 
102
111
  @auth_mutex.synchronize { refresh_or_reauthenticate }
112
+ # Multipart encoding consumed the files; replay their original bytes.
113
+ file_positions.each { |name, position| files[name].seek(position) }
103
114
  make_request(method, path, body: body, files: files, headers: headers, params: params,
104
115
  raw_response: raw_response, &callback)
105
116
  end
@@ -218,7 +229,7 @@ module Lara
218
229
  return response.body if raw_response
219
230
 
220
231
  content_type = response.headers["content-type"] || response.headers["Content-Type"]
221
- return response.body if content_type&.include?("text/csv")
232
+ return response.body if content_type&.include?("text/csv") || content_type&.include?("application/xml")
222
233
 
223
234
  if callback || (body && body[:reasoning])
224
235
  parse_stream_response(response.body, &callback)
@@ -6,10 +6,11 @@ module Lara
6
6
  module FileFormat
7
7
  UNIDIRECTIONAL = "csv/table-uni"
8
8
  MULTIDIRECTIONAL = "csv/table-multi"
9
+ TBX = "tbx"
9
10
 
10
11
  # @return [Array<String>] All supported formats
11
12
  def self.all
12
- [UNIDIRECTIONAL, MULTIDIRECTIONAL]
13
+ [UNIDIRECTIONAL, MULTIDIRECTIONAL, TBX]
13
14
  end
14
15
 
15
16
  # @param format [String] The format to validate
@@ -91,33 +92,28 @@ module Lara
91
92
  Lara::Models::GlossaryCounts.new(**@client.get("/v2/glossaries/#{id}/counts").transform_keys(&:to_sym))
92
93
  end
93
94
 
94
- # @param content_type [String] Either FileFormat::UNIDIRECTIONAL or FileFormat::MULTIDIRECTIONAL
95
- # @param gzip [Boolean] When true, compress the CSV before upload and set compression=gzip
95
+ # Import options are independent keyword arguments; omitted options use their defaults.
96
+ # @param content_type [String] One of FileFormat.all; defaults to UNIDIRECTIONAL
97
+ # @param gzip [Boolean] Whether the supplied file is already gzip-compressed; defaults to false
96
98
  # @param callback_url [String,nil] Optional URL notified when the import completes
97
99
  # @return [Lara::Models::GlossaryImport]
98
- def import_csv(id, csv_path, content_type: FileFormat::UNIDIRECTIONAL, gzip: false,
99
- callback_url: nil)
100
+ def import_file(id, file_path, content_type: FileFormat::UNIDIRECTIONAL, gzip: false,
101
+ callback_url: nil)
100
102
  unless FileFormat.valid?(content_type)
101
103
  raise ArgumentError, "Invalid content_type. Supported formats: #{FileFormat.all.join(', ')}"
102
104
  end
103
105
 
104
- basename = File.basename(csv_path)
105
- if gzip
106
- require "stringio"
107
- require "zlib"
108
-
109
- buffer = StringIO.new
110
- gz = Zlib::GzipWriter.new(buffer, 7, Zlib::DEFAULT_STRATEGY)
111
- File.open(csv_path, "rb") { |_f| IO.copy_stream(_f, gz) }
112
- gz.finish
113
- buffer.rewind
114
-
115
- body = { "compression" => "gzip" }
116
- files = { "csv" => Faraday::UploadIO.new(buffer, "application/gzip", "#{basename}.gz") }
117
- else
118
- body = {}
119
- files = { "csv" => Faraday::UploadIO.new(csv_path, "text/csv", basename) }
120
- end
106
+ basename = File.basename(file_path)
107
+ mime_type = if gzip
108
+ "application/gzip"
109
+ elsif content_type == FileFormat::TBX
110
+ "application/xml"
111
+ else
112
+ "text/csv"
113
+ end
114
+ files = { "csv" => Faraday::UploadIO.new(file_path, mime_type, basename) }
115
+ body = {}
116
+ body["compression"] = "gzip" if gzip
121
117
 
122
118
  body["content_type"] = content_type unless content_type == FileFormat::UNIDIRECTIONAL
123
119
  body["callback_url"] = callback_url if callback_url
@@ -125,6 +121,16 @@ module Lara
125
121
  body: body, files: files).transform_keys(&:to_sym))
126
122
  end
127
123
 
124
+ # @deprecated Use #import_file instead.
125
+ def import_csv(id, csv_path, content_type: FileFormat::UNIDIRECTIONAL, gzip: false,
126
+ callback_url: nil)
127
+ warn "[DEPRECATION] import_csv is deprecated; use import_file instead.", uplevel: 1
128
+ unless [FileFormat::UNIDIRECTIONAL, FileFormat::MULTIDIRECTIONAL].include?(content_type)
129
+ raise ArgumentError, "import_csv only supports CSV formats; use import_file for TBX files."
130
+ end
131
+ import_file(id, csv_path, content_type: content_type, gzip: gzip, callback_url: callback_url)
132
+ end
133
+
128
134
  # @return [Lara::Models::GlossaryImport]
129
135
  def get_import_status(import_id)
130
136
  Lara::Models::GlossaryImport.new(**@client.get("/v2/glossaries/imports/#{import_id}").transform_keys(&:to_sym))
@@ -146,10 +152,10 @@ module Lara
146
152
  current
147
153
  end
148
154
 
149
- # Exports a csv file with the glossary content.
150
- # @param content_type [String] Either FileFormat::UNIDIRECTIONAL or FileFormat::MULTIDIRECTIONAL
151
- # @param source [String, nil] Optional source language
152
- # @return [String] bytes
155
+ # Exports the glossary in the requested format.
156
+ # @param content_type [String] One of the formats returned by FileFormat.all
157
+ # @param source [String, nil] Required for unidirectional CSV; omit for multidirectional CSV and TBX
158
+ # @return [String] Raw CSV or TBX content
153
159
  def export(id, content_type: FileFormat::UNIDIRECTIONAL, source: nil)
154
160
  unless FileFormat.valid?(content_type)
155
161
  raise ArgumentError, "Invalid content_type. Supported formats: #{FileFormat.all.join(', ')}"
@@ -160,8 +166,8 @@ module Lara
160
166
  end
161
167
 
162
168
  # @param callback_url [String] URL notified when the export is ready
163
- # @param content_type [String] Either FileFormat::UNIDIRECTIONAL or FileFormat::MULTIDIRECTIONAL
164
- # @param source [String, nil] Optional source language (unidirectional exports only)
169
+ # @param content_type [String] One of the formats returned by FileFormat.all
170
+ # @param source [String, nil] Required for unidirectional CSV; omit for multidirectional CSV and TBX
165
171
  # @return [Lara::Models::GlossaryExport]
166
172
  def export_async(id, callback_url:, content_type: FileFormat::UNIDIRECTIONAL, source: nil)
167
173
  unless FileFormat.valid?(content_type)
data/lib/lara/images.rb CHANGED
@@ -46,10 +46,12 @@ module Lara
46
46
  # @param glossaries [Array<String>, nil] Glossary IDs to apply.
47
47
  # @param style [String, nil] Translation style ("faithful", "fluid", "creative").
48
48
  # @param verbose [Boolean] If true, includes match details in the response.
49
+ # @param include_layout [Boolean] When true, includes complete geometry and styling on every
50
+ # paragraph independently of verbose.
49
51
  # @param no_trace [Boolean] If true, disables request tracing.
50
52
  # @return [Lara::Models::ImageTextResult]
51
53
  def translate_text(file_path:, target:, source: nil, adapt_to: nil, glossaries: nil,
52
- style: nil, verbose: false, no_trace: false)
54
+ style: nil, verbose: false, no_trace: false, include_layout: false)
53
55
  image_upload = Faraday::Multipart::FilePart.new(file_path, mime_type_for(file_path))
54
56
 
55
57
  body = {
@@ -58,7 +60,8 @@ module Lara
58
60
  adapt_to: adapt_to&.to_json,
59
61
  glossaries: glossaries&.to_json,
60
62
  style: style,
61
- verbose: verbose.to_s
63
+ verbose: verbose.to_s,
64
+ include_layout: include_layout.to_s
62
65
  }.compact
63
66
 
64
67
  headers = {}
@@ -69,6 +72,27 @@ module Lara
69
72
  Lara::Models::ImageTextResult.from_hash(result)
70
73
  end
71
74
 
75
+ # Renders supplied translations onto the original image without translating again.
76
+ # Overlay and inpainting require ImageLayoutParagraph instances. Generative models
77
+ # accept either ImageParagraph or ImageLayoutParagraph. An omitted model defaults
78
+ # to generative_fast.
79
+ # @param paragraphs [Array<Lara::Models::ImageParagraph>] Text and translations to render.
80
+ # @return [String] Binary image data.
81
+ def render_translated(file_path:, target:, paragraphs:, source: nil, model: nil, no_trace: false)
82
+ image_upload = Faraday::Multipart::FilePart.new(file_path, mime_type_for(file_path))
83
+ body = {
84
+ source: source,
85
+ target: target,
86
+ paragraphs: paragraphs.map(&:to_render_hash).to_json,
87
+ model: model
88
+ }.compact
89
+ headers = {}
90
+ headers["X-No-Trace"] = "true" if no_trace
91
+
92
+ @client.post("/v2/images/render-translated", body: body, files: { image: image_upload },
93
+ headers: headers, raw_response: true)
94
+ end
95
+
72
96
  private
73
97
 
74
98
  def mime_type_for(file_path)
data/lib/lara/memories.rb CHANGED
@@ -144,20 +144,14 @@ module Lara
144
144
  end
145
145
  end
146
146
 
147
+ # @param gzip [Boolean] Whether the supplied file is already gzip-compressed; defaults to false
147
148
  # @return [Lara::Models::MemoryImport]
148
- def import_tmx(id, tmx_path, callback_url: nil)
149
- require "stringio"
150
- require "zlib"
149
+ def import_tmx(id, tmx_path, gzip: false, callback_url: nil)
151
150
  basename = File.basename(tmx_path)
152
-
153
- buffer = StringIO.new
154
- gz = Zlib::GzipWriter.new(buffer, 7, Zlib::DEFAULT_STRATEGY)
155
- File.open(tmx_path, "rb") { |_f| IO.copy_stream(_f, gz) }
156
- gz.finish
157
- buffer.rewind
158
-
159
- files = { "tmx" => Faraday::UploadIO.new(buffer, "application/gzip", "#{basename}.gz") }
160
- body = { "compression" => "gzip" }
151
+ mime_type = gzip ? "application/gzip" : "application/xml"
152
+ files = { "tmx" => Faraday::UploadIO.new(tmx_path, mime_type, basename) }
153
+ body = {}
154
+ body["compression"] = "gzip" if gzip
161
155
  body["callback_url"] = callback_url if callback_url
162
156
  Lara::Models::MemoryImport.new(**@client.post("/v2/memories/#{id}/import",
163
157
  body: body, files: files).transform_keys(&:to_sym))
@@ -18,6 +18,51 @@ module Lara
18
18
  end
19
19
  end
20
20
 
21
+ # Four corners, each an [x, y] pair of pixel coordinates.
22
+ class ImageBBox < Base
23
+ attr_reader :top_left, :top_right, :bottom_right, :bottom_left
24
+
25
+ def initialize(top_left:, top_right:, bottom_right:, bottom_left:)
26
+ super()
27
+ @top_left = top_left
28
+ @top_right = top_right
29
+ @bottom_right = bottom_right
30
+ @bottom_left = bottom_left
31
+ end
32
+
33
+ def self.from_hash(hash)
34
+ new(top_left: hash["top_left"] || hash["topLeft"],
35
+ top_right: hash["top_right"] || hash["topRight"],
36
+ bottom_right: hash["bottom_right"] || hash["bottomRight"],
37
+ bottom_left: hash["bottom_left"] || hash["bottomLeft"])
38
+ end
39
+
40
+ def to_render_hash
41
+ { top_left: top_left, top_right: top_right, bottom_right: bottom_right, bottom_left: bottom_left }
42
+ end
43
+ end
44
+
45
+ # Original text direction (ltr, rtl, or ttb) and colors.
46
+ class ImageTextInfo < Base
47
+ attr_reader :direction, :text_color, :background_color
48
+
49
+ def initialize(direction:, text_color:, background_color:)
50
+ super()
51
+ @direction = direction
52
+ @text_color = text_color
53
+ @background_color = background_color
54
+ end
55
+
56
+ def self.from_hash(hash)
57
+ new(direction: hash["direction"], text_color: hash["text_color"] || hash["textColor"],
58
+ background_color: hash["background_color"] || hash["backgroundColor"])
59
+ end
60
+
61
+ def to_render_hash
62
+ { direction: direction, text_color: text_color, background_color: background_color }
63
+ end
64
+ end
65
+
21
66
  class ImageParagraph < Base
22
67
  attr_reader :text, :translation, :adapted_to_matches, :glossaries_matches
23
68
 
@@ -28,6 +73,29 @@ module Lara
28
73
  @adapted_to_matches = adapted_to_matches
29
74
  @glossaries_matches = glossaries_matches
30
75
  end
76
+
77
+ def to_render_hash
78
+ { text: text, translation: translation }
79
+ end
80
+ end
81
+
82
+ class ImageLayoutParagraph < ImageParagraph
83
+ attr_reader :bbox, :lines_bboxes, :text_info, :alignment
84
+
85
+ def initialize(text:, translation:, bbox:, lines_bboxes:, text_info:, alignment:,
86
+ adapted_to_matches: nil, glossaries_matches: nil)
87
+ super(text: text, translation: translation, adapted_to_matches: adapted_to_matches,
88
+ glossaries_matches: glossaries_matches)
89
+ @bbox = bbox
90
+ @lines_bboxes = lines_bboxes
91
+ @text_info = text_info
92
+ @alignment = alignment
93
+ end
94
+
95
+ def to_render_hash
96
+ super.merge(bbox: bbox.to_render_hash, lines_bboxes: lines_bboxes.map(&:to_render_hash),
97
+ text_info: text_info.to_render_hash, alignment: alignment)
98
+ end
31
99
  end
32
100
 
33
101
  class ImageTextResult < Base
@@ -39,8 +107,8 @@ module Lara
39
107
  paragraphs = (hash["paragraphs"] || []).map { |p| build_paragraph(p) }
40
108
 
41
109
  new(
42
- source_language: hash["sourceLanguage"],
43
- adapted_to: hash["adaptedTo"],
110
+ source_language: hash["source_language"] || hash["sourceLanguage"],
111
+ adapted_to: hash["adapted_to"] || hash["adaptedTo"],
44
112
  glossaries: hash["glossaries"],
45
113
  paragraphs: paragraphs
46
114
  )
@@ -50,15 +118,30 @@ module Lara
50
118
  private
51
119
 
52
120
  def build_paragraph(paragraph_hash)
53
- adapted_to_matches = convert_matches(paragraph_hash["adaptedToMatches"], NGMemoryMatch)
54
- glossaries_matches = convert_matches(paragraph_hash["glossariesMatches"], NGGlossaryMatch)
121
+ adapted_to_matches = convert_matches(
122
+ paragraph_hash["adapted_to_matches"] || paragraph_hash["adaptedToMatches"], NGMemoryMatch
123
+ )
124
+ glossaries_matches = convert_matches(
125
+ paragraph_hash["glossaries_matches"] || paragraph_hash["glossariesMatches"], NGGlossaryMatch
126
+ )
55
127
 
56
- ImageParagraph.new(
128
+ attributes = {
57
129
  text: paragraph_hash["text"],
58
130
  translation: paragraph_hash["translation"],
59
131
  adapted_to_matches: adapted_to_matches,
60
132
  glossaries_matches: glossaries_matches
61
- )
133
+ }
134
+ bbox = paragraph_hash["bbox"]
135
+ lines_bboxes = paragraph_hash["lines_bboxes"] || paragraph_hash["linesBboxes"]
136
+ text_info = paragraph_hash["text_info"] || paragraph_hash["textInfo"]
137
+ alignment = paragraph_hash["alignment"]
138
+ if bbox && lines_bboxes && text_info && alignment
139
+ ImageLayoutParagraph.new(**attributes, bbox: ImageBBox.from_hash(bbox),
140
+ lines_bboxes: lines_bboxes.map { |box| ImageBBox.from_hash(box) },
141
+ text_info: ImageTextInfo.from_hash(text_info), alignment: alignment)
142
+ else
143
+ ImageParagraph.new(**attributes)
144
+ end
62
145
  end
63
146
 
64
147
  def convert_matches(value, klass)
data/lib/lara/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lara
4
- VERSION = "1.7.0"
4
+ VERSION = "1.9.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lara-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Translated
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-31 00:00:00.000000000 Z
11
+ date: 2026-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday