Class: Packages::AiTranslationService

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

Overview

Service for synchronous AI translation of package fields Uses OpenRouter API with Claude for high-quality translations Handles all package types: Ayce, PartyPack, BuffetExtra, HungryAtHome, SetMenu, Xperience, Diy

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of AiTranslationService.



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

def initialize(package, source_language: 'en', target_languages: [], target_fields: [], only_blank_languages: false, only_blank_fields: false)
  @package = package
  @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

#only_blank_fieldsObject (readonly)

Returns the value of attribute only_blank_fields.



11
12
13
# File 'app/services/packages/ai_translation_service.rb', line 11

def only_blank_fields
  @only_blank_fields
end

#only_blank_languagesObject (readonly)

Returns the value of attribute only_blank_languages.



11
12
13
# File 'app/services/packages/ai_translation_service.rb', line 11

def only_blank_languages
  @only_blank_languages
end

#packageObject (readonly)

Returns the value of attribute package.



11
12
13
# File 'app/services/packages/ai_translation_service.rb', line 11

def package
  @package
end

#source_languageObject (readonly)

Returns the value of attribute source_language.



11
12
13
# File 'app/services/packages/ai_translation_service.rb', line 11

def source_language
  @source_language
end

#target_fieldsObject (readonly)

Returns the value of attribute target_fields.



11
12
13
# File 'app/services/packages/ai_translation_service.rb', line 11

def target_fields
  @target_fields
end

#target_languagesObject (readonly)

Returns the value of attribute target_languages.



11
12
13
# File 'app/services/packages/ai_translation_service.rb', line 11

def target_languages
  @target_languages
end

Instance Method Details

#callObject



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
# File 'app/services/packages/ai_translation_service.rb', line 23

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? || fields_needing_translation.blank?
    skipped_count = (actual_languages.size * actual_fields.size) - (languages_needing_translation.size * fields_needing_translation.size)
    return success_result({}, message: "All selected translations already exist (#{skipped_count} skipped)")
  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 or 4 fields per batch
  all_translations = {}
  failed_batches = []

  # Strategy: If too many languages, batch by language groups
  # If too many fields, batch by field groups
  if languages_needing_translation.size > 6
    languages_needing_translation.each_slice(6) do |lang_batch|
      result = execute_single_batch(source_texts, lang_batch, fields_needing_translation)
      if result[:success]
        all_translations.deep_merge!(result[:translations])
      else
        failed_batches << { languages: lang_batch, error: result[:message] }
      end
    end
  elsif fields_needing_translation.size > 4
    fields_needing_translation.each_slice(4) do |field_batch|
      batch_source_texts = source_texts.slice(*field_batch)
      result = execute_single_batch(batch_source_texts, languages_needing_translation, field_batch)
      if result[:success]
        all_translations.deep_merge!(result[:translations])
      else
        failed_batches << { fields: field_batch, error: result[:message] }
      end
    end
  else
    result = execute_single_batch(source_texts, languages_needing_translation, fields_needing_translation)
    if result[:success]
      all_translations = result[:translations]
    else
      failed_batches << { all: true, error: result[:message] }
    end
  end

  # If all batches failed, return error
  if all_translations.blank? && failed_batches.present?
    error_msg = failed_batches.pluck(:error).join('; ')
    return failure_result("All translation batches failed: #{error_msg}")
  end

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

  # Update package fields with translations
  update_package_fields(all_translations, languages_needing_translation, fields_needing_translation)

  skipped_count = (actual_languages.size * actual_fields.size) - (languages_needing_translation.size * fields_needing_translation.size)
  message = "Successfully translated #{languages_needing_translation.size} language(s) for #{fields_needing_translation.size} field(s)"
  message += " (#{skipped_count} already existed)" if skipped_count > 0

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