lingodotdev 0.1.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.
@@ -0,0 +1,688 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe LingoDotDev::Engine do
6
+ let(:api_key) { ENV['LINGODOTDEV_API_KEY'] }
7
+ let(:api_url) { 'https://engine.lingo.dev' }
8
+ let(:target_locale) { 'es' }
9
+ let(:source_locale) { 'en' }
10
+
11
+ describe 'initialization' do
12
+ it 'creates an engine with valid api_key' do
13
+ engine = described_class.new(api_key: api_key)
14
+ expect(engine.config.api_key).to eq(api_key)
15
+ end
16
+
17
+ it 'creates engine with custom configuration' do
18
+ engine = described_class.new(
19
+ api_key: api_key,
20
+ api_url: 'https://custom.example.com',
21
+ batch_size: 50,
22
+ ideal_batch_item_size: 500
23
+ )
24
+ expect(engine.config.api_url).to eq('https://custom.example.com')
25
+ expect(engine.config.batch_size).to eq(50)
26
+ expect(engine.config.ideal_batch_item_size).to eq(500)
27
+ end
28
+
29
+ it 'allows block-based configuration' do
30
+ engine = described_class.new(api_key: api_key) do |config|
31
+ config.batch_size = 75
32
+ end
33
+ expect(engine.config.batch_size).to eq(75)
34
+ end
35
+ end
36
+
37
+ describe '#localize_text' do
38
+ it 'localizes text to target locale' do
39
+ engine = described_class.new(api_key: api_key)
40
+ result = engine.localize_text(
41
+ 'Hello world',
42
+ target_locale: target_locale
43
+ )
44
+ expect(result).to be_a(String)
45
+ expect(result.length).to be > 0
46
+ end
47
+
48
+ it 'localizes text with source locale specified' do
49
+ engine = described_class.new(api_key: api_key)
50
+ result = engine.localize_text(
51
+ 'Hello world',
52
+ target_locale: target_locale,
53
+ source_locale: source_locale
54
+ )
55
+ expect(result).to be_a(String)
56
+ expect(result.length).to be > 0
57
+ end
58
+
59
+ it 'localizes text with fast flag' do
60
+ engine = described_class.new(api_key: api_key)
61
+ result = engine.localize_text(
62
+ 'Hello world',
63
+ target_locale: target_locale,
64
+ fast: true
65
+ )
66
+ expect(result).to be_a(String)
67
+ expect(result.length).to be > 0
68
+ end
69
+
70
+ it 'localizes text with reference context' do
71
+ engine = described_class.new(api_key: api_key)
72
+ reference = { context: 'greeting' }
73
+ result = engine.localize_text(
74
+ 'Hello',
75
+ target_locale: target_locale,
76
+ reference: reference
77
+ )
78
+ expect(result).to be_a(String)
79
+ expect(result.length).to be > 0
80
+ end
81
+
82
+ it 'supports progress callback block' do
83
+ engine = described_class.new(api_key: api_key)
84
+ progress_updates = []
85
+ result = engine.localize_text(
86
+ 'Hello world',
87
+ target_locale: target_locale
88
+ ) { |progress| progress_updates << progress }
89
+ expect(result).to be_a(String)
90
+ expect(progress_updates).not_to be_empty if result.length > 0
91
+ end
92
+
93
+ it 'supports on_progress callback parameter' do
94
+ engine = described_class.new(api_key: api_key)
95
+ progress_updates = []
96
+ result = engine.localize_text(
97
+ 'Hello world',
98
+ target_locale: target_locale,
99
+ on_progress: proc { |progress| progress_updates << progress }
100
+ )
101
+ expect(result).to be_a(String)
102
+ end
103
+
104
+ it 'raises ValidationError when target_locale is nil' do
105
+ engine = described_class.new(api_key: api_key)
106
+ expect {
107
+ engine.localize_text('Hello', target_locale: nil)
108
+ }.to raise_error(LingoDotDev::ValidationError, /Target locale is required/)
109
+ end
110
+
111
+ it 'raises ValidationError when target_locale is empty' do
112
+ engine = described_class.new(api_key: api_key)
113
+ expect {
114
+ engine.localize_text('Hello', target_locale: '')
115
+ }.to raise_error(LingoDotDev::ValidationError, /Target locale is required/)
116
+ end
117
+
118
+ it 'raises ValidationError when text is nil' do
119
+ engine = described_class.new(api_key: api_key)
120
+ expect {
121
+ engine.localize_text(nil, target_locale: target_locale)
122
+ }.to raise_error(LingoDotDev::ValidationError, /Text cannot be nil/)
123
+ end
124
+ end
125
+
126
+ describe '#localize_object' do
127
+ it 'localizes a hash object to target locale' do
128
+ engine = described_class.new(api_key: api_key)
129
+ obj = { greeting: 'Hello', farewell: 'Goodbye' }
130
+ result = engine.localize_object(
131
+ obj,
132
+ target_locale: target_locale
133
+ )
134
+ expect(result).to be_a(Hash)
135
+ expect(result.keys).to include('greeting', 'farewell')
136
+ end
137
+
138
+ it 'localizes object with source locale' do
139
+ engine = described_class.new(api_key: api_key)
140
+ obj = { message: 'Hello world' }
141
+ result = engine.localize_object(
142
+ obj,
143
+ target_locale: target_locale,
144
+ source_locale: source_locale
145
+ )
146
+ expect(result).to be_a(Hash)
147
+ end
148
+
149
+ it 'localizes object with fast flag' do
150
+ engine = described_class.new(api_key: api_key)
151
+ obj = { greeting: 'Hi' }
152
+ result = engine.localize_object(
153
+ obj,
154
+ target_locale: target_locale,
155
+ fast: true
156
+ )
157
+ expect(result).to be_a(Hash)
158
+ end
159
+
160
+ it 'supports progress callback for objects' do
161
+ engine = described_class.new(api_key: api_key)
162
+ obj = { text: 'Hello' }
163
+ progress_updates = []
164
+ result = engine.localize_object(
165
+ obj,
166
+ target_locale: target_locale
167
+ ) { |progress| progress_updates << progress }
168
+ expect(result).to be_a(Hash)
169
+ end
170
+
171
+ it 'raises ValidationError when target_locale is nil' do
172
+ engine = described_class.new(api_key: api_key)
173
+ expect {
174
+ engine.localize_object({ text: 'Hello' }, target_locale: nil)
175
+ }.to raise_error(LingoDotDev::ValidationError, /Target locale is required/)
176
+ end
177
+
178
+ it 'raises ValidationError when object is nil' do
179
+ engine = described_class.new(api_key: api_key)
180
+ expect {
181
+ engine.localize_object(nil, target_locale: target_locale)
182
+ }.to raise_error(LingoDotDev::ValidationError, /Object cannot be nil/)
183
+ end
184
+
185
+ it 'raises ValidationError when object is not a Hash' do
186
+ engine = described_class.new(api_key: api_key)
187
+ expect {
188
+ engine.localize_object('not a hash', target_locale: target_locale)
189
+ }.to raise_error(LingoDotDev::ValidationError, /Object must be a Hash/)
190
+ end
191
+ end
192
+
193
+ describe '#localize_chat' do
194
+ it 'localizes chat messages to target locale' do
195
+ engine = described_class.new(api_key: api_key)
196
+ chat = [
197
+ { name: 'user', text: 'Hello!' },
198
+ { name: 'assistant', text: 'Hi there!' }
199
+ ]
200
+ result = engine.localize_chat(
201
+ chat,
202
+ target_locale: target_locale
203
+ )
204
+ expect(result).to be_an(Array)
205
+ expect(result.length).to eq(2)
206
+ end
207
+
208
+ it 'localizes chat with source locale' do
209
+ engine = described_class.new(api_key: api_key)
210
+ chat = [{ name: 'user', text: 'Hello' }]
211
+ result = engine.localize_chat(
212
+ chat,
213
+ target_locale: target_locale,
214
+ source_locale: source_locale
215
+ )
216
+ expect(result).to be_an(Array)
217
+ end
218
+
219
+ it 'localizes chat with fast flag' do
220
+ engine = described_class.new(api_key: api_key)
221
+ chat = [{ name: 'user', text: 'Hi' }]
222
+ result = engine.localize_chat(
223
+ chat,
224
+ target_locale: target_locale,
225
+ fast: true
226
+ )
227
+ expect(result).to be_an(Array)
228
+ end
229
+
230
+ it 'supports progress callback for chat' do
231
+ engine = described_class.new(api_key: api_key)
232
+ chat = [{ name: 'user', text: 'Hello' }]
233
+ progress_updates = []
234
+ result = engine.localize_chat(
235
+ chat,
236
+ target_locale: target_locale
237
+ ) { |progress| progress_updates << progress }
238
+ expect(result).to be_an(Array)
239
+ end
240
+
241
+ it 'raises ValidationError when target_locale is nil' do
242
+ engine = described_class.new(api_key: api_key)
243
+ expect {
244
+ engine.localize_chat([], target_locale: nil)
245
+ }.to raise_error(LingoDotDev::ValidationError, /Target locale is required/)
246
+ end
247
+
248
+ it 'raises ValidationError when chat is nil' do
249
+ engine = described_class.new(api_key: api_key)
250
+ expect {
251
+ engine.localize_chat(nil, target_locale: target_locale)
252
+ }.to raise_error(LingoDotDev::ValidationError, /Chat cannot be nil/)
253
+ end
254
+
255
+ it 'raises ValidationError when chat is not an Array' do
256
+ engine = described_class.new(api_key: api_key)
257
+ expect {
258
+ engine.localize_chat({}, target_locale: target_locale)
259
+ }.to raise_error(LingoDotDev::ValidationError, /Chat must be an Array/)
260
+ end
261
+
262
+ it 'raises ValidationError when chat messages lack name' do
263
+ engine = described_class.new(api_key: api_key)
264
+ expect {
265
+ engine.localize_chat(
266
+ [{ text: 'Hello' }],
267
+ target_locale: target_locale
268
+ )
269
+ }.to raise_error(LingoDotDev::ValidationError, /:name and :text keys/)
270
+ end
271
+
272
+ it 'raises ValidationError when chat messages lack text' do
273
+ engine = described_class.new(api_key: api_key)
274
+ expect {
275
+ engine.localize_chat(
276
+ [{ name: 'user' }],
277
+ target_locale: target_locale
278
+ )
279
+ }.to raise_error(LingoDotDev::ValidationError, /:name and :text keys/)
280
+ end
281
+ end
282
+
283
+ describe '#localize_html' do
284
+ it 'correctly extracts, localizes, and reconstructs HTML content' do
285
+ input_html = <<~HTML.strip
286
+ <!DOCTYPE html>
287
+ <html>
288
+ <head>
289
+ <title>Test Page</title>
290
+ <meta name="description" content="Page description">
291
+ </head>
292
+ <body>
293
+ standalone text
294
+ <div>
295
+ <h1>Hello World</h1>
296
+ <p>
297
+ This is a paragraph with
298
+ <a href="/test" title="Link title">a link</a>
299
+ and an
300
+ <img src="/test.jpg" alt="Test image">
301
+ and some <b>bold <i>and italic</i></b> text.
302
+ </p>
303
+ <script>
304
+ const doNotTranslate = "this text should be ignored";
305
+ </script>
306
+ <input type="text" placeholder="Enter text">
307
+ </div>
308
+ </body>
309
+ </html>
310
+ HTML
311
+
312
+ engine = described_class.new(api_key: api_key)
313
+ extracted_content = nil
314
+ call_params = nil
315
+
316
+ allow(engine).to receive(:localize_raw) do |content, params, &block|
317
+ extracted_content = content
318
+ call_params = params
319
+ localized = {}
320
+ content.each do |key, value|
321
+ localized[key] = "ES:#{value}"
322
+ end
323
+ localized
324
+ end
325
+
326
+ result = engine.localize_html(input_html, target_locale: 'es', source_locale: 'en')
327
+
328
+ expect(call_params[:target_locale]).to eq('es')
329
+ expect(call_params[:source_locale]).to eq('en')
330
+ expect(extracted_content).to include(
331
+ 'head/0/0' => 'Test Page',
332
+ 'head/1#content' => 'Page description',
333
+ 'body/0' => 'standalone text',
334
+ 'body/1/0/0' => 'Hello World',
335
+ 'body/1/1/0' => 'This is a paragraph with',
336
+ 'body/1/1/1#title' => 'Link title',
337
+ 'body/1/1/1/0' => 'a link',
338
+ 'body/1/1/2' => 'and an',
339
+ 'body/1/1/3#alt' => 'Test image',
340
+ 'body/1/1/4' => 'and some',
341
+ 'body/1/1/5/0' => 'bold',
342
+ 'body/1/1/5/1/0' => 'and italic',
343
+ 'body/1/1/6' => 'text.',
344
+ 'body/1/3#placeholder' => 'Enter text'
345
+ )
346
+
347
+ expect(result).to include('lang="es"')
348
+ expect(result).to include('<title>ES:Test Page</title>')
349
+ expect(result).to include('content="ES:Page description"')
350
+ expect(result).to include('>ES:standalone text<')
351
+ expect(result).to include('<h1>ES:Hello World</h1>')
352
+ expect(result).to include('title="ES:Link title"')
353
+ expect(result).to include('alt="ES:Test image"')
354
+ expect(result).to include('placeholder="ES:Enter text"')
355
+ expect(result).to include('const doNotTranslate = "this text should be ignored"')
356
+ end
357
+
358
+ it 'localizes HTML with source locale' do
359
+ html = '<html><head><title>Hello</title></head><body><p>World</p></body></html>'
360
+ engine = described_class.new(api_key: api_key)
361
+ call_params = nil
362
+ allow(engine).to receive(:localize_raw) do |content, params, &block|
363
+ call_params = params
364
+ { 'head/0/0' => 'Hola', 'body/0/0' => 'Mundo' }
365
+ end
366
+
367
+ result = engine.localize_html(html, target_locale: 'es', source_locale: 'en')
368
+
369
+ expect(call_params[:source_locale]).to eq('en')
370
+ expect(call_params[:target_locale]).to eq('es')
371
+ expect(result).to include('lang="es"')
372
+ end
373
+
374
+ it 'localizes HTML with fast flag' do
375
+ html = '<html><head><title>Hello</title></head><body><p>World</p></body></html>'
376
+ engine = described_class.new(api_key: api_key)
377
+ call_params = nil
378
+ allow(engine).to receive(:localize_raw) do |content, params, &block|
379
+ call_params = params
380
+ { 'head/0/0' => 'Hola', 'body/0/0' => 'Mundo' }
381
+ end
382
+
383
+ result = engine.localize_html(html, target_locale: 'es', fast: true)
384
+
385
+ expect(call_params[:fast]).to eq(true)
386
+ end
387
+
388
+ it 'supports progress callback for HTML' do
389
+ html = '<html><head><title>Hello</title></head><body><p>World</p></body></html>'
390
+ engine = described_class.new(api_key: api_key)
391
+ progress_updates = []
392
+ allow(engine).to receive(:localize_raw) do |content, params, &block|
393
+ block&.call(100, {}, {})
394
+ { 'head/0/0' => 'Hola', 'body/0/0' => 'Mundo' }
395
+ end
396
+
397
+ result = engine.localize_html(html, target_locale: 'es') { |progress| progress_updates << progress }
398
+
399
+ expect(progress_updates).not_to be_empty
400
+ end
401
+
402
+ it 'raises ValidationError when target_locale is nil' do
403
+ engine = described_class.new(api_key: api_key)
404
+ expect {
405
+ engine.localize_html('<html></html>', target_locale: nil)
406
+ }.to raise_error(LingoDotDev::ValidationError, /Target locale is required/)
407
+ end
408
+
409
+ it 'raises ValidationError when target_locale is empty' do
410
+ engine = described_class.new(api_key: api_key)
411
+ expect {
412
+ engine.localize_html('<html></html>', target_locale: '')
413
+ }.to raise_error(LingoDotDev::ValidationError, /Target locale is required/)
414
+ end
415
+
416
+ it 'raises ValidationError when html is nil' do
417
+ engine = described_class.new(api_key: api_key)
418
+ expect {
419
+ engine.localize_html(nil, target_locale: 'es')
420
+ }.to raise_error(LingoDotDev::ValidationError, /HTML cannot be nil/)
421
+ end
422
+ end
423
+
424
+ describe '#batch_localize_text' do
425
+ it 'batch localizes text to multiple locales' do
426
+ engine = described_class.new(api_key: api_key)
427
+ results = engine.batch_localize_text(
428
+ 'Hello world',
429
+ target_locales: ['es', 'fr']
430
+ )
431
+ expect(results).to be_an(Array)
432
+ expect(results.length).to eq(2)
433
+ expect(results.all? { |r| r.is_a?(String) }).to be true
434
+ end
435
+
436
+ it 'batch localizes with source locale' do
437
+ engine = described_class.new(api_key: api_key)
438
+ results = engine.batch_localize_text(
439
+ 'Hello',
440
+ target_locales: ['es', 'de'],
441
+ source_locale: source_locale
442
+ )
443
+ expect(results).to be_an(Array)
444
+ expect(results.length).to eq(2)
445
+ end
446
+
447
+ it 'batch localizes with fast flag' do
448
+ engine = described_class.new(api_key: api_key)
449
+ results = engine.batch_localize_text(
450
+ 'Hi',
451
+ target_locales: ['es', 'fr'],
452
+ fast: true
453
+ )
454
+ expect(results).to be_an(Array)
455
+ expect(results.length).to eq(2)
456
+ end
457
+
458
+ it 'batch localizes concurrently' do
459
+ engine = described_class.new(api_key: api_key)
460
+ results = engine.batch_localize_text(
461
+ 'Hello world',
462
+ target_locales: ['es', 'fr', 'de'],
463
+ concurrent: true
464
+ )
465
+ expect(results).to be_an(Array)
466
+ expect(results.length).to eq(3)
467
+ end
468
+
469
+ it 'raises ValidationError when text is nil' do
470
+ engine = described_class.new(api_key: api_key)
471
+ expect {
472
+ engine.batch_localize_text(nil, target_locales: ['es'])
473
+ }.to raise_error(LingoDotDev::ValidationError, /Text cannot be nil/)
474
+ end
475
+
476
+ it 'raises ValidationError when target_locales is not an Array' do
477
+ engine = described_class.new(api_key: api_key)
478
+ expect {
479
+ engine.batch_localize_text('Hello', target_locales: 'es')
480
+ }.to raise_error(LingoDotDev::ValidationError, /Target locales must be an Array/)
481
+ end
482
+
483
+ it 'raises ValidationError when target_locales is empty' do
484
+ engine = described_class.new(api_key: api_key)
485
+ expect {
486
+ engine.batch_localize_text('Hello', target_locales: [])
487
+ }.to raise_error(LingoDotDev::ValidationError, /Target locales cannot be empty/)
488
+ end
489
+ end
490
+
491
+ describe '#batch_localize_objects' do
492
+ it 'batch localizes multiple objects to same locale' do
493
+ engine = described_class.new(api_key: api_key)
494
+ objects = [
495
+ { greeting: 'Hello' },
496
+ { farewell: 'Goodbye' }
497
+ ]
498
+ results = engine.batch_localize_objects(
499
+ objects,
500
+ target_locale: target_locale
501
+ )
502
+ expect(results).to be_an(Array)
503
+ expect(results.length).to eq(2)
504
+ expect(results.all? { |r| r.is_a?(Hash) }).to be true
505
+ end
506
+
507
+ it 'batch localizes objects with source locale' do
508
+ engine = described_class.new(api_key: api_key)
509
+ objects = [{ text: 'Hello' }]
510
+ results = engine.batch_localize_objects(
511
+ objects,
512
+ target_locale: target_locale,
513
+ source_locale: source_locale
514
+ )
515
+ expect(results).to be_an(Array)
516
+ end
517
+
518
+ it 'batch localizes objects with fast flag' do
519
+ engine = described_class.new(api_key: api_key)
520
+ objects = [{ text: 'Hi' }]
521
+ results = engine.batch_localize_objects(
522
+ objects,
523
+ target_locale: target_locale,
524
+ fast: true
525
+ )
526
+ expect(results).to be_an(Array)
527
+ end
528
+
529
+ it 'batch localizes objects concurrently' do
530
+ engine = described_class.new(api_key: api_key)
531
+ objects = [
532
+ { text: 'Hello' },
533
+ { text: 'Hi' },
534
+ { text: 'Hey' }
535
+ ]
536
+ results = engine.batch_localize_objects(
537
+ objects,
538
+ target_locale: target_locale,
539
+ concurrent: true
540
+ )
541
+ expect(results).to be_an(Array)
542
+ expect(results.length).to eq(3)
543
+ end
544
+
545
+ it 'raises ValidationError when objects is not an Array' do
546
+ engine = described_class.new(api_key: api_key)
547
+ expect {
548
+ engine.batch_localize_objects(
549
+ { text: 'Hello' },
550
+ target_locale: target_locale
551
+ )
552
+ }.to raise_error(LingoDotDev::ValidationError, /Objects must be an Array/)
553
+ end
554
+
555
+ it 'raises ValidationError when objects is empty' do
556
+ engine = described_class.new(api_key: api_key)
557
+ expect {
558
+ engine.batch_localize_objects([], target_locale: target_locale)
559
+ }.to raise_error(LingoDotDev::ValidationError, /Objects cannot be empty/)
560
+ end
561
+
562
+ it 'raises ValidationError when target_locale is nil' do
563
+ engine = described_class.new(api_key: api_key)
564
+ expect {
565
+ engine.batch_localize_objects(
566
+ [{ text: 'Hello' }],
567
+ target_locale: nil
568
+ )
569
+ }.to raise_error(LingoDotDev::ValidationError, /Target locale is required/)
570
+ end
571
+
572
+ it 'raises ValidationError when object is not a Hash' do
573
+ engine = described_class.new(api_key: api_key)
574
+ expect {
575
+ engine.batch_localize_objects(
576
+ ['not a hash'],
577
+ target_locale: target_locale
578
+ )
579
+ }.to raise_error(LingoDotDev::ValidationError, /Each object must be a Hash/)
580
+ end
581
+ end
582
+
583
+ describe '#recognize_locale' do
584
+ it 'recognizes locale of given text' do
585
+ engine = described_class.new(api_key: api_key)
586
+ locale = engine.recognize_locale('Hello world')
587
+ expect(locale).to be_a(String)
588
+ expect(locale.length).to be > 0
589
+ end
590
+
591
+ it 'raises ValidationError when text is nil' do
592
+ engine = described_class.new(api_key: api_key)
593
+ expect {
594
+ engine.recognize_locale(nil)
595
+ }.to raise_error(LingoDotDev::ValidationError, /Text cannot be empty/)
596
+ end
597
+
598
+ it 'raises ValidationError when text is empty' do
599
+ engine = described_class.new(api_key: api_key)
600
+ expect {
601
+ engine.recognize_locale('')
602
+ }.to raise_error(LingoDotDev::ValidationError, /Text cannot be empty/)
603
+ end
604
+
605
+ it 'raises ValidationError when text is only whitespace' do
606
+ engine = described_class.new(api_key: api_key)
607
+ expect {
608
+ engine.recognize_locale(' ')
609
+ }.to raise_error(LingoDotDev::ValidationError, /Text cannot be empty/)
610
+ end
611
+ end
612
+
613
+ describe '#whoami' do
614
+ it 'returns user information' do
615
+ engine = described_class.new(api_key: api_key)
616
+ result = engine.whoami
617
+ if result
618
+ expect(result).to be_a(Hash)
619
+ expect(result).to include(:email, :id)
620
+ end
621
+ end
622
+ end
623
+
624
+ describe '.quick_translate' do
625
+ it 'quickly translates a string' do
626
+ result = described_class.quick_translate(
627
+ 'Hello world',
628
+ api_key: api_key,
629
+ target_locale: target_locale
630
+ )
631
+ expect(result).to be_a(String)
632
+ expect(result.length).to be > 0
633
+ end
634
+
635
+ it 'quickly translates a hash with concurrent processing' do
636
+ result = described_class.quick_translate(
637
+ { greeting: 'Hello', farewell: 'Goodbye' },
638
+ api_key: api_key,
639
+ target_locale: target_locale
640
+ )
641
+ expect(result).to be_a(Hash)
642
+ end
643
+
644
+ it 'raises ValidationError for invalid content type' do
645
+ expect {
646
+ described_class.quick_translate(
647
+ 123,
648
+ api_key: api_key,
649
+ target_locale: target_locale
650
+ )
651
+ }.to raise_error(LingoDotDev::ValidationError, /Content must be a String or Hash/)
652
+ end
653
+ end
654
+
655
+ describe '.quick_batch_translate' do
656
+ it 'quickly batch translates a string to multiple locales' do
657
+ results = described_class.quick_batch_translate(
658
+ 'Hello',
659
+ api_key: api_key,
660
+ target_locales: ['es', 'fr']
661
+ )
662
+ expect(results).to be_an(Array)
663
+ expect(results.length).to eq(2)
664
+ expect(results.all? { |r| r.is_a?(String) }).to be true
665
+ end
666
+
667
+ it 'quickly batch translates a hash to multiple locales' do
668
+ results = described_class.quick_batch_translate(
669
+ { greeting: 'Hello' },
670
+ api_key: api_key,
671
+ target_locales: ['es', 'fr']
672
+ )
673
+ expect(results).to be_an(Array)
674
+ expect(results.length).to eq(2)
675
+ expect(results.all? { |r| r.is_a?(Hash) }).to be true
676
+ end
677
+
678
+ it 'raises ValidationError for invalid content type' do
679
+ expect {
680
+ described_class.quick_batch_translate(
681
+ 123,
682
+ api_key: api_key,
683
+ target_locales: ['es']
684
+ )
685
+ }.to raise_error(LingoDotDev::ValidationError, /Content must be a String or Hash/)
686
+ end
687
+ end
688
+ end