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,399 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Exemple d'intégration MistralTranslator avec des modèles Rails
5
+ # Usage: rails runner examples/rails-model.rb
6
+
7
+ # Arrêter si le script n'est pas exécuté dans un contexte Rails
8
+ unless defined?(Rails)
9
+ warn "Ce script est destiné à être exécuté dans un projet Rails. Utilisez: rails runner examples/rails-model.rb"
10
+ exit 1
11
+ end
12
+
13
+ # Configuration initiale
14
+ MistralTranslator.configure do |config|
15
+ config.api_key = ENV.fetch("MISTRAL_API_KEY", nil)
16
+ config.enable_metrics = true
17
+ config.setup_rails_logging
18
+ end
19
+
20
+ # === EXEMPLE 1: Modèle avec Mobility ===
21
+
22
+ class Article < ApplicationRecord
23
+ extend Mobility
24
+
25
+ translates :title, :content, :description, backend: :table
26
+
27
+ # Callbacks pour traduction automatique
28
+ after_create :translate_to_all_locales, if: :should_auto_translate?
29
+ after_update :retranslate_if_changed, if: :should_auto_translate?
30
+
31
+ def translate_to_all_locales(source_locale: I18n.locale)
32
+ MistralTranslator::RecordTranslation.translate_mobility_record(
33
+ self,
34
+ %i[title content description],
35
+ source_locale: source_locale
36
+ )
37
+ end
38
+
39
+ def translate_to(target_locales, source_locale: I18n.locale)
40
+ Array(target_locales).each do |target_locale|
41
+ next if source_locale.to_s == target_locale.to_s
42
+
43
+ translatable_fields.each do |field|
44
+ source_text = public_send("#{field}_#{source_locale}")
45
+ next if source_text.blank?
46
+
47
+ translated = MistralTranslator.translate(
48
+ source_text,
49
+ from: source_locale,
50
+ to: target_locale,
51
+ context: "article content"
52
+ )
53
+
54
+ public_send("#{field}_#{target_locale}=", translated)
55
+ end
56
+ end
57
+
58
+ save! if changed?
59
+ end
60
+
61
+ def estimate_translation_cost
62
+ total_chars = translatable_fields.sum do |field|
63
+ content = public_send("#{field}_#{I18n.default_locale}")
64
+ content&.length || 0
65
+ end
66
+
67
+ target_locales = I18n.available_locales - [I18n.default_locale]
68
+
69
+ {
70
+ character_count: total_chars,
71
+ target_languages: target_locales.size,
72
+ estimated_cost: (total_chars * target_locales.size / 1000.0) * 0.02,
73
+ currency: "USD"
74
+ }
75
+ end
76
+
77
+ private
78
+
79
+ def should_auto_translate?
80
+ Rails.env.production? && ENV["AUTO_TRANSLATE"] == "true"
81
+ end
82
+
83
+ def retranslate_if_changed
84
+ changed_fields = translatable_fields.select do |field|
85
+ saved_change_to_attribute?("#{field}_#{I18n.default_locale}")
86
+ end
87
+
88
+ return if changed_fields.empty?
89
+
90
+ TranslationJob.perform_later(self, changed_fields)
91
+ end
92
+
93
+ def translatable_fields
94
+ %i[title content description]
95
+ end
96
+ end
97
+
98
+ # === EXEMPLE 2: Modèle avec attributs I18n simples ===
99
+
100
+ class Product < ApplicationRecord
101
+ # Colonnes: name_fr, name_en, description_fr, description_en, etc.
102
+
103
+ include MistralTranslator::Helpers::RecordHelpers
104
+
105
+ SUPPORTED_LOCALES = %w[fr en es de].freeze
106
+ TRANSLATABLE_FIELDS = %w[name description features].freeze
107
+
108
+ def translate_all!(source_locale: "fr")
109
+ target_locales = SUPPORTED_LOCALES - [source_locale]
110
+
111
+ TRANSLATABLE_FIELDS.each do |field|
112
+ source_text = public_send("#{field}_#{source_locale}")
113
+ next if source_text.blank?
114
+
115
+ target_locales.each do |target_locale|
116
+ translated = MistralTranslator.translate(
117
+ source_text,
118
+ from: source_locale,
119
+ to: target_locale,
120
+ context: "e-commerce product",
121
+ glossary: product_glossary
122
+ )
123
+
124
+ public_send("#{field}_#{target_locale}=", translated)
125
+ end
126
+ end
127
+
128
+ save!
129
+ end
130
+
131
+ def translate_field(field, from:, to:)
132
+ source_text = public_send("#{field}_#{from}")
133
+ return if source_text.blank?
134
+
135
+ translated = MistralTranslator.translate(
136
+ source_text,
137
+ from: from,
138
+ to: to,
139
+ context: "product #{field}",
140
+ glossary: product_glossary
141
+ )
142
+
143
+ update!("#{field}_#{to}" => translated)
144
+ translated
145
+ end
146
+
147
+ def missing_translations
148
+ missing = {}
149
+
150
+ TRANSLATABLE_FIELDS.each do |field|
151
+ SUPPORTED_LOCALES.each do |locale|
152
+ if public_send("#{field}_#{locale}").blank?
153
+ missing[field] ||= []
154
+ missing[field] << locale
155
+ end
156
+ end
157
+ end
158
+
159
+ missing
160
+ end
161
+
162
+ private
163
+
164
+ def product_glossary
165
+ {
166
+ "premium" => "premium",
167
+ "pro" => "pro",
168
+ "standard" => "standard",
169
+ brand => brand # Garder le nom de marque
170
+ }
171
+ end
172
+ end
173
+
174
+ # === EXEMPLE 3: Service Object pour traductions en masse ===
175
+
176
+ class BulkTranslationService
177
+ def initialize(model_class, field_names, options = {})
178
+ @model_class = model_class
179
+ @field_names = Array(field_names)
180
+ @source_locale = options[:source_locale] || "fr"
181
+ @target_locales = options[:target_locales] || %w[en es de]
182
+ @batch_size = options[:batch_size] || 10
183
+ @context = options[:context]
184
+ end
185
+
186
+ def translate_all!
187
+ @model_class.find_in_batches(batch_size: @batch_size) do |batch|
188
+ translate_batch!(batch)
189
+ sleep(2) # Rate limiting
190
+ end
191
+ end
192
+
193
+ def translate_missing!
194
+ records_with_missing = @model_class.joins(@target_locales.map do |locale|
195
+ "LEFT JOIN #{@model_class.table_name} as #{locale}_table ON #{locale}_table.id = #{@model_class.table_name}.id"
196
+ end.join(" ")).where(
197
+ @target_locales.map do |locale|
198
+ @field_names.map { |field| "#{field}_#{locale} IS NULL OR #{field}_#{locale} = ''" }
199
+ end.flatten.join(" OR ")
200
+ )
201
+
202
+ records_with_missing.find_in_batches(batch_size: @batch_size) do |batch|
203
+ translate_batch!(batch)
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ def translate_batch!(records)
210
+ records.each do |record|
211
+ @field_names.each do |field|
212
+ source_text = record.public_send("#{field}_#{@source_locale}")
213
+ next if source_text.blank?
214
+
215
+ @target_locales.each do |target_locale|
216
+ next unless record.public_send("#{field}_#{target_locale}").blank?
217
+
218
+ begin
219
+ translated = MistralTranslator.translate(
220
+ source_text,
221
+ from: @source_locale,
222
+ to: target_locale,
223
+ context: @context
224
+ )
225
+
226
+ record.update_column("#{field}_#{target_locale}", translated)
227
+ Rails.logger.info "✅ Translated #{@model_class.name}##{record.id} #{field} to #{target_locale}"
228
+ rescue MistralTranslator::Error => e
229
+ Rails.logger.error "❌ Failed to translate #{@model_class.name}##{record.id}: #{e.message}"
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ # === EXEMPLE 4: Job Sidekiq pour traductions asynchrones ===
238
+
239
+ class TranslationJob < ApplicationJob
240
+ queue_as :translations
241
+ retry_on MistralTranslator::RateLimitError, wait: :exponentially_longer
242
+ discard_on MistralTranslator::AuthenticationError
243
+
244
+ # rubocop:disable Metrics/PerceivedComplexity
245
+ def perform(record, field_names, options = {})
246
+ source_locale = options["source_locale"] || I18n.default_locale.to_s
247
+ target_locales = options["target_locales"] || (I18n.available_locales.map(&:to_s) - [source_locale])
248
+ context = options["context"] || "#{record.class.name.downcase} content"
249
+
250
+ Array(field_names).each do |field|
251
+ source_text = record.public_send("#{field}_#{source_locale}")
252
+ next if source_text.blank?
253
+
254
+ target_locales.each do |target_locale|
255
+ translated = MistralTranslator.translate(
256
+ source_text,
257
+ from: source_locale,
258
+ to: target_locale,
259
+ context: context
260
+ )
261
+
262
+ record.update_column("#{field}_#{target_locale}", translated)
263
+ end
264
+ end
265
+
266
+ # Callback optionnel
267
+ record.after_translation_complete if record.respond_to?(:after_translation_complete)
268
+ end
269
+ # rubocop:enable Metrics/PerceivedComplexity
270
+ end
271
+
272
+ # === EXEMPLE 5: Concern réutilisable ===
273
+
274
+ module Translatable
275
+ extend ActiveSupport::Concern
276
+
277
+ included do
278
+ scope :with_missing_translations, lambda { |locale|
279
+ where(translatable_fields.map { |field| "#{field}_#{locale} IS NULL OR #{field}_#{locale} = ''" }.join(" OR "))
280
+ }
281
+
282
+ scope :translated_in, lambda { |locale|
283
+ where.not(translatable_fields.map do |field|
284
+ "#{field}_#{locale} IS NULL OR #{field}_#{locale} = ''"
285
+ end.join(" OR "))
286
+ }
287
+ end
288
+
289
+ class_methods do
290
+ def translatable_fields(*fields)
291
+ if fields.any?
292
+ @translatable_fields = fields
293
+ else
294
+ @translatable_fields || []
295
+ end
296
+ end
297
+
298
+ def supported_locales(*locales)
299
+ if locales.any?
300
+ @supported_locales = locales.map(&:to_s)
301
+ else
302
+ @supported_locales || I18n.available_locales.map(&:to_s)
303
+ end
304
+ end
305
+
306
+ def bulk_translate!(source_locale: "fr", target_locales: nil, context: nil)
307
+ target_locales ||= supported_locales - [source_locale.to_s]
308
+
309
+ BulkTranslationService.new(
310
+ self,
311
+ translatable_fields,
312
+ source_locale: source_locale,
313
+ target_locales: target_locales,
314
+ context: context || name.downcase
315
+ ).translate_missing!
316
+ end
317
+ end
318
+
319
+ def translate_async!(source_locale: I18n.locale, target_locales: nil, context: nil)
320
+ target_locales ||= self.class.supported_locales - [source_locale.to_s]
321
+
322
+ TranslationJob.perform_later(
323
+ self,
324
+ self.class.translatable_fields,
325
+ "source_locale" => source_locale.to_s,
326
+ "target_locales" => target_locales,
327
+ "context" => context
328
+ )
329
+ end
330
+
331
+ def translation_progress
332
+ total_combinations = self.class.translatable_fields.size * self.class.supported_locales.size
333
+ completed = 0
334
+
335
+ self.class.translatable_fields.each do |field|
336
+ self.class.supported_locales.each do |locale|
337
+ completed += 1 unless public_send("#{field}_#{locale}").blank?
338
+ end
339
+ end
340
+
341
+ (completed.to_f / total_combinations * 100).round(1)
342
+ end
343
+ end
344
+
345
+ # === UTILISATION ===
346
+
347
+ puts "=== Exemples Rails Models avec MistralTranslator ==="
348
+
349
+ # Utilisation du concern
350
+ class BlogPost < ApplicationRecord
351
+ include Translatable
352
+
353
+ translatable_fields :title, :content, :summary
354
+ supported_locales :fr, :en, :es, :de
355
+ end
356
+
357
+ # Exemples d'utilisation
358
+ if defined?(Rails) && Rails.env.development?
359
+
360
+ # Test avec un article
361
+ article = Article.create!(
362
+ title_fr: "Les avantages de Ruby on Rails",
363
+ content_fr: "Ruby on Rails est un framework...",
364
+ description_fr: "Guide complet sur Rails"
365
+ )
366
+
367
+ puts "Article créé: #{article.title_fr}"
368
+
369
+ # Traduction manuelle
370
+ article.translate_to(%i[en es])
371
+ puts "Traduit en: #{article.title_en}, #{article.title_es}"
372
+
373
+ # Estimation des coûts
374
+ cost = article.estimate_translation_cost
375
+ puts "Coût estimé: $#{cost[:estimated_cost]} pour #{cost[:target_languages]} langues"
376
+
377
+ # Traduction en masse
378
+ puts "\nTraduction en masse des articles..."
379
+ BulkTranslationService.new(
380
+ Article,
381
+ %i[title content],
382
+ source_locale: "fr",
383
+ target_locales: %w[en es],
384
+ context: "blog articles"
385
+ ).translate_missing!
386
+
387
+ # Utilisation du concern
388
+ post = BlogPost.create!(
389
+ title_fr: "Nouveau post",
390
+ content_fr: "Contenu du post..."
391
+ )
392
+
393
+ puts "Progress: #{post.translation_progress}%"
394
+ post.translate_async!(target_locales: %w[en es])
395
+ puts "Job de traduction lancé"
396
+
397
+ end
398
+
399
+ puts "Exemples terminés !"
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MistralTranslator
4
+ module Adapters
5
+ # Interface de base pour les adaptateurs
6
+ class BaseAdapter
7
+ def initialize(record)
8
+ @record = record
9
+ end
10
+
11
+ # Méthodes à implémenter par les adaptateurs spécifiques
12
+ def available_locales
13
+ raise NotImplementedError, "Subclass must implement #available_locales"
14
+ end
15
+
16
+ def get_field_value(field, locale)
17
+ raise NotImplementedError, "Subclass must implement #get_field_value"
18
+ end
19
+
20
+ def set_field_value(field, locale, value)
21
+ raise NotImplementedError, "Subclass must implement #set_field_value"
22
+ end
23
+
24
+ def save_record!
25
+ @record.save!
26
+ end
27
+
28
+ # Méthodes utilitaires communes
29
+ def detect_source_locale(field, preferred_locale = nil)
30
+ # Priorité à une locale source fournie
31
+ return preferred_locale.to_sym if preferred_locale && content?(field, preferred_locale)
32
+
33
+ # Chercher quelle locale a du contenu pour ce champ
34
+ available_locales.each do |locale|
35
+ return locale if content?(field, locale)
36
+ end
37
+
38
+ # Fallback sur la première locale disponible
39
+ available_locales.first
40
+ end
41
+
42
+ def content?(field, locale)
43
+ value = get_field_value(field, locale)
44
+ return false if value.nil?
45
+
46
+ # Gestion des rich text
47
+ if defined?(ActionText::RichText) && value.is_a?(ActionText::RichText)
48
+ !value.to_plain_text.to_s.strip.empty?
49
+ else
50
+ !value.to_s.strip.empty?
51
+ end
52
+ end
53
+
54
+ def get_translatable_content(field, locale)
55
+ value = get_field_value(field, locale)
56
+ return nil if value.nil?
57
+
58
+ # Gestion des rich text - préserver le HTML
59
+ if defined?(ActionText::RichText) && value.is_a?(ActionText::RichText)
60
+ value.body.to_s
61
+ else
62
+ value.to_s
63
+ end
64
+ end
65
+ end
66
+
67
+ # Adaptateur pour Mobility
68
+ class MobilityAdapter < BaseAdapter
69
+ def available_locales
70
+ I18n.available_locales
71
+ end
72
+
73
+ def get_field_value(field, locale)
74
+ @record.public_send("#{field}_#{locale}")
75
+ rescue NoMethodError
76
+ nil
77
+ end
78
+
79
+ def set_field_value(field, locale, value)
80
+ @record.public_send("#{field}_#{locale}=", value)
81
+ end
82
+ end
83
+
84
+ # Adaptateur pour les attributs I18n standards avec suffixes
85
+ class I18nAttributesAdapter < BaseAdapter
86
+ def available_locales
87
+ I18n.available_locales
88
+ end
89
+
90
+ def get_field_value(field, locale)
91
+ @record.public_send("#{field}_#{locale}")
92
+ rescue NoMethodError
93
+ nil
94
+ end
95
+
96
+ def set_field_value(field, locale, value)
97
+ @record.public_send("#{field}_#{locale}=", value)
98
+ end
99
+ end
100
+
101
+ # Adaptateur pour Globalize
102
+ class GlobalizeAdapter < BaseAdapter
103
+ def available_locales
104
+ @record.class.translated_locales || I18n.available_locales
105
+ end
106
+
107
+ def get_field_value(field, locale)
108
+ I18n.with_locale(locale) do
109
+ @record.public_send(field)
110
+ end
111
+ rescue NoMethodError
112
+ nil
113
+ end
114
+
115
+ def set_field_value(field, locale, value)
116
+ I18n.with_locale(locale) do
117
+ @record.public_send("#{field}=", value)
118
+ end
119
+ end
120
+ end
121
+
122
+ # Adaptateur pour des méthodes custom
123
+ class CustomAdapter < BaseAdapter
124
+ def initialize(record, options = {})
125
+ super(record)
126
+ @get_method = options[:get_method] || :get_translation
127
+ @set_method = options[:set_method] || :set_translation
128
+ @locales_method = options[:locales_method] || :available_locales
129
+ end
130
+
131
+ def available_locales
132
+ if @record.respond_to?(@locales_method)
133
+ @record.public_send(@locales_method)
134
+ else
135
+ I18n.available_locales
136
+ end
137
+ end
138
+
139
+ def get_field_value(field, locale)
140
+ @record.public_send(@get_method, field, locale)
141
+ rescue NoMethodError
142
+ nil
143
+ end
144
+
145
+ def set_field_value(field, locale, value)
146
+ @record.public_send(@set_method, field, locale, value)
147
+ end
148
+ end
149
+
150
+ # Factory pour détecter automatiquement l'adaptateur approprié
151
+ class AdapterFactory
152
+ def self.build_for(record)
153
+ # Détecter Mobility
154
+ return MobilityAdapter.new(record) if defined?(Mobility) && record.class.respond_to?(:mobility_attributes)
155
+
156
+ # Détecter Globalize
157
+ if defined?(Globalize) && record.class.respond_to?(:translated_attribute_names)
158
+ return GlobalizeAdapter.new(record)
159
+ end
160
+
161
+ # Par défaut, essayer l'adaptateur I18n avec suffixes
162
+ I18nAttributesAdapter.new(record)
163
+ end
164
+ end
165
+ end
166
+
167
+ # Service de traduction utilisant les adaptateurs
168
+ module Adapters
169
+ class RecordTranslationService
170
+ def initialize(record, translatable_fields, adapter: nil, source_locale: nil)
171
+ @record = record
172
+ @translatable_fields = Array(translatable_fields)
173
+ @adapter = adapter || Adapters::AdapterFactory.build_for(record)
174
+ @source_locale = source_locale
175
+ end
176
+
177
+ def translate_to_all_locales
178
+ return false if @translatable_fields.empty?
179
+
180
+ ActiveRecord::Base.transaction do
181
+ @translatable_fields.each do |field|
182
+ translate_field(field)
183
+ end
184
+ @adapter.save_record!
185
+ end
186
+
187
+ true
188
+ rescue StandardError => e
189
+ Rails.logger.error "Translation failed: #{e.message}" if defined?(Rails)
190
+ false
191
+ end
192
+
193
+ private
194
+
195
+ def translate_field(field)
196
+ source_locale = @adapter.detect_source_locale(field, @source_locale)
197
+ source_content = @adapter.get_translatable_content(field, source_locale)
198
+
199
+ return if source_content.nil? || source_content.strip.empty?
200
+
201
+ target_locales = @adapter.available_locales - [source_locale]
202
+
203
+ target_locales.each do |target_locale|
204
+ translate_to_locale(field, source_content, source_locale, target_locale)
205
+ sleep(2) # Rate limiting basique
206
+ end
207
+ end
208
+
209
+ def translate_to_locale(field, content, source_locale, target_locale)
210
+ translated_content = MistralTranslator.translate(
211
+ content,
212
+ from: source_locale.to_s,
213
+ to: target_locale.to_s
214
+ )
215
+
216
+ if translated_content && !translated_content.strip.empty?
217
+ @adapter.set_field_value(field, target_locale,
218
+ translated_content)
219
+ end
220
+ rescue MistralTranslator::RateLimitError => e
221
+ Rails.logger.warn "Rate limit: #{e.message}" if defined?(Rails)
222
+ sleep(2)
223
+ retry
224
+ rescue StandardError => e
225
+ if defined?(Rails)
226
+ Rails.logger.error "Translation error for #{field} #{source_locale}->#{target_locale}: #{e.message}"
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ # Méthodes de convenance pour utilisation directe
233
+ module RecordTranslation
234
+ def self.translate_record(record, fields, adapter: nil, source_locale: nil)
235
+ service = Adapters::RecordTranslationService.new(record, fields, adapter: adapter, source_locale: source_locale)
236
+ service.translate_to_all_locales
237
+ end
238
+
239
+ # Pour Mobility (exemple d'usage)
240
+ def self.translate_mobility_record(record, fields, source_locale: nil)
241
+ adapter = Adapters::MobilityAdapter.new(record)
242
+ translate_record(record, fields, adapter: adapter, source_locale: source_locale)
243
+ end
244
+
245
+ # Pour Globalize (exemple d'usage)
246
+ def self.translate_globalize_record(record, fields, source_locale: nil)
247
+ adapter = Adapters::GlobalizeAdapter.new(record)
248
+ translate_record(record, fields, adapter: adapter, source_locale: source_locale)
249
+ end
250
+
251
+ # Pour des méthodes custom
252
+ def self.translate_custom_record(record, fields, get_method:, set_method:, **options)
253
+ adapter = Adapters::CustomAdapter.new(record, {
254
+ get_method: get_method,
255
+ set_method: set_method,
256
+ locales_method: options[:locales_method]
257
+ })
258
+ translate_record(record, fields, adapter: adapter, source_locale: options[:source_locale])
259
+ end
260
+ end
261
+ end