0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-04 10:39:43 +08:00
discourse/spec/serializers/about_serializer_spec.rb
Natalie Tay af11f8992c
FEATURE: Localizable /about page fields via its settings page (#41123)
The /about page (unlike /guidelines or /tos) is not backed by a topic,
but instead via a bunch of site settings.

If it were backed by a topic, localization would be easy as it can just
use TopicLocalizations. We also can't swap it out to use a topic, since
there are so many fields on the about page that can't be safely
contained in a topic.

----------

This PR adds SiteSettingLocalization, allowing new attributes in site
settings config for localization.

For now this is wired into the About page settings flow:
  - admins can select a non-default locale on /admin/config/about
  - only translatable About fields are shown for that locale
  - localized About values are used on /about and /about.json
- company_url can be set manually per locale, but is excluded from
automatic translation
- ~~AI backfill can translate eligible site setting localizations~~ no
backfill (added in
https://github.com/discourse/discourse/pull/41123/commits/386c655d2f44aca54440c069bd06ea8007793d84,
removed in
https://github.com/discourse/discourse/pull/41123/commits/6f8ec9cc43dca4195bda638b1ce980eb68a59941.
about page has very non automa-ble fields

This intentionally keeps the scope narrow. It does not make every site
setting localizable, and any other setting can be localized by adding to
that allowlist.

Some screenshots

|page | 📸  | 
|--|--|
| eng setting | <img width="1278" height="871" alt="Screenshot
2026-06-23 at 11 00 34 PM"
src="https://github.com/user-attachments/assets/59431464-fb05-41d9-8c4f-fd9aae7fb45f"
/>
| ja setting | <img width="1278" height="871" alt="Screenshot 2026-06-23
at 11 01 20 PM"
src="https://github.com/user-attachments/assets/73cd2878-f59a-4dc4-a76d-2f46b74a6eed"
/>
| ja anon about page | <img width="1278" height="871" alt="Screenshot
2026-06-23 at 11 01 48 PM"
src="https://github.com/user-attachments/assets/02464a5f-9ca0-4519-9521-be41498c1967"
/>
2026-06-26 20:41:08 +08:00

131 lines
4.6 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe AboutSerializer do
fab!(:user)
describe "localized site settings" do
before do
SiteSetting.content_localization_enabled = true
SiteSetting.title = "English title"
SiteSetting.site_description = "English description"
SiteSetting.extended_site_description = "English **extended** description"
SiteSetting.extended_site_description_cooked =
PrettyText.markdown(SiteSetting.extended_site_description)
SiteSettingLocalization.create!(setting_name: "title", locale: "ja", value: "日本語タイトル")
SiteSettingLocalization.create!(
setting_name: "site_description",
locale: "ja",
value: "日本語の説明",
)
SiteSettingLocalization.create!(
setting_name: "extended_site_description",
locale: "ja",
value: "日本語の **詳細** 説明",
)
end
it "serializes localized about content" do
json =
AboutSerializer.new(
About.new(user, locale: "ja"),
scope: Guardian.new(user),
root: nil,
).as_json
aggregate_failures do
expect(json[:title]).to eq("日本語タイトル")
expect(json[:description]).to eq("日本語の説明")
expect(json[:extended_site_description]).to include("<strong>詳細</strong>")
end
end
it "serializes original about content when requested" do
json =
AboutSerializer.new(
About.new(user, locale: "ja", show_original: true),
scope: Guardian.new(user),
root: nil,
).as_json
aggregate_failures do
expect(json[:title]).to eq("English title")
expect(json[:description]).to eq("English description")
expect(json[:extended_site_description]).to include("<strong>extended</strong>")
end
end
end
context "when login_required is enabled" do
before do
SiteSetting.login_required = true
SiteSetting.contact_url = "https://example.com/contact"
SiteSetting.contact_email = "example@foobar.com"
end
it "contact details are hidden from anonymous users" do
json = AboutSerializer.new(About.new(nil), scope: Guardian.new(nil), root: nil).as_json
expect(json[:contact_url]).to eq(nil)
expect(json[:contact_email]).to eq(nil)
end
it "contact details are visible to regular users" do
json = AboutSerializer.new(About.new(user), scope: Guardian.new(user), root: nil).as_json
expect(json[:contact_url]).to eq(SiteSetting.contact_url)
expect(json[:contact_email]).to eq(SiteSetting.contact_email)
end
end
context "when login_required is disabled" do
before do
SiteSetting.login_required = false
SiteSetting.contact_url = "https://example.com/contact"
SiteSetting.contact_email = "example@foobar.com"
end
it "contact details are visible to anonymous users" do
json = AboutSerializer.new(About.new(nil), scope: Guardian.new(nil), root: nil).as_json
expect(json[:contact_url]).to eq(SiteSetting.contact_url)
expect(json[:contact_email]).to eq(SiteSetting.contact_email)
end
it "contact details are visible to regular users" do
json = AboutSerializer.new(About.new(user), scope: Guardian.new(user), root: nil).as_json
expect(json[:contact_url]).to eq(SiteSetting.contact_url)
expect(json[:contact_email]).to eq(SiteSetting.contact_email)
end
end
describe "#stats" do
after do
DiscoursePluginRegistry.reset_register!(:private_stat)
DiscoursePluginRegistry.reset_register!(:exposable_stat)
end
let(:plugin) { Plugin::Instance.new }
it "serialize exposable stats only" do
Discourse.redis.del(About.stats_cache_key)
plugin.register_stat("private_stat", expose_via_api: false) do
{ :last_day => 1, "7_days" => 2, "30_days" => 3, :count => 4 }
end
plugin.register_stat("exposable_stat", expose_via_api: true) do
{ :last_day => 11, "7_days" => 12, "30_days" => 13, :count => 14 }
end
serializer = AboutSerializer.new(About.new(user), scope: Guardian.new(user), root: nil)
json = serializer.as_json
stats = json[:stats]
expect(stats["exposable_stat_last_day"]).to be(11)
expect(stats["exposable_stat_7_days"]).to be(12)
expect(stats["exposable_stat_30_days"]).to be(13)
expect(stats["exposable_stat_count"]).to be(14)
expect(stats["private_stat_last_day"]).not_to be_present
expect(stats["private_stat_7_days"]).not_to be_present
expect(stats["private_stat_30_days"]).not_to be_present
expect(stats["private_stat_count"]).not_to be_present
end
end
end