Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7a3064c

Browse files
committedApr 22, 2025··
DEV: Add the ability to translate a single piece of text
1 parent f4724ab commit 7a3064c

File tree

11 files changed

+149
-40
lines changed

11 files changed

+149
-40
lines changed
 

‎app/services/discourse_translator/provider/amazon.rb

+14
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,20 @@ def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
140140
end
141141
end
142142

143+
def self.translate_text!(text, target_locale_sym = I18n.locale)
144+
begin
145+
client.translate_text(
146+
{
147+
text: truncate(text),
148+
source_language_code: "auto",
149+
target_language_code: SUPPORTED_LANG_MAPPING[target_locale_sym],
150+
},
151+
)
152+
rescue Aws::Translate::Errors::UnsupportedLanguagePairException
153+
raise I18n.t("translator.not_supported")
154+
end
155+
end
156+
143157
def self.client
144158
opts = { region: SiteSetting.translator_aws_region }
145159

‎app/services/discourse_translator/provider/base_provider.rb

+4
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
6565
raise "Not Implemented"
6666
end
6767

68+
def self.translate_text(text, target_locale_sym = I18n.locale)
69+
raise "Not Implemented"
70+
end
71+
6872
# Returns the stored detected locale of a post or topic.
6973
# If the locale does not exist yet, it will be detected first via the API then stored.
7074
# @param translatable [Post|Topic]

‎app/services/discourse_translator/provider/discourse_ai.rb

+19-19
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,13 @@ def self.language_supported?(detected_lang)
1010
end
1111

1212
def self.detect!(topic_or_post)
13-
unless required_settings_enabled
14-
raise TranslatorError.new(
15-
I18n.t(
16-
"translator.discourse_ai.ai_helper_required",
17-
{ base_url: Discourse.base_url },
18-
),
19-
)
20-
end
13+
required_settings_enabled!
2114

2215
::DiscourseAi::LanguageDetector.new(text_for_detection(topic_or_post)).detect
2316
end
2417

2518
def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
26-
unless required_settings_enabled
27-
raise TranslatorError.new(
28-
I18n.t(
29-
"translator.discourse_ai.ai_helper_required",
30-
{ base_url: Discourse.base_url },
31-
),
32-
)
33-
end
19+
required_settings_enabled!
3420

3521
language = get_language_name(target_locale_sym)
3622
translated =
@@ -51,11 +37,25 @@ def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
5137
DiscourseTranslator::TranslatedContentNormalizer.normalize(translatable, translated)
5238
end
5339

40+
def self.translate_text!(text, target_locale_sym = I18n.locale)
41+
required_settings_enabled!
42+
43+
language = get_language_name(target_locale_sym)
44+
::DiscourseAi::ShortTextTranslator.new(text, language).translate
45+
end
46+
5447
private
5548

56-
def self.required_settings_enabled
57-
SiteSetting.translator_enabled && SiteSetting.translator_provider == "DiscourseAi" &&
58-
SiteSetting.discourse_ai_enabled && SiteSetting.ai_helper_enabled
49+
def self.required_settings_enabled!
50+
unless SiteSetting.translator_enabled && SiteSetting.translator_provider == "DiscourseAi" &&
51+
SiteSetting.discourse_ai_enabled && SiteSetting.ai_helper_enabled
52+
raise TranslatorError.new(
53+
I18n.t(
54+
"translator.discourse_ai.ai_helper_required",
55+
{ base_url: Discourse.base_url },
56+
),
57+
)
58+
end
5959
end
6060

6161
def self.get_language_name(target_locale_sym)

‎app/services/discourse_translator/provider/google.rb

+5
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
106106
res["translations"][0]["translatedText"]
107107
end
108108

109+
def self.translate_text!(text, target_locale_sym = I18n.locale)
110+
res = result(TRANSLATE_URI, q: text, target: SUPPORTED_LANG_MAPPING[target_locale_sym])
111+
res["translations"][0]["translatedText"]
112+
end
113+
109114
def self.result(url, body)
110115
body[:key] = access_token
111116

‎app/services/discourse_translator/provider/libre_translate.rb

+6
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
105105
res["translatedText"]
106106
end
107107

