mistral_translator 0.1.0 → 0.2.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +189 -121
  4. data/README_TESTING.md +33 -0
  5. data/SECURITY.md +157 -0
  6. data/docs/.nojekyll +2 -0
  7. data/docs/404.html +30 -0
  8. data/docs/README.md +153 -0
  9. data/docs/advanced-usage/batch-processing.md +158 -0
  10. data/docs/advanced-usage/error-handling.md +106 -0
  11. data/docs/advanced-usage/monitoring.md +133 -0
  12. data/docs/advanced-usage/summarization.md +86 -0
  13. data/docs/advanced-usage/translations.md +141 -0
  14. data/docs/api-reference/callbacks.md +231 -0
  15. data/docs/api-reference/configuration.md +74 -0
  16. data/docs/api-reference/errors.md +673 -0
  17. data/docs/api-reference/methods.md +539 -0
  18. data/docs/getting-started.md +179 -0
  19. data/docs/index.html +27 -0
  20. data/docs/installation.md +142 -0
  21. data/docs/migration-0.1.0-to-0.2.0.md +61 -0
  22. data/docs/rails-integration/adapters.md +84 -0
  23. data/docs/rails-integration/controllers.md +107 -0
  24. data/docs/rails-integration/jobs.md +97 -0
  25. data/docs/rails-integration/setup.md +339 -0
  26. data/examples/basic_usage.rb +129 -102
  27. data/examples/batch-job.rb +511 -0
  28. data/examples/monitoring-setup.rb +499 -0
  29. data/examples/rails-model.rb +399 -0
  30. data/lib/mistral_translator/adapters.rb +261 -0
  31. data/lib/mistral_translator/client.rb +103 -100
  32. data/lib/mistral_translator/client_helpers.rb +161 -0
  33. data/lib/mistral_translator/configuration.rb +171 -1
  34. data/lib/mistral_translator/errors.rb +16 -0
  35. data/lib/mistral_translator/helpers.rb +292 -0
  36. data/lib/mistral_translator/helpers_extensions.rb +150 -0
  37. data/lib/mistral_translator/levenshtein_helpers.rb +40 -0
  38. data/lib/mistral_translator/logger.rb +28 -4
  39. data/lib/mistral_translator/prompt_builder.rb +93 -41
  40. data/lib/mistral_translator/prompt_helpers.rb +83 -0
  41. data/lib/mistral_translator/prompt_metadata_helpers.rb +42 -0
  42. data/lib/mistral_translator/response_parser.rb +194 -23
  43. data/lib/mistral_translator/security.rb +72 -0
  44. data/lib/mistral_translator/summarizer.rb +41 -2
  45. data/lib/mistral_translator/translator.rb +174 -98
  46. data/lib/mistral_translator/translator_helpers.rb +268 -0
  47. data/lib/mistral_translator/version.rb +1 -1
  48. data/lib/mistral_translator.rb +51 -25
  49. metadata +39 -3
