0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/spec/jobs/process_localized_cooked_spec.rb
Natalie Tay e4b67f4b32
FEATURE: Localize local oneboxes (#40493)
We currently see unlocalized oneboxes even when
`content_localization_enabled`. An internal topic onebox stored the
linked topic's title and excerpt in its original language, so a reader
using content localization saw it untranslated even when a translation
existed.

This shows internal topic oneboxes in the reader's language via two
purpose-built paths (no rewriting cooked HTML at request time):

- For translated posts, `LocalizedCookedPostProcessor` bakes the card in
the localization's locale at cook time; the original cooked is
untouched.
- For "original posts", the serializer adds a `localized_oneboxes` map
(built once per page from `topic_links`, scoped to onebox links + the
reader's locale family), and a post-cooked decorator swaps the
title/excerpt into the rendered card.

Falls back to the original when no translation exists, respects "show
original", and never exposes a title/preview the reader couldn't already
see. "Rebuild HTML" now refreshes localizations too, without
re-translating.
2026-06-08 11:49:55 +08:00

161 lines
5.3 KiB
Ruby
Vendored

# 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" 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 "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
describe "recook" do
it "re-cooks the localization from its raw before processing when recook is true" do
post_localization.update!(raw: "新しい本文です。", cooked: "<p>古いHTML</p>")
job.execute(post_localization_id: post_localization.id, recook: true)
post_localization.reload
expect(post_localization.cooked).to include("")
expect(post_localization.cooked).not_to include("HTML")
end
it "uses the stored cooked when recook is not set" do
post_localization.update!(raw: "", cooked: "<p>HTML</p>")
job.execute(post_localization_id: post_localization.id)
expect(post_localization.reload.cooked).to include("古いHTML")
end
end
describe "topic localization excerpt" do
fab!(:topic)
fab!(:first_post) { Fabricate(:post, topic: topic, post_number: 1) }
fab!(:first_post_localization) do
Fabricate(
:post_localization,
post: first_post,
locale: "ja",
raw: "これは最初の投稿です。",
cooked: "<p>これは最初の投稿です。</p>",
)
end
fab!(:topic_localization) do
Fabricate(:topic_localization, topic: topic, locale: "ja", title: "")
end
it "updates topic localization excerpt when processing first post" do
job.execute(post_localization_id: first_post_localization.id)
topic_localization.reload
expect(topic_localization.excerpt).to eq("稿")
end
it "does not update excerpt when processing non-first post" do
second_post = Fabricate(:post, topic: topic, post_number: 2)
second_post_localization =
Fabricate(
:post_localization,
post: second_post,
locale: "ja",
raw: "2稿",
cooked: "<p>2稿</p>",
)
job.execute(post_localization_id: second_post_localization.id)
topic_localization.reload
expect(topic_localization.excerpt).to be_nil
end
it "does not error when topic localization does not exist" do
topic_localization.destroy!
expect { job.execute(post_localization_id: first_post_localization.id) }.not_to raise_error
end
end
end