108+
def self.translate_text!(text, target_locale_sym = I18n.locale)
109+
# Unsupported - see https://libretranslate.com/docs/#/translate/post_translate
110+
# requires a source language
111+
raise TranslatorError.new(I18n.t("translator.not_supported"))
112+
end
113+
108114
def self.get(url)
109115
begin
110116
response = Excon.get(url)

‎app/services/discourse_translator/provider/microsoft.rb

+12
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
171171
response_body.first["translations"].first["text"]
172172
end
173173

174+
def self.translate_text!(text, target_locale_sym = I18n.locale)
175+
locale =
176+
SUPPORTED_LANG_MAPPING[target_locale_sym] || (raise I18n.t("translator.not_supported"))
177+
178+
query = default_query.merge("to" => locale, "textType" => "html")
179+
body = [{ "Text" => text }].to_json
180+
uri = URI(translate_endpoint)
181+
uri.query = URI.encode_www_form(query)
182+
response_body = result(uri.to_s, body, default_headers)
183+
response_body.first["translations"].first["text"]
184+
end
185+
174186
def self.translate_supported?(detected_lang, target_lang)
175187
SUPPORTED_LANG_MAPPING.keys.include?(detected_lang.to_sym) &&
176188
SUPPORTED_LANG_MAPPING.values.include?(detected_lang.to_s)

‎app/services/discourse_translator/provider/yandex.rb

+5
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ def self.translate_translatable!(translatable, target_locale_sym = I18n.locale)
148148
response_body["text"][0]
149149
end
150150

151+
def self.translate_text!(translatable, target_locale_sym = I18n.locale)
152+
# Not supported for v1.5 https://translate.yandex.com/developers
153+
raise TranslatorError.new(I18n.t("translator.not_supported"))
154+
end
155+
151156
def self.translate_supported?(detected_lang, target_lang)
152157
SUPPORTED_LANG_MAPPING.keys.include?(detected_lang.to_sym) &&
153158
SUPPORTED_LANG_MAPPING.values.include?(detected_lang.to_s)

‎spec/services/amazon_spec.rb

+26-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
end
4747
end
4848

49-
describe ".translate" do
49+
describe ".translate_translatable!" do
5050
let(:post) { Fabricate(:post) }
5151
let!(:client) { Aws::Translate::Client.new(stub_responses: true) }
5252

@@ -66,9 +66,33 @@
6666
end
6767