@@ -0,0 +1,511 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Exemple de traitement par batch avec MistralTranslator
5
+ # Usage: ruby examples/batch-job.rb
6
+
7
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
8
+ require "mistral_translator"
9
+
10
+ # Configuration
11
+ MistralTranslator.configure do |config|
12
+ config.api_key = ENV["MISTRAL_API_KEY"] || "your_api_key_here"
13
+ config.enable_metrics = true
14
+ config.retry_delays = [2, 4, 8, 16]
15
+ end
16
+
17
+ # === EXEMPLE 1: Traduction de fichiers CSV ===
18
+
19
+ require "csv"
20
+ require "fileutils"
21
+
22
+ class CSVTranslationBatch
23
+ def initialize(input_file, output_file, from:, to:, text_columns: [])
24
+ @input_file = input_file
25
+ @output_file = output_file
26
+ @from = from
27
+ @to = to
28
+ @text_columns = text_columns
29
+ @batch_size = 10
30
+ end
31
+
32
+ def process!
33
+ rows = CSV.read(@input_file, headers: true)
34
+ puts "📄 Processing #{rows.size} rows from #{@input_file}"
35
+
36
+ translated_rows = []
37
+
38
+ total_batches = (rows.size.to_f / @batch_size).ceil
39
+ rows.each_slice(@batch_size).with_index do |batch, index|
40
+ puts "🔄 Processing batch of #{batch.size} rows..."
41
+ translated_rows.concat(process_batch(batch))
42
+
43
+ # Rate limiting uniquement entre les batches
44
+ sleep(2) if index < total_batches - 1
45
+ end
46
+
47
+ # Écriture du fichier de sortie
48
+ CSV.open(@output_file, "w", headers: true) do |csv|
49
+ # Headers
50
+ csv << translated_rows.first.headers if translated_rows.any?
51
+
52
+ # Data
53
+ translated_rows.each { |row| csv << row }
54
+ end
55
+
56
+ puts "✅ Traduction terminée: #{@output_file}"
57
+ end
58
+
59
+ private
60
+
61
+ def process_batch(batch)
62
+ batch.map do |row|
63
+ translated_row = row.dup
64
+
65
+ @text_columns.each do |column|
66
+ text = row[column]
67
+ next if text.nil? || text.empty?
68
+
69
+ begin
70
+ translated = MistralTranslator.translate(text, from: @from, to: @to)
71
+ translated_row["#{column}_#{@to}"] = translated
72
+ rescue MistralTranslator::Error => e
73
+ puts "❌ Error translating row #{row.to_h}: #{e.message}"
74
+ translated_row["#{column}_#{@to}"] = "[TRANSLATION_ERROR]"
75
+ end
76
+ end
77
+
78
+ translated_row
79
+ end
80
+ end
81
+ end
82
+
83
+ # === EXEMPLE 2: Job de traduction avec queue ===
84
+
85
+ class TranslationQueue
86
+ def initialize
87
+ @queue = []
88
+ @results = {}
89
+ @workers = 3
90
+ @batch_size = 5
91
+ end
92
+
93
+ def add_job(id, text, from:, to:, context: nil)
94
+ @queue << {
95
+ id: id,
96
+ text: text,
97
+ from: from,
98
+ to: to,
99
+ context: context,
100
+ status: :pending
101
+ }
102
+ end
103
+
104
+ def process_all!
105
+ puts "🚀 Starting translation queue with #{@workers} workers"
106
+ puts "📋 #{@queue.size} jobs to process"
107
+
108
+ threads = []
109
+ job_chunks = @queue.each_slice(@batch_size).to_a
110
+
111
+ @workers.times do |worker_id|
112
+ threads << Thread.new do
113
+ process_worker_jobs(worker_id, job_chunks)
114
+ end
115
+ end
116
+
117
+ threads.each(&:join)
118
+
119
+ # Résultats
120
+ successful = @results.values.count { |r| r[:status] == :success }
121
+ failed = @results.values.count { |r| r[:status] == :error }
122
+
123
+ puts "\n📊 Results:"
124
+ puts "✅ Successful: #{successful}"
125
+ puts "❌ Failed: #{failed}"
126
+ puts "📈 Success rate: #{(successful.to_f / @queue.size * 100).round(1)}%"
127
+
128
+ @results
129
+ end
130
+
131
+ private
132
+
133
+ def process_worker_jobs(worker_id, job_chunks)
134
+ my_chunks = job_chunks.select.with_index { |_, i| i % @workers == worker_id }
135
+
136
+ my_chunks.each do |chunk|
137
+ puts "🔧 Worker #{worker_id} processing #{chunk.size} jobs"
138
+
139
+ chunk.each do |job|
140
+ process_single_job(worker_id, job)
141
+ end
142
+
143
+ # Rate limiting entre les chunks
144
+ sleep(1)
145
+ end
146
+ end
147
+
148
+ def process_single_job(worker_id, job)
149
+ translated = MistralTranslator.translate(
150
+ job[:text],
151
+ from: job[:from],
152
+ to: job[:to],
153
+ context: job[:context]
154
+ )
155
+
156
+ @results[job[:id]] = {
157
+ status: :success,
158
+ original: job[:text],
159
+ translated: translated,
160
+ worker: worker_id
161
+ }
162
+
163
+ puts "✅ Worker #{worker_id}: Job #{job[:id]} completed"
164
+ rescue MistralTranslator::Error => e
165
+ @results[job[:id]] = {
166
+ status: :error,
167
+ original: job[:text],
168
+ error: e.message,
169
+ worker: worker_id
170
+ }
171
+
172
+ puts "❌ Worker #{worker_id}: Job #{job[:id]} failed - #{e.message}"
173
+ end
174
+ end
175
+
176
+ # === EXEMPLE 3: Traitement de fichiers JSON ===
177
+
178
+ require "json"
179
+
180
+ class JSONBatchProcessor
181
+ def initialize(input_file, output_file)
182
+ @input_file = input_file
183
+ @output_file = output_file
184
+ end
185
+
186
+ def translate_nested_json(from:, to:)
187
+ data = JSON.parse(File.read(@input_file))
188
+ puts "📄 Processing JSON file: #{@input_file}"
189
+
190
+ translated_data = translate_recursive(data, from, to)
191
+
192
+ File.write(@output_file, JSON.pretty_generate(translated_data))
193
+ puts "✅ Translated JSON saved: #{@output_file}"
194
+
195
+ translated_data
196
+ end
197
+
198
+ private
199
+
200
+ def translate_recursive(obj, from, to, path = [])
201
+ case obj
202
+ when Hash
203
+ result = {}
204
+ obj.each do |key, value|
205
+ current_path = path + [key]
206
+
207
+ if translatable_key?(key) && value.is_a?(String)
208
+ puts "🔄 Translating #{current_path.join(".")}: #{value[0..50]}..."
209
+
210
+ begin
211
+ result[key] = MistralTranslator.translate(value, from: from, to: to)
212
+ result["#{key}_original"] = value # Garder l'original
213
+ rescue MistralTranslator::Error => e
214
+ puts "❌ Translation failed for #{current_path.join(".")}: #{e.message}"
215
+ result[key] = value # Garder l'original en cas d'erreur
216
+ end
217
+ else
218
+ result[key] = translate_recursive(value, from, to, current_path)
219
+ end
220
+ end
221
+ result
222
+
223
+ when Array
224
+ obj.map.with_index do |item, index|
225
+ translate_recursive(item, from, to, path + [index])
226
+ end
227
+
228
+ else
229
+ obj # Primitive values
230
+ end
231
+ end
232
+
233
+ def translatable_key?(key)
234
+ # Keys qui contiennent du texte à traduire
235
+ %w[title description content text message label name summary].include?(key.to_s.downcase)
236
+ end
237
+ end
238
+
239
+ # === EXEMPLE 4: Batch avec retry et monitoring ===
240
+
241
+ class RobustBatchProcessor
242
+ def initialize(items, from:, to:)
243
+ @items = items
244
+ @from = from
245
+ @to = to
246
+ @results = {}
247
+ @errors = {}
248
+ @retry_queue = []
249
+ end
250
+
251
+ def process_with_monitoring!
252
+ puts "🚀 Starting robust batch processing"
253
+ puts "📊 Items: #{@items.size}"
254
+
255
+ # Première passe
256
+ process_initial_batch
257
+
258
+ # Retry des éléments échoués
259
+ retry_failed_items if @retry_queue.any?
260
+
261
+ # Rapport final
262
+ generate_report
263
+
264
+ @results
265
+ end
266
+
267
+ private
268
+
269
+ def process_initial_batch
270
+ @items.each_with_index do |item, index|
271
+ print_progress(index + 1, @items.size)
272
+
273
+ begin
274
+ result = MistralTranslator.translate(item, from: @from, to: @to)
275
+ @results[index] = result
276
+ rescue MistralTranslator::RateLimitError
277
+ puts "\n⏳ Rate limit hit, waiting..."
278
+ sleep(30)
279
+ @retry_queue << { index: index, item: item, attempts: 1 }
280
+ rescue MistralTranslator::Error => e
281
+ @errors[index] = e.message
282
+ @retry_queue << { index: index, item: item, attempts: 1 }
283
+ end
284
+
285
+ # Rate limiting
286
+ sleep(0.5)
287
+ end
288
+
289
+ puts "\n✅ Initial batch completed"
290
+ end
291
+
292
+ def retry_failed_items
293
+ puts "🔄 Retrying #{@retry_queue.size} failed items..."
294
+
295
+ @retry_queue.each do |retry_item|
296
+ next if retry_item[:attempts] >= 3
297
+
298
+ begin
299
+ result = MistralTranslator.translate(retry_item[:item], from: @from, to: @to)
300
+ @results[retry_item[:index]] = result
301
+ puts "✅ Retry successful for item #{retry_item[:index]}"
302
+ rescue MistralTranslator::Error => e
303
+ retry_item[:attempts] += 1
304
+ @errors[retry_item[:index]] = "#{e.message} (#{retry_item[:attempts]} attempts)"
305
+ puts "❌ Retry failed for item #{retry_item[:index]}: #{e.message}"
306
+ end
307
+
308
+ sleep(2) # Plus de délai pour les retries
309
+ end
310
+ end
311
+
312
+ def print_progress(current, total)
313
+ percentage = (current.to_f / total * 100).round(1)
314
+ print "\r🔄 Progress: #{current}/#{total} (#{percentage}%)"
315
+ end
316
+
317
+ def generate_report
318
+ totals = compute_totals
319
+ print_header_and_totals(totals)
320
+ print_translator_metrics_if_enabled
321
+ print_top_errors
322
+ end
323
+
324
+ def compute_totals
325
+ successful = @results.size
326
+ failed = @errors.size
327
+ total = @items.size
328
+ {
329
+ successful: successful,
330
+ failed: failed,
331
+ total: total,
332
+ success_rate: percentage(successful, total),
333
+ fail_rate: percentage(failed, total)
334
+ }
335
+ end
336
+
337
+ def percentage(count, total)
338
+ return 0.0 if total.zero?
339
+
340
+ (count.to_f / total * 100).round(1)
341
+ end
342
+
343
+ def print_header_and_totals(totals)
344
+ puts "\n\n📊 BATCH PROCESSING REPORT"
345
+ puts "=" * 40
346
+ puts "Total items: #{totals[:total]}"
347
+ puts "Successful: #{totals[:successful]} (#{totals[:success_rate]}%)"
348
+ puts "Failed: #{totals[:failed]} (#{totals[:fail_rate]}%)"
349
+ end
350
+
351
+ def print_translator_metrics_if_enabled
352
+ return unless MistralTranslator.configuration.enable_metrics
353
+
354
+ metrics = MistralTranslator.metrics
355
+ puts "\n📈 Translation Metrics:"
356
+ puts "Total translations: #{metrics[:total_translations]}"
357
+ puts "Average time: #{metrics[:average_translation_time]}s"
358
+ puts "Error rate: #{metrics[:error_rate]}%"
359
+ end
360
+
361
+ def print_top_errors
362
+ return unless @errors.any?
363
+
364
+ puts "\n❌ Top Errors:"
365
+ error_groups = @errors.values.group_by(&:itself)
366
+ error_groups.sort_by { |_, v| -v.size }.first(3).each do |error, occurrences|
367
+ puts " #{error}: #{occurrences.size} times"
368
+ end
369
+ end
370
+ end
371
+
372
+ # === UTILISATION ===
373
+
374
+ puts "=== MistralTranslator Batch Processing Examples ==="
375
+
376
+ # Test de base avec vérification de l'API
377
+ begin
378
+ test_result = MistralTranslator.translate("Hello", from: "en", to: "fr")
379
+ puts "✅ API connection OK: #{test_result}"
380
+ rescue MistralTranslator::Error => e
381
+ puts "❌ API Error: #{e.message}"
382
+ puts "Continuing with examples that don't require API..."
383
+ end
384
+
385
+ # EXEMPLE 1: CSV
386
+ puts "\n1. CSV Translation Example"
387
+ puts "-" * 30
388
+
389
+ # Créer un fichier CSV d'exemple
390
+ sample_csv = "tmp_products.csv"
391
+ CSV.open(sample_csv, "w", headers: true) do |csv|
392
+ csv << %w[id name description]
393
+ csv << ["1", "Laptop Premium", "Ordinateur portable haute performance"]
394
+ csv << ["2", "Souris Gaming", "Souris optique pour gaming"]
395
+ csv << ["3", "Clavier Mécanique", "Clavier mécanique rétroéclairé"]
396
+ end
397
+
398
+ if File.exist?(sample_csv)
399
+ processor = CSVTranslationBatch.new(
400
+ sample_csv,
401
+ "tmp_products_en.csv",
402
+ from: "fr",
403
+ to: "en",
404
+ text_columns: %w[name description]
405
+ )
406
+
407
+ begin
408
+ processor.process!
409
+ rescue StandardError => e
410
+ puts "CSV processing error: #{e.message}"
411
+ ensure
412
+ FileUtils.rm_f(sample_csv)
413
+ FileUtils.rm_f("tmp_products_en.csv")
414
+ end
415
+ end
416
+
417
+ # EXEMPLE 2: Queue
418
+ puts "\n2. Translation Queue Example"
419
+ puts "-" * 30
420
+
421
+ queue = TranslationQueue.new
422
+
423
+ # Ajouter des jobs
424
+ sample_texts = [
425
+ "Bonjour le monde",
426
+ "Comment allez-vous ?",
427
+ "Ruby on Rails est génial",
428
+ "J'aime la programmation",
429
+ "Les tests sont importants"
430
+ ]
431
+
432
+ sample_texts.each_with_index do |text, i|
433
+ queue.add_job(i, text, from: "fr", to: "en", context: "casual conversation")
434
+ end
435
+
436
+ begin
437
+ results = queue.process_all!
438
+
439
+ puts "\nSample results:"
440
+ results.values.first(2).each do |result|
441
+ if result[:status] == :success
442
+ puts " #{result[:original]} → #{result[:translated]}"
443
+ else
444
+ puts " ERROR: #{result[:error]}"
445
+ end
446
+ end
447
+ rescue StandardError => e
448
+ puts "Queue processing error: #{e.message}"
449
+ end
450
+
451
+ # EXEMPLE 3: JSON
452
+ puts "\n3. JSON Translation Example"
453
+ puts "-" * 30
454
+
455
+ sample_json = {
456
+ "app" => {
457
+ "title" => "Mon Application",
458
+ "description" => "Une application formidable pour tous",
459
+ "version" => "1.0.0",
460
+ "features" => [
461
+ {
462
+ "name" => "Traduction Automatique",
463
+ "description" => "Traduit votre contenu instantanément"
464
+ },
465
+ {
466
+ "name" => "Interface Intuitive",
467
+ "description" => "Design simple et élégant"
468
+ }
469
+ ]
470
+ }
471
+ }
472
+
473
+ json_file = "tmp_app.json"
474
+ File.write(json_file, JSON.generate(sample_json))
475
+
476
+ processor = JSONBatchProcessor.new(json_file, "tmp_app_en.json")
477
+
478
+ begin
479
+ processor.translate_nested_json(from: "fr", to: "en")
480
+ rescue StandardError => e
481
+ puts "JSON processing error: #{e.message}"
482
+ ensure
483
+ FileUtils.rm_f(json_file)
484
+ FileUtils.rm_f("tmp_app_en.json")
485
+ end
486
+
487
+ # EXEMPLE 4: Robust batch
488
+ puts "\n4. Robust Batch Processing Example"
489
+ puts "-" * 30
490
+
491
+ texts_to_translate = [
492
+ "Bienvenue dans notre application",
493
+ "Votre compte a été créé avec succès",
494
+ "Merci pour votre commande",
495
+ "Erreur de connexion au serveur",
496
+ "Votre mot de passe a été mis à jour"
497
+ ]
498
+
499
+ robust_processor = RobustBatchProcessor.new(
500
+ texts_to_translate,
501
+ from: "fr",
502
+ to: "en"
503
+ )
504
+
505
+ begin
506
+ robust_processor.process_with_monitoring!
507
+ rescue StandardError => e
508
+ puts "Robust batch error: #{e.message}"
509
+ end
510
+
511
+ puts "\n🎉 All batch examples completed!"