mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:23:17 -05:00
FEATURE: Also process manually updated translations (#35276)
This is a follow up to https://github.com/discourse/discourse/pull/34900. When a user manually updates translation via the translation composer, also send the new translated cooked for post processing. I moved the Processor to core, given PostLocalizations are core feature. <img width="551" height="385" alt="Screenshot 2025-10-08 at 6 12 26 PM" src="https://github.com/user-attachments/assets/1cce7ce3-5487-4e75-90fd-440792b9a899" />
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class ProcessLocalizedCooked < ::Jobs::Base
|
||||
def execute(args)
|
||||
DistributedMutex.synchronize(
|
||||
"process_localized_cook_#{args[:post_localization_id]}",
|
||||
validity: 10.minutes,
|
||||
) do
|
||||
post_localization = PostLocalization.find_by(id: args[:post_localization_id])
|
||||
return if post_localization.blank?
|
||||
|
||||
post = post_localization.post
|
||||
return if post.blank? || post.topic.blank?
|
||||
original_cooked = post_localization.cooked
|
||||
|
||||
processor = LocalizedCookedPostProcessor.new(post_localization, post, {})
|
||||
processor.post_process
|
||||
cooked = processor.html
|
||||
|
||||
if cooked != original_cooked && cooked.present?
|
||||
post_localization.update_column(:cooked, cooked)
|
||||
MessageBus.publish("/topic/#{post.topic_id}", type: :localized, id: post.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class LocalizedCookedPostProcessor
|
||||
include CookedProcessorMixin
|
||||
|
||||
def initialize(post_localization, post, opts = {})
|
||||
@post_localization = post_localization
|
||||
@post = post
|
||||
@opts = opts
|
||||
@doc = Loofah.html5_fragment(@post_localization.cooked)
|
||||
@cooking_options = @post.cooking_options || {}
|
||||
@cooking_options[:topic_id] = @post.topic_id
|
||||
@cooking_options = @cooking_options.symbolize_keys
|
||||
@model = @post
|
||||
@category_id = @post&.topic&.category_id
|
||||
@omit_nofollow = @post.omit_nofollow?
|
||||
@size_cache = {}
|
||||
end
|
||||
|
||||
def post_process
|
||||
post_process_oneboxes
|
||||
post_process_images
|
||||
end
|
||||
|
||||
def html
|
||||
@doc.try(:to_html)
|
||||
end
|
||||
end
|
||||
@@ -7,13 +7,18 @@ class PostLocalizationCreator
|
||||
post = Post.find_by(id: post_id)
|
||||
raise Discourse::NotFound unless post
|
||||
|
||||
PostLocalization.create!(
|
||||
post_id: post.id,
|
||||
post_version: post.version,
|
||||
locale: locale,
|
||||
raw: raw,
|
||||
cooked: PrettyText.cook(raw),
|
||||
localizer_user_id: user.id,
|
||||
)
|
||||
localization =
|
||||
PostLocalization.create!(
|
||||
post: post,
|
||||
locale: locale,
|
||||
raw: raw,
|
||||
cooked: post.post_analyzer.cook(raw, post.cooking_options || {}),
|
||||
post_version: post.version,
|
||||
localizer_user_id: user.id,
|
||||
)
|
||||
|
||||
Jobs.enqueue(:process_localized_cooked, post_localization_id: localization.id)
|
||||
|
||||
localization
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,10 @@ class PostLocalizationDestroyer
|
||||
|
||||
localization = PostLocalization.find_by(post_id: post_id, locale: locale)
|
||||
raise Discourse::NotFound unless localization
|
||||
post = localization.post
|
||||
|
||||
localization.destroy
|
||||
localization.destroy!
|
||||
|
||||
post.publish_change_to_clients! :revised
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,9 @@ class PostLocalizationUpdater
|
||||
localization.localizer_user_id = user.id
|
||||
localization.post_version = post.version
|
||||
localization.save!
|
||||
|
||||
Jobs.enqueue(:process_localized_cooked, post_localization_id: localization.id)
|
||||
|
||||
localization
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Translation
|
||||
class LocalizedCookedPostProcessor
|
||||
include CookedProcessorMixin
|
||||
|
||||
def initialize(post_localization, post, opts = {})
|
||||
@post_localization = post_localization
|
||||
@post = post
|
||||
@opts = opts
|
||||
@doc = Loofah.html5_fragment(@post_localization.cooked)
|
||||
@cooking_options = @post.cooking_options || {}
|
||||
@cooking_options[:topic_id] = @post.topic_id
|
||||
@cooking_options = @cooking_options.symbolize_keys
|
||||
@model = @post
|
||||
@category_id = @post&.topic&.category_id
|
||||
@omit_nofollow = @post.omit_nofollow?
|
||||
@size_cache = {}
|
||||
end
|
||||
|
||||
def post_process
|
||||
post_process_oneboxes
|
||||
post_process_images
|
||||
end
|
||||
|
||||
def html
|
||||
@doc.try(:to_html)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "localized_cooked_post_processor"
|
||||
|
||||
module DiscourseAi
|
||||
module Translation
|
||||
class PostLocalizer
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe Jobs::ProcessLocalizedCooked do
|
||||
subject(:job) { described_class.new }
|
||||
|
||||
fab!(:post)
|
||||
fab!(:post_localization) do
|
||||
Fabricate(
|
||||
:post_localization,
|
||||
post: post,
|
||||
locale: "ja",
|
||||
raw: "これはテスト投稿です。",
|
||||
cooked: "<p>これはテスト投稿です。</p>",
|
||||
)
|
||||
end
|
||||
|
||||
it "returns when the post_localization cannot be found" do
|
||||
expect { job.execute(post_localization_id: 999_999) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "returns when the post_localization's post is deleted" do
|
||||
post_localization.post.destroy!
|
||||
expect { job.execute(post_localization_id: post_localization.id) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "returns when the post_localization's topic is deleted" do
|
||||
post_localization.post.topic.destroy!
|
||||
expect { job.execute(post_localization_id: post_localization.id) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "does not replace cooked when LocalizedCookedPostProcessor returns blank" do
|
||||
LocalizedCookedPostProcessor.any_instance.expects(:html).returns(" ")
|
||||
original_cooked = post_localization.cooked
|
||||
|
||||
job.execute(post_localization_id: post_localization.id)
|
||||
|
||||
post_localization.reload
|
||||
expect(post_localization.cooked).to eq(original_cooked)
|
||||
end
|
||||
|
||||
it "updates cooked when processor makes changes" do
|
||||
processed_html = "<p>これはテスト投稿です。</p><div class='onebox'>Processed</div>"
|
||||
LocalizedCookedPostProcessor.any_instance.expects(:html).returns(processed_html)
|
||||
|
||||
job.execute(post_localization_id: post_localization.id)
|
||||
|
||||
post_localization.reload
|
||||
expect(post_localization.cooked).to eq(processed_html)
|
||||
end
|
||||
|
||||
it "does not update cooked when processor returns same content" do
|
||||
LocalizedCookedPostProcessor.any_instance.expects(:html).returns(post_localization.cooked)
|
||||
|
||||
expect { job.execute(post_localization_id: post_localization.id) }.not_to change {
|
||||
post_localization.reload.cooked
|
||||
}
|
||||
end
|
||||
|
||||
it "publishes MessageBus notification when cooked changes" do
|
||||
processed_html = "<p>これはテスト投稿です。</p><div class='onebox'>Processed</div>"
|
||||
LocalizedCookedPostProcessor.any_instance.expects(:html).returns(processed_html)
|
||||
|
||||
messages =
|
||||
MessageBus.track_publish("/topic/#{post.topic_id}") do
|
||||
job.execute(post_localization_id: post_localization.id)
|
||||
end
|
||||
|
||||
expect(messages.length).to eq(1)
|
||||
expect(messages.first.data[:type]).to eq(:localized)
|
||||
expect(messages.first.data[:id]).to eq(post.id)
|
||||
end
|
||||
|
||||
it "does not publish MessageBus notification when cooked unchanged" do
|
||||
LocalizedCookedPostProcessor.any_instance.expects(:html).returns(post_localization.cooked)
|
||||
|
||||
messages =
|
||||
MessageBus.track_publish("/topic/#{post.topic_id}") do
|
||||
job.execute(post_localization_id: post_localization.id)
|
||||
end
|
||||
|
||||
expect(messages.length).to eq(0)
|
||||
end
|
||||
|
||||
it "processes oneboxes and images" do
|
||||
stub_image_size
|
||||
onebox_html = <<~HTML
|
||||
<aside class="onebox">
|
||||
<article class="onebox-body">
|
||||
<h3><a href="https://www.discourse.org">Discourse</a></h3>
|
||||
<p>A platform for community discussion</p>
|
||||
</article>
|
||||
</aside>
|
||||
HTML
|
||||
|
||||
post_localization.update!(
|
||||
raw: "Check out https://www.discourse.org",
|
||||
cooked: "<p>Check out https://www.discourse.org</p>\n#{onebox_html}",
|
||||
)
|
||||
|
||||
job.execute(post_localization_id: post_localization.id)
|
||||
|
||||
post_localization.reload
|
||||
expect(post_localization.cooked).to include("onebox")
|
||||
end
|
||||
end
|
||||
@@ -26,6 +26,12 @@ describe PostLocalizationCreator do
|
||||
)
|
||||
end
|
||||
|
||||
it "enqueues ProcessLocalizedCook job" do
|
||||
loc = described_class.create(post_id: post.id, locale:, raw:, user:)
|
||||
|
||||
expect_job_enqueued(job: :process_localized_cooked, args: { post_localization_id: loc.id })
|
||||
end
|
||||
|
||||
it "raises not found if the post is missing" do
|
||||
expect { described_class.create(post_id: -1, locale:, raw:, user:) }.to raise_error(
|
||||
Discourse::NotFound,
|
||||
|
||||
@@ -26,4 +26,15 @@ describe PostLocalizationDestroyer do
|
||||
described_class.destroy(post_id: post.id, locale: "nope", acting_user: user)
|
||||
}.to raise_error(Discourse::NotFound)
|
||||
end
|
||||
|
||||
it "publishes MessageBus notification" do
|
||||
messages =
|
||||
MessageBus.track_publish("/topic/#{post.topic_id}") do
|
||||
described_class.destroy(post_id: post.id, locale: locale, acting_user: user)
|
||||
end
|
||||
|
||||
expect(messages.length).to eq(1)
|
||||
expect(messages.first.data[:type]).to eq(:revised)
|
||||
expect(messages.first.data[:id]).to eq(post.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,6 +29,12 @@ describe PostLocalizationUpdater do
|
||||
)
|
||||
end
|
||||
|
||||
it "enqueues ProcessLocalizedCook job" do
|
||||
loc = described_class.update(post_id: post.id, locale: locale, raw: new_raw, user: user)
|
||||
|
||||
expect_job_enqueued(job: :process_localized_cooked, args: { post_localization_id: loc.id })
|
||||
end
|
||||
|
||||
it "raises not found if the localization is missing" do
|
||||
expect {
|
||||
described_class.update(post_id: post.id, locale: "nope", raw: new_raw, user: user)
|
||||
|
||||
Reference in New Issue
Block a user