6868
it "raises an error when trying to translate an unsupported language" do
69-
expect { described_class.translate(post) }.to raise_error(
69+
expect { described_class.translate_translatable!(post) }.to raise_error(
7070
I18n.t("translator.failed.post", source_locale: "en", target_locale: "es"),
7171
)
7272
end
7373
end
74+
75+
describe ".translate_text!" do
76+
let!(:client) { Aws::Translate::Client.new(stub_responses: true) }
77+
78+
before do
79+
client.stub_responses(
80+
:translate_text,
81+
"UnsupportedLanguagePairException",
82+
{
83+
translated_text: "Probando traducciones",
84+
source_language_code: "en",
85+
target_language_code: "es",
86+
},
87+
)
88+
described_class.stubs(:client).returns(client)
89+
I18n.stubs(:locale).returns(:es)
90+
end
91+
92+
it "raises an error when trying to translate an unsupported language" do
93+
expect { described_class.translate_text!("derp") }.to raise_error(
94+
I18n.t("translator.not_supported", source_locale: "en", target_locale: "es"),
95+
)
96+
end
97+
end
7498
end

‎spec/services/discourse_ai_spec.rb

+14-5
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
end
3535
end
3636

37-
describe ".translate" do
37+
describe ".translate_translatable!" do
3838
before do
3939
post.set_detected_locale("de")
4040
topic.set_detected_locale("de")
@@ -44,8 +44,7 @@
4444
DiscourseAi::Completions::Llm.with_prepared_responses(
4545
[translation_json("some translated text")],
4646
) do
47-
locale, translated_text = DiscourseTranslator::Provider::DiscourseAi.translate(post)
48-
expect(locale).to eq "de"
47+
translated_text = DiscourseTranslator::Provider::DiscourseAi.translate_translatable!(post)
4948
expect(translated_text).to eq "<p>some translated text</p>"
5049
end
5150
end
@@ -55,8 +54,7 @@
5554
DiscourseAi::Completions::Llm.with_prepared_responses(
5655
[translation_json("some translated text")],
5756
) do
58-
locale, translated_text = DiscourseTranslator::Provider::DiscourseAi.translate(topic)
59-
expect(locale).to eq "de"
57+
translated_text = DiscourseTranslator::Provider::DiscourseAi.translate_translatable!(topic)
6058
expect(translated_text).to eq "some translated text"
6159
end
6260
end
@@ -73,6 +71,17 @@
7371
end
7472
end
7573

74+
describe ".translate_text!" do
75+
it "returns the translated text" do
76+
DiscourseAi::Completions::Llm.with_prepared_responses(
77+
[translation_json("some translated text")],
78+
) do
79+
translated_text = DiscourseTranslator::Provider::DiscourseAi.translate_text!("derp")
80+
expect(translated_text).to eq "some translated text"
81+
end
82+
end
83+
end
84+
7685
def locale_json(content)
7786
{ locale: content }.to_json
7887
end

‎spec/services/google_spec.rb

+21
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,25 @@
187187
expect(described_class.translate_translatable!(post)).to eq(translated_text)
188188
end
189189
end
190+
191+
describe ".translate_text!" do
192+
it "translates plain text" do
193+
text = "ABCDEFG"
194+
body = { q: text, target: "ja", key: api_key }
195+
196+
translated_text = "hur dur hur dur"
197+
stub_request(:post, DiscourseTranslator::Provider::Google::TRANSLATE_URI).with(
198+
body: URI.encode_www_form(body),
199+
headers: {
200+
"Content-Type" => "application/x-www-form-urlencoded",
201+
"Referer" => "http://test.localhost",
202+
},
203+
).to_return(
204+
status: 200,
205+
body: %{ { "data": { "translations": [ { "translatedText": "#{translated_text}" } ] } } },
206+
)
207+
208+
expect(described_class.translate_text!(text, :ja)).to eq(translated_text)
209+
end
210+
end
190211
end

‎spec/services/microsoft_spec.rb

+23-14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
before { SiteSetting.translator_enabled = true }
55
after { Discourse.redis.del(described_class.cache_key) }
66

7+
def translate_endpoint(from: "en", to: I18n.locale)
8+
uri = URI(described_class.translate_endpoint)
9+
default_query = described_class.default_query.merge("textType" => "html")
10+
default_query = default_query.merge("from" => from) if from
11+
default_query = default_query.merge("to" => to) if to
12+
uri.query = URI.encode_www_form(default_query)
13+
uri.to_s
14+
end
15+
716
describe ".detect" do
817
let(:post) { Fabricate(:post) }
918
let(:detected_lang) { "en" }
@@ -112,20 +121,6 @@ def detect_endpoint
112121
describe ".translate" do
113122
let(:post) { Fabricate(:post) }
114123

115-
def translate_endpoint
116-
uri = URI(described_class.translate_endpoint)
117-
uri.query =
118-
URI.encode_www_form(
119-
described_class.default_query.merge(
120-
"from" => "en",
121-
"to" => I18n.locale,
122-
"textType" => "html",
123-
),
124-
)
125-
126-
uri.to_s
127-
end
128-
129124
before do
130125
post.set_detected_locale("en")
131126
SiteSetting.translator_azure_subscription_key = "e1bba646088021aaf1ef972a48"
@@ -209,4 +204,18 @@ def translate_endpoint
209204
end
210205
end
211206
end
207+
208+
describe ".translate_text!" do
209+
it "translates text" do
210+
text = "ABCDEFG"
211+
SiteSetting.translator_azure_subscription_key = "123123"
212+
213+
I18n.locale = :es
214+
stub_request(:post, translate_endpoint(from: nil, to: "es")).with(
215+
{ body: [{ "Text" => text }].to_json },
216+
).to_return(status: 200, body: [{ "translations" => [{ "text" => "some text" }] }].to_json)
217+
218+
expect(described_class.translate_text!(text)).to eq("some text")
219+
end
220+
end
212221
end

0 commit comments

Comments
 (0)
Please sign in to comment.