Class: FeaturedRestaurants::AiTranslationService

Inherits:
Object
  • Object
show all
Includes:
ElasticAPM::SpanHelpers
Defined in:
app/services/featured_restaurants/ai_translation_service.rb

Overview

Service for synchronous AI translation of featured restaurant fields Uses OpenRouter API with Claude for high-quality translations

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(featured_restaurant, source_language: 'en', target_languages: [], target_fields: [], only_blank_languages: false, only_blank_fields: false) ⇒ AiTranslationService

Returns a new instance of AiTranslationService.



13
14
15
16
17
18
19
20
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 13

def initialize(featured_restaurant, source_language: 'en', target_languages: [], target_fields: [], only_blank_languages: false, only_blank_fields: false)
  @featured_restaurant = featured_restaurant
  @source_language = source_language
  @target_languages = target_languages
  @target_fields = target_fields
  @only_blank_languages = only_blank_languages
  @only_blank_fields = only_blank_fields
end

Instance Attribute Details

Returns the value of attribute featured_restaurant.



10
11
12
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 10

def featured_restaurant
  @featured_restaurant
end

#only_blank_fieldsObject (readonly)

Returns the value of attribute only_blank_fields.



10
11
12
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 10

def only_blank_fields
  @only_blank_fields
end

#only_blank_languagesObject (readonly)

Returns the value of attribute only_blank_languages.



10
11
12
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 10

def only_blank_languages
  @only_blank_languages
end

#source_languageObject (readonly)

Returns the value of attribute source_language.



10
11
12
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 10

def source_language
  @source_language
end

#target_fieldsObject (readonly)

Returns the value of attribute target_fields.



10
11
12
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 10

def target_fields
  @target_fields
end

#target_languagesObject (readonly)

Returns the value of attribute target_languages.



10
11
12
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 10

def target_languages
  @target_languages
end

Instance Method Details

#callObject



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/services/featured_restaurants/ai_translation_service.rb', line 22

def call
  validate_inputs!

  # Determine actual languages and fields to translate
  actual_languages = determine_target_languages
  actual_fields = determine_target_fields

  return failure_result('No languages to translate') if actual_languages.blank?
  return failure_result('No fields to translate') if actual_fields.blank?

  # Filter out language-field combinations that already have content
  languages_needing_translation, fields_needing_translation = filter_existing_translations(actual_languages,
                                                                                           actual_fields)

  if languages_needing_translation.blank?
    log_already_translated(actual_languages, actual_fields)
    return success_result({}, message: 'All translations already exist')
  end

  source_texts = extract_source_texts(fields_needing_translation)
  return failure_result('No source text found for translation') if source_texts.blank?

  # Batch translations to avoid timeout: max 6 languages per batch
  all_translations = {}
  failed_batches = []

  # If more than 6 languages, batch them
  if languages_needing_translation.size > 6
    # Batch by languages (6 at a time)
    languages_needing_translation.each_slice(6).with_index do |language_batch, index|
      BUSINESS_LOGGER.info('Processing language batch', {
                             featured_restaurant_id: featured_restaurant.id,
                             batch_index: index + 1,
                             batch_size: language_batch.size,
                             languages: language_batch,
                           })

      prompt = build_translation_prompt(source_texts, language_batch)
      batch_translations = execute_translation(prompt)

      if batch_translations.present?
        all_translations.merge!(batch_translations)
        BUSINESS_LOGGER.info('Language batch completed', {
                               featured_restaurant_id: featured_restaurant.id,
                               batch_index: index + 1,
                               translated_languages: batch_translations.keys,
                             })
      end
    rescue StandardError => e
      failed_batches << "language_batch_#{index + 1}"
      log_batch_error(e, "language_batch_#{index + 1}", language_batch)
    end
  else
    # Small enough to do in one request
    prompt = build_translation_prompt(source_texts, languages_needing_translation)
    all_translations = execute_translation(prompt)
  end

  # If all batches failed, return error
  if all_translations.blank?
    error_msg = failed_batches.any? ? "All batches failed: #{failed_batches.join(', ')}" : 'No translations generated'
    return failure_result(error_msg)
  end

  # If some batches failed, log warning but continue
  if failed_batches.any?
    log_partial_success(failed_batches, all_translations.keys)
  end

  update_featured_restaurant_fields(all_translations, languages_needing_translation, fields_needing_translation)

  # Calculate how many were skipped
  skipped_count = (actual_languages.size * actual_fields.size) - (languages_needing_translation.size * fields_needing_translation.size)

  message = 'Translation completed successfully'
  message += " (#{skipped_count} already existed)" if skipped_count > 0
  message += " (#{failed_batches.size} batches failed)" if failed_batches.any?

  success_result(all_translations, message: message)
rescue StandardError => e
  handle_error(e)
end