Skip to content

DEV: Use localizable concerns across post, topic, categories #34137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/models/category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Category < ActiveRecord::Base
include CategoryHashtag
include AnonCacheInvalidator
include HasDestroyedWebHook
include Localizable

SLUG_REF_SEPARATOR = ":"
DEFAULT_TEXT_COLORS = %w[FFFFFF 000000]
Expand Down
2 changes: 2 additions & 0 deletions app/models/category_localization.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class CategoryLocalization < ActiveRecord::Base
include LocaleMatchable

belongs_to :category

validates :locale, presence: true, length: { maximum: 20 }
Expand Down
13 changes: 13 additions & 0 deletions app/models/concerns/locale_matchable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module LocaleMatchable
extend ActiveSupport::Concern

included do
scope :matching_locale,
->(locale) do
regionless_locale = locale.to_s.split("_").first
where("locale LIKE ?", "#{regionless_locale}%")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious, what does regionless mean here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like, en is the locale in en_GB we could define GB as a region (/country)

end
end
end
18 changes: 18 additions & 0 deletions app/models/concerns/localizable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Localizable
extend ActiveSupport::Concern

included { has_many :localizations, class_name: "#{model_name}Localization", dependent: :destroy }

def get_localization(locale = I18n.locale)
locale_str = locale.to_s.sub("-", "_")

# prioritise exact match
if match = localizations.find { |l| l.locale == locale_str }
return match
end

localizations.find { |l| LocaleNormalizer.is_same?(l.locale, locale_str) }
end
end
14 changes: 1 addition & 13 deletions app/models/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Post < ActiveRecord::Base
include Searchable
include HasCustomFields
include LimitedEdit
include Localizable

self.ignored_columns = [
"avg_time", # TODO: Remove when 20240212034010_drop_deprecated_columns has been promoted to pre-deploy
Expand Down Expand Up @@ -67,8 +68,6 @@ class Post < ActiveRecord::Base

has_many :user_actions, foreign_key: :target_post_id

has_many :post_localizations, dependent: :destroy

belongs_to :image_upload, class_name: "Upload"

has_many :post_hotlinked_media, dependent: :destroy, class_name: "PostHotlinkedMedia"
Expand Down Expand Up @@ -1343,17 +1342,6 @@ def in_user_locale?
LocaleNormalizer.is_same?(locale, I18n.locale)
end

def get_localization(locale = I18n.locale)
locale_str = locale.to_s.sub("-", "_")

# prioritise exact match
if match = post_localizations.find { |l| l.locale == locale_str }
return match
end

post_localizations.find { |l| LocaleNormalizer.is_same?(l.locale, locale_str) }
end

private

def parse_quote_into_arguments(quote)
Expand Down
2 changes: 2 additions & 0 deletions app/models/post_localization.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class PostLocalization < ActiveRecord::Base
include LocaleMatchable

belongs_to :post

validates :post_version, presence: true
Expand Down
16 changes: 2 additions & 14 deletions app/models/topic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class NotAllowed < StandardError
include Trashable
include Searchable
include LimitedEdit
include Localizable
extend Forwardable

EXTERNAL_ID_MAX_LENGTH = 50
Expand All @@ -31,8 +32,6 @@ class NotAllowed < StandardError

attr_accessor :allowed_user_ids, :allowed_group_ids, :tags_changed, :includes_destination_category

has_many :topic_localizations, dependent: :destroy

def self.max_fancy_title_length
400
end
Expand Down Expand Up @@ -2137,24 +2136,13 @@ def self.editable_custom_fields(guardian)
end

def has_localization?(locale = I18n.locale)
topic_localizations.exists?(locale: locale.to_s.sub("-", "_"))
localizations.exists?(locale: locale.to_s.sub("-", "_"))
end

def in_user_locale?
LocaleNormalizer.is_same?(locale, I18n.locale)
end

def get_localization(locale = I18n.locale)
locale_str = locale.to_s.sub("-", "_")

# prioritise exact match
if match = topic_localizations.find { |l| l.locale == locale_str }
return match
end

topic_localizations.find { |l| LocaleNormalizer.is_same?(l.locale, locale_str) }
end

private

def invite_to_private_message(invited_by, target_user, guardian)
Expand Down
2 changes: 1 addition & 1 deletion app/models/topic_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def load_topics
{ category: :parent_category },
]

topic_preloader_associations << :topic_localizations if SiteSetting.content_localization_enabled
topic_preloader_associations << :localizations if SiteSetting.content_localization_enabled

DiscoursePluginRegistry.topic_preloader_associations.each do |a|
fields = a[:fields]
Expand Down
2 changes: 2 additions & 0 deletions app/models/topic_localization.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class TopicLocalization < ActiveRecord::Base
include LocaleMatchable

belongs_to :topic

validates :locale, presence: true, length: { maximum: 20 }
Expand Down
4 changes: 2 additions & 2 deletions app/serializers/post_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -656,11 +656,11 @@ def include_mentioned_users?
end

def has_post_localizations
object.post_localizations.any?
object.localizations.any?
end

def post_localizations_count
object.post_localizations.size
object.localizations.size
end

def include_has_post_localizations?
Expand Down
2 changes: 1 addition & 1 deletion lib/topic_view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ def filter_posts_by_ids(post_ids)
:deleted_by,
:incoming_email,
:image_upload,
:post_localizations,
:localizations,
)

@posts = @posts.includes({ user: :user_status }) if SiteSetting.enable_user_status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ def execute(args)

locales.each do |locale|
next if LocaleNormalizer.is_same?(locale, detected_locale)

regionless_locale = locale.split("_").first
exists = post.post_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
exists = post.localizations.matching_locale(locale).exists?

if exists && !DiscourseAi::Translation::PostLocalizer.has_relocalize_quota?(post, locale)
next
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ def execute(args)

locales.each do |locale|
next if LocaleNormalizer.is_same?(locale, detected_locale)
regionless_locale = locale.split("_").first
next if topic.topic_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
next if topic.localizations.matching_locale(locale).exists?

begin
DiscourseAi::Translation::TopicLocalizer.localize(topic, locale)
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/topic_query_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2400,7 +2400,7 @@ def suggested_for(topic)
topic_query = TopicQuery.new(user)
topic_list = topic_query.list_latest

expect(topic_list.topics.first.association(:topic_localizations).loaded?).to eq(true)
expect(topic_list.topics.first.association(:localizations).loaded?).to eq(true)

queries =
track_sql_queries { topic_list.topics.each { |topic| topic.get_localization&.fancy_title } }
Expand Down
Loading