Skip to content

Commit 7c3bc25

Browse files
authored
FEATURE: Automatically detect and translate posts into Post and PostLocalization (#290)
With the introduction of Post.locale and PostLocalization in core, we want to use this plugin's translator provider to automatically detect and translate posts and save it into those tables.
1 parent 5796fa3 commit 7c3bc25

File tree

7 files changed

+205
-1
lines changed

7 files changed

+205
-1
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
module Jobs
4+
class DetectTranslatePost < ::Jobs::Base
5+
def execute(args)
6+
return unless SiteSetting.translator_enabled
7+
return unless SiteSetting.experimental_content_translation
8+
return if args[:post_id].blank?
9+
10+
post = Post.find(args[:post_id])
11+
return if post.blank? || post.raw.blank? || post.deleted_at.present? || post.user_id <= 0
12+
13+
detected_locale = DiscourseTranslator::PostLocaleDetector.detect_locale(post)
14+
15+
locales = SiteSetting.automatic_translation_target_languages.split("|")
16+
return if locales.blank?
17+
18+
locales.each do |locale|
19+
next if locale == detected_locale
20+
21+
begin
22+
DiscourseTranslator::PostTranslator.translate(post, locale)
23+
rescue => e
24+
Rails.logger.error(
25+
"Discourse Translator: Failed to translate post #{post.id} to #{locale}: #{e.message}",
26+
)
27+
end
28+
end
29+
end
30+
end
31+
end

app/jobs/regular/translate_posts.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ def execute(args)
1414
locales = SiteSetting.automatic_translation_target_languages.split("|")
1515
return if locales.blank?
1616

17+
limit = args[:limit] || BATCH_SIZE
18+
1719
locales.each do |locale|
1820
posts =
1921
Post
@@ -26,7 +28,7 @@ def execute(args)
2628
.where.not(locale: nil)
2729
.where.not(locale: locale)
2830
.where("pl.id IS NULL")
29-
.limit(BATCH_SIZE)
31+
.limit(limit)
3032

3133
next if posts.empty?
3234

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
3+
module Jobs
4+
class PostTranslationBackfill < ::Jobs::Scheduled
5+
every 5.minutes
6+
cluster_concurrency 1
7+
8+
def execute(args)
9+
return unless SiteSetting.translator_enabled
10+
return unless SiteSetting.experimental_content_translation
11+
12+
return if SiteSetting.automatic_translation_target_languages.blank?
13+
return if SiteSetting.automatic_translation_backfill_rate == 0
14+
15+
Jobs.enqueue(:translate_posts, limit: SiteSetting.automatic_translation_backfill_rate)
16+
end
17+
end
18+
end

lib/discourse_translator/automatic_translations.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ def inject(plugin)
77
if translatable?(post)
88
Jobs.enqueue(:translate_translatable, type: "Post", translatable_id: post.id)
99
end
10+
11+
if SiteSetting.experimental_content_localization
12+
Jobs.enqueue(:detect_translate_post, post_id: post.id)
13+
end
1014
end
1115

1216
plugin.on(:topic_created) do |topic|
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
describe Jobs::DetectTranslatePost do
4+
fab!(:post)
5+
subject(:job) { described_class.new }
6+
7+
let(:locales) { %w[en ja] }
8+
9+
before do
10+
SiteSetting.translator_enabled = true
11+
SiteSetting.experimental_content_translation = true
12+
SiteSetting.automatic_translation_backfill_rate = 1
13+
SiteSetting.automatic_translation_target_languages = locales.join("|")
14+
end
15+
16+
it "does nothing when translator is disabled" do
17+
SiteSetting.translator_enabled = false
18+
DiscourseTranslator::PostLocaleDetector.expects(:detect_locale).never
19+
DiscourseTranslator::PostTranslator.expects(:translate).never
20+
21+
job.execute({ post_id: post.id })
22+
end
23+
24+
it "does nothing when content translation is disabled" do
25+
SiteSetting.experimental_content_translation = false
26+
DiscourseTranslator::PostLocaleDetector.expects(:detect_locale).never
27+
DiscourseTranslator::PostTranslator.expects(:translate).never
28+
29+
job.execute({ post_id: post.id })
30+
end
31+
32+
it "detects locale" do
33+
SiteSetting.translator_enabled = true
34+
DiscourseTranslator::PostLocaleDetector.expects(:detect_locale).with(post).once
35+
DiscourseTranslator::PostTranslator.expects(:translate).twice
36+
37+
job.execute({ post_id: post.id })
38+
end
39+
40+
it "skips bot posts" do
41+
post.update!(user: Discourse.system_user)
42+
DiscourseTranslator::PostTranslator.expects(:translate).never
43+
44+
job.execute({ post_id: post.id })
45+
end
46+
47+
it "does not translate when no target languages are configured" do
48+
SiteSetting.automatic_translation_target_languages = ""
49+
DiscourseTranslator::PostLocaleDetector.expects(:detect_locale).with(post).returns("en")
50+
DiscourseTranslator::PostTranslator.expects(:translate).never
51+
52+
job.execute({ post_id: post.id })
53+
end
54+
55+
it "skips translating to the post's language" do
56+
post.update(locale: "en")
57+
DiscourseTranslator::PostLocaleDetector.expects(:detect_locale).with(post).returns("en")
58+
DiscourseTranslator::PostTranslator.expects(:translate).with(post, "en").never
59+
DiscourseTranslator::PostTranslator.expects(:translate).with(post, "ja").once
60+
61+
job.execute({ post_id: post.id })
62+
end
63+
64+
it "handles translation errors gracefully" do
65+
post.update(locale: "en")
66+
DiscourseTranslator::PostLocaleDetector.expects(:detect_locale).with(post).returns("en")
67+
DiscourseTranslator::PostTranslator.expects(:translate).raises(StandardError.new("API error"))
68+
69+
expect { job.execute({ post_id: post.id }) }.not_to raise_error
70+
end
71+
end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# frozen_string_literal: true
2+
3+
describe Jobs::PostTranslationBackfill do
4+
before do
5+
SiteSetting.automatic_translation_backfill_rate = 100
6+
SiteSetting.automatic_translation_target_languages = "en"
7+
end
8+
9+
it "does not enqueue post translation when translator disabled" do
10+
SiteSetting.translator_enabled = false
11+
12+
described_class.new.execute({})
13+
14+
expect_not_enqueued_with(job: :translate_posts)
15+
end
16+
17+
it "does not enqueue post translation when experimental translation disabled" do
18+
SiteSetting.translator_enabled = true
19+
SiteSetting.experimental_content_translation = false
20+
21+
described_class.new.execute({})
22+
23+
expect_not_enqueued_with(job: :translate_posts)
24+
end
25+
26+
it "does not enqueue psot translation if backfill languages are not set" do
27+
SiteSetting.translator_enabled = true
28+
SiteSetting.experimental_content_translation = true
29+
SiteSetting.automatic_translation_target_languages = ""
30+
31+
described_class.new.execute({})
32+
33+
expect_not_enqueued_with(job: :translate_posts)
34+
end
35+
36+
it "does not enqueue psot translation if backfill limit is set to 0" do
37+
SiteSetting.translator_enabled = true
38+
SiteSetting.experimental_content_translation = true
39+
SiteSetting.automatic_translation_backfill_rate = 0
40+
41+
described_class.new.execute({})
42+
43+
expect_not_enqueued_with(job: :translate_posts)
44+
end
45+
46+
it "enqueues post translation with correct limit" do
47+
SiteSetting.translator_enabled = true
48+
SiteSetting.experimental_content_translation = true
49+
SiteSetting.automatic_translation_backfill_rate = 10
50+
51+
described_class.new.execute({})
52+
53+
expect_job_enqueued(job: :translate_posts, args: { limit: 10 })
54+
end
55+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
describe DiscourseTranslator::AutomaticTranslations do
4+
before { SiteSetting.translator_enabled = true }
5+
6+
describe "upon post process cooked" do
7+
it "enqueues detect post locale and translate post job" do
8+
SiteSetting.experimental_content_localization = true
9+
post = Fabricate(:post)
10+
CookedPostProcessor.new(post).post_process
11+
12+
expect_job_enqueued(job: :detect_translate_post, args: { post_id: post.id })
13+
end
14+
15+
it "does not enqueue if setting disabled" do
16+
SiteSetting.experimental_content_localization = false
17+
post = Fabricate(:post)
18+
CookedPostProcessor.new(post).post_process
19+
20+
expect(job_enqueued?(job: :detect_translate_post, args: { post_id: post.id })).to eq false
21+
end
22+
end
23+
end

0 commit comments

Comments
 (0)