lib/translatomatic/translation/fetcher.rb
module Translatomatic
module Translation
# Fetches translations from the database and translation providers
class Fetcher
def initialize(options = {})
ATTRIBUTES.each do |i|
instance_variable_set("@#{i}", options[i])
end
end
# Fetch a list of translations for all texts given in the constructor
# for all providers.
# Translations are fetched from the database first, then from providers.
# @return [Array<Result>] List of translations
def translations
collection = Collection.new
# add all translations from the database to the collection
collection.add(find_database_translations(@texts)) if @use_db
# request translations for all texts that aren't in the database
untranslated = untranslated(collection)
if untranslated.present?
provider_translations = find_provider_translations(untranslated)
save_database_translations(provider_translations)
collection.add(provider_translations)
end
# puts collection.description
collection
end
private
include Util
include Munging
ATTRIBUTES = %i[provider texts from_locale to_locale
use_db listener].freeze
# find texts that we do not have translations for
# @param collection [Collection] Translation collection
# @return [Array<String>] Untranslated texts
def untranslated(collection)
@texts.reject { |i| collection.translated?(i, @provider.name) }
end
# @return [Array<Result>] translations from the database
def find_database_translations(texts)
from = db_locale(@from_locale)
to = db_locale(@to_locale)
db_texts = Translatomatic::Model::Text.where(
locale: to, provider: @provider.name,
from_texts_texts: {
locale_id: from,
# convert untranslated texts to strings
value: texts.collect(&:to_s)
}
).joins(:from_text).includes(:from_text)
texts_to_translations(db_texts, texts)
end
# @return [Array<Result>] translations from provider
def find_provider_translations(texts)
texts = wrap_notranslate(texts)
translations = @provider.translate(
texts, @from_locale, @to_locale
)
# check for valid response from provider and restore variables
translations.each do |tr|
raise t('provider.invalid_response') unless tr.is_a?(Result)
end
munge_translation_results(translations)
end
# use the original text from the translation rather than
# db_text.from_text.value, as the original string has required
# information such as offset and context.
def texts_to_translations(db_texts, texts)
db_text_map = hashify(db_texts, proc { |i| i.from_text.value })
texts.collect do |text|
next unless (db_text = db_text_map[text.to_s])
@listener.update_progress(1) if @listener
provider = db_text.provider
translation = build_text(db_text.value, @to_locale)
Result.new(text, translation, provider, from_database: true)
end.compact
end
def save_database_translations(translations)
return unless @use_db
ActiveRecord::Base.transaction do
from = db_locale(@from_locale)
to = db_locale(@to_locale)
translations.each do |tr|
next if tr.result.nil? # skip invalid translations
save_database_translation(from, to, tr)
end
end
end
def save_database_translation(from_locale, to_locale, translation)
original_text = Translatomatic::Model::Text.find_or_create_by!(
locale: from_locale,
value: translation.original.to_s
)
text = Translatomatic::Model::Text.find_or_create_by!(
locale: to_locale, value: translation.result.to_s,
from_text: original_text,
provider: @provider.name
)
text
end
def db_locale(locale)
Translatomatic::Model::Locale.from_tag(locale)
end
end
end
end