discourse-translator/app/services/discourse_translator/provider/amazon.rb
Natalie Tay 7507795782
FIX: Don't double cook (#297)
This PR fixes the bug where we were double-cooking when saving `PostLocalization` when the AI provider is used.

The reason is due to the fact that we used to translate raw, then cook, and save the cooked. But now, we are required to save both the raw and cooked into `PostLocalization`.

This PR also splits `translate_translatable` into `translate_post` and `translate_topic`. Originally they were taking the same route, but it was proving to be a bit annoying because post uses raw and cooked, whereas topic uses title. It makes understanding any issues with a single model (e.g. Posts) easier to understand if it is a single code path. Lastly, PR also fixes an Amazon provider bug as it was never tested.
2025-05-13 14:39:01 +08:00

165 lines
4.2 KiB
Ruby

# frozen_string_literal: true
module DiscourseTranslator
module Provider
class Amazon < BaseProvider
require "aws-sdk-translate"
MAX_BYTES = 10_000
# Hash which maps Discourse's locale code to Amazon Translate's language code found in
# https://docs.aws.amazon.com/translate/latest/dg/what-is-languages.html
SUPPORTED_LANG_MAPPING = {
af: "af",
am: "am",
ar: "ar",
az: "az",
bg: "bg",
bn: "bn",
bs: "bs",
bs_BA: "bs",
ca: "ca",
cs: "cs",
cy: "cy",
da: "da",
de: "de",
el: "el",
en: "en",
en_GB: "en",
es: "es",
es_MX: "es-MX",
et: "et",
fa: "fa",
fa_AF: "fa-AF",
fa_IR: "fa-AF",
fi: "fi",
fr: "fr",
fr_CA: "fr-CA",
ga: "ga",
gu: "gu",
ha: "ha",
he: "he",
hi: "hi",
hr: "hr",
ht: "ht",
hu: "hu",
hy: "hy",
id: "id",
is: "is",
it: "it",
ja: "ja",
ka: "ka",
kk: "kk",
kn: "kn",
ko: "ko",
lt: "lt",
lv: "lv",
mk: "mk",
ml: "ml",
mn: "mn",
mr: "mr",
ms: "ms",
mt: "mt",
nl: "nl",
no: "no",
pa: "pa",
pl: "pl",
pl_PL: "pl",
ps: "ps",
pt: "pt",
pt_PT: "pt-PT",
pt_BR: "pt",
ro: "ro",
ru: "ru",
si: "si",
sk: "sk",
sl: "sl",
so: "so",
sq: "sq",
sr: "sr",
sv: "sv",
sw: "sw",
ta: "ta",
te: "te",
th: "th",
tl: "tl",
tr: "tr",
tr_TR: "tr_TR",
uk: "uk",
ur: "ur",
uz: "uz",
vi: "vi",
zh: "zh",
zh_CN: "zh",
zh_TW: "zh-TW",
}
# The API expects a maximum of 10k __bytes__ of text
def self.truncate(text)
return text if text.bytesize <= MAX_BYTES
text = text.byteslice(...MAX_BYTES)
text = text.byteslice(...text.bytesize - 1) until text.valid_encoding?
text
end
def self.access_token_key
"aws-translator"
end
def self.detect!(topic_or_post)
begin
client.translate_text(
{
text: truncate(text_for_detection(topic_or_post)),
source_language_code: "auto",
target_language_code: SUPPORTED_LANG_MAPPING[I18n.locale],
},
)&.source_language_code
rescue Aws::Errors::MissingCredentialsError
raise I18n.t("translator.amazon.invalid_credentials")
end
end
def self.translate_post!(post, target_locale_sym = I18n.locale, opts = {})
raw = opts.key?(:raw) ? opts[:raw] : !opts[:cooked]
text = text_for_translation(post, raw:)
translate_text!(text, target_locale_sym)
end
def self.translate_topic!(topic, target_locale_sym = I18n.locale)
text = text_for_translation(topic)
translate_text!(text, target_locale_sym)
end
def self.translate_text!(text, target_locale_sym = I18n.locale)
client.translate_text(
{
text: truncate(text),
source_language_code: "auto",
target_language_code: SUPPORTED_LANG_MAPPING[target_locale_sym],
},
)&.translated_text
end
def self.client
opts = { region: SiteSetting.translator_aws_region }
if SiteSetting.translator_aws_key_id.present? &&
SiteSetting.translator_aws_secret_access.present?
opts[:access_key_id] = SiteSetting.translator_aws_key_id
opts[:secret_access_key] = SiteSetting.translator_aws_secret_access
elsif SiteSetting.translator_aws_iam_role.present?
sts_client = Aws::STS::Client.new(region: SiteSetting.translator_aws_region)
opts[:credentials] = Aws::AssumeRoleCredentials.new(
client: sts_client,
role_arn: SiteSetting.translator_aws_iam_role,
role_session_name: "discourse-aws-translator",
)
end
@client ||= Aws::Translate::Client.new(opts)
end
end
end
end