2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-10-03 17:21:20 +08:00

FEATURE: Show translated category names from localizations table if they exist for the user locale (#32380)

This PR adds category localization support to core.

- Depends on https://github.com/discourse/discourse/pull/32378
- Creates the required table `category_localizations`
- Makes use of the site category cache to display localized categories
- Supports name and description
- This does not yet include /categories page. (coming in a next PR)

## Before

<img width="1089" alt="Screenshot 2025-04-21 at 4 31 43 PM"
src="https://github.com/user-attachments/assets/9e49b21b-b16a-43d2-9b16-0fd0324a21ca"
/>

## After

<img width="1085" alt="Screenshot 2025-04-21 at 4 32 42 PM"
src="https://github.com/user-attachments/assets/7bfa21a2-6df2-4cdf-a00a-d044c3526a40"
/>
This commit is contained in:
Natalie Tay 2025-04-22 15:13:19 +08:00 committed by GitHub
parent d5b2b186b4
commit b0ab1b2321
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 145 additions and 15 deletions

View file

@ -36,6 +36,7 @@ class Category < ActiveRecord::Base
has_many :category_users
has_many :category_featured_topics
has_many :featured_topics, through: :category_featured_topics, source: :topic
has_many :category_localizations, dependent: :destroy

has_many :category_groups, dependent: :destroy
has_many :category_moderation_groups, dependent: :destroy

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true

class CategoryLocalization < ActiveRecord::Base
belongs_to :category

validates :locale, presence: true, length: { maximum: 20 }
validates :name, presence: true, length: { maximum: 50 }
validates :category_id, uniqueness: { scope: :locale }

after_commit :invalidate_site_cache

def invalidate_site_cache
I18n.with_locale(locale) { Site.clear_cache }
end
end

# == Schema Information
#
# Table name: category_localizations
#
# id :bigint not null, primary key
# category_id :bigint not null
# locale :string(20) not null
# name :string(50) not null
# description :text
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_category_localizations_on_category_id (category_id)
# index_category_localizations_on_category_id_and_locale (category_id,locale) UNIQUE
#

View file

@ -73,24 +73,35 @@ class Site
categories =
begin
query =
Category
.includes(
:uploaded_logo,
:uploaded_logo_dark,
:uploaded_background,
:uploaded_background_dark,
:tags,
:tag_groups,
:form_templates,
category_required_tag_groups: :tag_group,
)
.joins("LEFT JOIN topics t on t.id = categories.topic_id")
.select("categories.*, t.slug topic_slug")
.order(:position)
Category.includes(
:uploaded_logo,
:uploaded_logo_dark,
:uploaded_background,
:uploaded_background_dark,
:tags,
:tag_groups,
:form_templates,
category_required_tag_groups: :tag_group,
).joins("LEFT JOIN topics t on t.id = categories.topic_id")

if SiteSetting.experimental_content_localization
locale = I18n.locale.to_s
query =
query.joins(
"LEFT JOIN category_localizations cl ON cl.category_id = categories.id AND cl.locale = '#{ActiveRecord::Base.connection.quote_string(locale)}'",
).select(
"categories.*,
t.slug topic_slug,
COALESCE(cl.name, categories.name) AS name,
COALESCE(cl.description, categories.description) AS description",
)
else
query = query.select("categories.*, t.slug topic_slug")
end

query = query.order(:position)
query =
DiscoursePluginRegistry.apply_modifier(:site_all_categories_cache_query, query, self)

query.to_a
end


View file

@ -2768,6 +2768,7 @@ en:
about_page_hidden_groups: "Do not show members of specific groups on the /about page."
adobe_analytics_tags_url: "Adobe Analytics tags URL (`https://assets.adobedtm.com/...`)"
view_raw_email_allowed_groups: "Groups which can view the raw email content of a post if it was created by an incoming email. This includes email headers and other technical information."
experimental_content_localization: "Displays localized content for users based on their language preferences. Such content may include categories, tags, posts, and topics. This feature is under heavy development."
errors:
invalid_css_color: "Invalid color. Enter a color name or hex value."
invalid_email: "Invalid email address."

View file

@ -3973,3 +3973,6 @@ experimental:
client: true
default: false
hidden: true
experimental_content_localization:
client: true
default: false

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true

class CreateCategoryLocalizationTable < ActiveRecord::Migration[7.2]
def change
create_table :category_localizations do |t|
t.references :category, null: false
t.string :locale, limit: 20, null: false
t.string :name, limit: 50, null: false
t.text :description, null: true

t.timestamps null: false
end

add_index :category_localizations, %i[category_id locale], unique: true
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true

Fabricator(:category_localization) do
category
locale "ja"
name { sequence(:name) { |i| "ワク" * (i + 1) } }
description "日本のディスカッション"
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true

describe CategoryLocalization do
context "when commit" do
it "clears the site cache for the locale" do
category = Fabricate(:category, name: "yy")

I18n.locale = "es"
expect(Site.all_categories_cache.pluck(:name)).to include("yy")

I18n.locale = "en"
expect(Site.all_categories_cache.pluck(:name)).to include("yy")

category.update_columns(name: "zz")

I18n.locale = "es"
expect(Site.all_categories_cache.pluck(:name)).to include("yy")
I18n.locale = "en"
expect(Site.all_categories_cache.pluck(:name)).to include("yy")

Fabricate(:category_localization, name: "Japón", locale: "es", category:)

I18n.locale = "es"
expect(Site.all_categories_cache.pluck(:name)).to include("zz")
I18n.locale = "en"
expect(Site.all_categories_cache.pluck(:name)).to include("yy")
end
end
end

View file

@ -344,5 +344,33 @@ RSpec.describe Site do
&modifier_block
)
end

describe "experimental_content_localization" do
it "returns localized category names when enabled" do
SiteSetting.experimental_content_localization = true

localization = Fabricate(:category_localization)
category = localization.category
locale = localization.locale.to_sym

I18n.locale = locale

all_categories_cache = Site.all_categories_cache
cached_category = all_categories_cache.find { |c| c[:id] == category.id }
expect(cached_category[:name]).to eq(localization.name)
expect(cached_category[:description]).to eq(localization.description)
end

it "returns original names when enabled" do
SiteSetting.experimental_content_localization = true

category = Fabricate(:category, name: "derp", description: "derp derp")

all_categories_cache = Site.all_categories_cache
cached_category = all_categories_cache.find { |c| c[:id] == category.id }
expect(cached_category[:name]).to eq(category.name)
expect(cached_category[:description]).to eq(category.description)
end
end
end
end