Skip to content

Commit

Permalink
feat(Observations): Add automatic translations
Browse files Browse the repository at this point in the history
  • Loading branch information
santostiago committed Feb 2, 2024
1 parent f390604 commit 5fc0a6f
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ RESPONSIBLE_EMAIL=
SEND_EMAILS_IN_DEV=false

MAPBOX_API_KEY=

GOOGLE_CLIENT_ID=
GOOGLE_API_KEY=
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ gem "paper_trail"
# Interactors
gem "interactor", "~> 3.0"

# Translations
gem "google-cloud-translate"

# Error Management
gem "sentry-rails"
gem "sentry-ruby"
Expand Down
80 changes: 80 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,29 @@ GEM
railties (>= 5.0.0)
faker (3.2.3)
i18n (>= 1.8.11, < 2)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
ferrum (0.14)
addressable (~> 2.5)
concurrent-ruby (~> 1.1)
Expand All @@ -273,14 +296,62 @@ GEM
formtastic (5.0.0)
actionpack (>= 6.0.0)
formtastic_i18n (0.7.0)
gapic-common (0.20.0)
faraday (>= 1.9, < 3.a)
faraday-retry (>= 1.0, < 3.a)
google-protobuf (~> 3.14)
googleapis-common-protos (>= 1.3.12, < 2.a)
googleapis-common-protos-types (>= 1.3.1, < 2.a)
googleauth (~> 1.0)
grpc (~> 1.36)
globalid (1.2.1)
activesupport (>= 6.1)
globalize (6.2.1)
activemodel (>= 4.2, < 7.1)
activerecord (>= 4.2, < 7.1)
request_store (~> 1.0)
google-cloud-core (1.6.1)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.0.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.3.1)
google-cloud-translate (3.4.1)
google-cloud-core (~> 1.6)
google-cloud-translate-v2 (>= 0.0, < 2.a)
google-cloud-translate-v3 (>= 0.6, < 2.a)
google-cloud-translate-v2 (0.4.1)
faraday (>= 0.17.3, < 2.a)
google-cloud-core (~> 1.6)
googleapis-common-protos (>= 1.3.10, < 2.a)
googleapis-common-protos-types (>= 1.0.5, < 2.a)
googleauth (>= 0.16.2, < 2.a)
google-cloud-translate-v3 (0.9.0)
gapic-common (>= 0.20.0, < 2.a)
google-cloud-errors (~> 1.0)
google-protobuf (3.25.1)
google-protobuf (3.25.1-x86_64-linux)
googleapis-common-protos (1.4.0)
google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.2)
grpc (~> 1.27)
googleapis-common-protos-types (1.11.0)
google-protobuf (~> 3.18)
googleauth (1.9.0)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.0, >= 2.0.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
groupdate (6.4.0)
activesupport (>= 6.1)
grpc (1.60.0)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
grpc (1.60.0-x86_64-linux)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
has_scope (0.8.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
Expand Down Expand Up @@ -368,6 +439,8 @@ GEM
minitest (5.21.2)
mjml-rails (4.10.0)
msgpack (1.7.2)
multi_json (1.15.0)
multipart-post (2.3.0)
mustache (1.1.1)
mutex_m (0.2.0)
net-imap (0.4.9.1)
Expand Down Expand Up @@ -395,6 +468,7 @@ GEM
oj_mimic_json (1.0.1)
optimist (3.1.0)
orm_adapter (0.5.0)
os (1.1.4)
paper_trail (12.3.0)
activerecord (>= 5.2)
request_store (~> 1.1)
Expand Down Expand Up @@ -585,6 +659,11 @@ GEM
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
redis-client (>= 0.14.0)
signet (0.18.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
Expand Down Expand Up @@ -701,6 +780,7 @@ DEPENDENCIES
faker
globalize
globalize-versioning!
google-cloud-translate
groupdate
http
i18n_generators
Expand Down
11 changes: 11 additions & 0 deletions app/admin/observation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ def permitted_params
end
end

member_action :force_translations, method: :put do
TranslationJob.perform_later(resource, I18n.locale)
redirect_to edit_admin_observation_path(resource), notice: I18n.t("active_admin.observations_page.translating_observation")
end

action_item :ready_for_publication, only: :show do
if resource.validation_status == "QC in progress"
link_to I18n.t("active_admin.observations_page.ready_for_publication"), ready_for_publication_admin_observation_path(observation),
Expand Down Expand Up @@ -581,10 +586,16 @@ def permitted_params
end

f.inputs I18n.t("active_admin.shared.translated_fields") do
div do
link_to I18n.t("active_admin.observations_page.force_translations"), force_translations_admin_observation_path(resource), method: :put, class: 'button'
end
f.translated_inputs "Translations", switch_locale: false do |t|
t.input :details, **visibility
t.input :details_translated_from, input_html: {disabled: true}
t.input :concern_opinion, **visibility
t.input :concern_opinion_translated_from, input_html: {disabled: true}
t.input :litigation_status, **visibility
t.input :litigation_status_translated_from, input_html: {disabled: true}
end
end
f.actions
Expand Down
42 changes: 42 additions & 0 deletions app/jobs/translation_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class TranslationJob < ApplicationJob
class TranslationException < StandardError
end

queue_as :default
retry_on TranslationException, wait: 5.minutes, attempts: 3

# Takes an entity (an ActiveRecord object) and an original_locale, and translates all the fields based on that locale
# The model should have a TRANSLATABLE_FIELDS constant
def perform(entity, original_locale)
return unless entity.class.const_defined?(:AUTOMATICALLY_TRANSLATABLE_FIELDS)

fields = entity.class.const_get(:AUTOMATICALLY_TRANSLATABLE_FIELDS)
translation_service = TranslationService.new
translated_fields = {}

fields.each do |field|
translated_fields[field] = {}
I18n.available_locales.each do |locale|
next if locale == original_locale

I18n.with_locale original_locale do
translated_fields[field][locale] = translation_service.call(entity.send(field), I18n.locale, locale)
end
end
end

translated_fields.each do |field, translation|
translation.each do |locale, text|
I18n.with_locale locale do
entity.send("#{field}=", text)
entity.translation.send("#{field}_translated_from=", "#{original_locale}")
end
end
end
entity.save

rescue e
Sentry.capture_exception(e)
raise TranslationException
end
end
11 changes: 11 additions & 0 deletions app/models/observation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class Observation < ApplicationRecord

validate_enum_attributes :observation_type, :evidence_type, :location_accuracy

AUTOMATICALLY_TRANSLATABLE_FIELDS = %w[details concern_opinion litigation_status]

STATUS_TRANSITIONS = {
monitor: {
"Created" => ["Ready for QC"],
Expand All @@ -85,6 +87,7 @@ class Observation < ApplicationRecord
].freeze

attr_accessor :user_type
attr_accessor :admin_locale

belongs_to :country, inverse_of: :observations
belongs_to :severity, inverse_of: :observations, optional: true
Expand Down Expand Up @@ -152,6 +155,7 @@ class Observation < ApplicationRecord

after_save :remove_documents, if: -> { evidence_type == "Evidence presented in the report" }
after_save :update_fmu_geojson
after_save :force_translations, if: :saved_change_to_validation_status?

after_commit :notify_about_creation, on: :create
after_commit :notify_about_changes, if: :saved_change_to_validation_status?
Expand Down Expand Up @@ -368,4 +372,11 @@ def notify_users(users, mail_template)
end
end
end

def force_translations
return unless published?
return unless I18n.available_locales.include? admin_locale

TranslationJob.perform_later(resource, admin_locale)
end
end
3 changes: 2 additions & 1 deletion app/resources/v1/observation_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,11 @@ def set_user
@model.user_id = context[:current_user].id
end

# Saves the last user who modified the observation
# Saves the last user who modified the observation and its locale
def set_modified
user = context[:current_user]
@model.modified_user_id = user.id
@model.admin_locale = user.locale
end

# Makes sure the validation status can be an acceptable one
Expand Down
15 changes: 15 additions & 0 deletions app/services/translation_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

# Service that communicates with the Google Translator API
class TranslationService
def initialize
@translator = Google::Cloud::Translate.translation_v2_service(
key: ENV["GOOGLE_API_KEY"]
)
end

def call(text, from, to)
translation = @translator.translate text, from: from, to: to

translation.text
end
end
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ en:
orphaned: 'Orphaned'
details: 'Operator Document Annex Details'
observations_page:
force_translations: 'Force translations'
translating_observation: 'Translating observation'
not_modified: 'Observation NOT modified'
perform_qc: 'Perform Quality Control'
performed_qc: 'Quality Control performed'
Expand Down
2 changes: 2 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@ fr:
orphaned: 'Orphelin'
details: "Détails de l'annexe du document de l'opérateur"
observations_page:
force_translations: 'Forcer les traductions'
translating_observation: "L'observation est en train d'être traduite"
not_modified: 'Observation NON modifiée'
perform_qc: 'Effectuer un contrôle qualité'
performed_qc: 'Contrôle qualité effectué'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddAutomaticTranslationFieldToObservationTranslations < ActiveRecord::Migration[7.0]
def change
add_column :observation_translations, :details_translated_from, :string
add_column :observation_translations, :concern_opinion_translated_from, :string
add_column :observation_translations, :litigation_status_translated_from, :string
end
end
5 changes: 4 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2023_11_30_113839) do
ActiveRecord::Schema[7.0].define(version: 2024_01_28_163512) do
# These are extensions that must be enabled in order to support this database
enable_extension "address_standardizer"
enable_extension "address_standardizer_data_us"
Expand Down Expand Up @@ -510,6 +510,9 @@
t.text "concern_opinion"
t.string "litigation_status"
t.datetime "deleted_at", precision: nil
t.string "details_translated_from"
t.string "concern_opinion_translated_from"
t.string "litigation_status_translated_from"
t.index ["deleted_at"], name: "index_observation_translations_on_deleted_at"
t.index ["locale"], name: "index_observation_translations_on_locale"
t.index ["observation_id"], name: "index_observation_translations_on_observation_id"
Expand Down

0 comments on commit 5fc0a6f

Please sign in to comment.