0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-04 10:39:43 +08:00
discourse/app/serializers/topic_view_serializer.rb
Natalie Tay cff8f6ea4d
FEATURE: Allow selection of multiple content languages when viewing topics and posts (#42128)
Related:
https://meta.discourse.org/t/multi-language-preferences-for-displaying-localized-content/381116

Allow anon and logged in users to set more than one language that they
understand, when content localization is enabled.


### User preferences

In user preferences, we now include a Content Languages section when the
site has Content Localization enabled.

### Topic menu

In topics where there are any languages that's not the user's language,
the option will appear to set their preferred content languages. This
appears for anon and logged in users, but anon users will be asked to
logged in to set the languages.
__________

These preferences apply only to topic titles and posts. They do not
affect navigational features like categories, tags, or sidebar content.
The per-post option to show the original remains independent of the
user's preferences.

This PR temporarily retains `show_original_content` as a compatibility
field computed from `automatically_translate`, keeping cached clients
and rolling deployments safe. A follow-up PR will remove the legacy API
and frontend compatibility code, then retire the old database column
through the staged column-removal process.
2026-07-30 12:19:44 +08:00

347 lines
7.2 KiB
Ruby
Vendored

# frozen_string_literal: true
class TopicViewSerializer < ApplicationSerializer
include PostStreamSerializerMixin
include SuggestedTopicsMixin
include TopicTagsMixin
include ApplicationHelper
include LocalizedFancyTopicTitleMixin
def self.attributes_from_topic(*list)
[list].flatten.each do |attribute|
attributes(attribute)
class_eval %{def #{attribute}
object.topic.#{attribute}
end}
end
end
attributes_from_topic(
:id,
:title,
:posts_count,
:created_at,
:views,
:reply_count,
:like_count,
:last_posted_at,
:visible,
:closed,
:archived,
:has_summary,
:archetype,
:slug,
:category_id,
:word_count,
:deleted_at,
:user_id,
:featured_link,
:featured_link_root_domain,
:pinned_globally,
:pinned_at,
:pinned_until,
:image_url,
:slow_mode_seconds,
:external_id,
:visibility_reason_id,
)
attributes(
:draft,
:draft_key,
:draft_sequence,
:posted,
:unpinned,
:pinned,
:current_post_number,
:highest_post_number,
:last_read_post_number,
:last_read_post_id,
:deleted_by,
:has_deleted,
:actions_summary,
:expandable_first_post,
:is_warning,
:chunk_size,
:bookmarked,
:message_archived,
:topic_timer,
:unicode_title,
:message_bus_last_id,
:participant_count,
:destination_category_id,
:pm_with_non_human_user,
:queued_posts_count,
:show_read_indicator,
:requested_group_name,
:thumbnails,
:user_last_posted_at,
:is_shared_draft,
:slow_mode_enabled_until,
:has_localized_content,
:can_localize_topic,
:is_nested_view,
)
has_one :details, serializer: TopicViewDetailsSerializer, root: false, embed: :objects
has_many :pending_posts, serializer: TopicPendingPostSerializer, root: false, embed: :objects
has_many :categories, serializer: CategoryBadgeSerializer, embed: :objects
has_many :bookmarks, serializer: TopicViewBookmarkSerializer, root: false, embed: :objects
has_one :published_page, embed: :objects
def details
object
end
def message_bus_last_id
object.message_bus_last_id
end
def chunk_size
object.chunk_size
end
def is_warning
object.personal_message && object.topic.subtype == TopicSubtype.moderator_warning
end
def include_is_warning?
is_warning
end
def include_external_id?
external_id
end
def draft
object.draft
end
def draft_key
object.draft_key
end
def draft_sequence
object.draft_sequence
end
def include_message_archived?
object.personal_message
end
def message_archived
object.topic.message_archived?(scope.user)
end
def deleted_by
BasicUserSerializer.new(object.topic.deleted_by, root: false).as_json
end
# Topic user stuff
def has_topic_user?
object.topic_user.present?
end
def current_post_number
object.current_post_number
end
def include_current_post_number?
object.current_post_number.present?
end
def highest_post_number
object.highest_post_number
end
def last_read_post_id
return nil unless last_read_post_number
object.filtered_post_id(last_read_post_number)
end
alias_method :include_last_read_post_id?, :has_topic_user?
def last_read_post_number
@last_read_post_number ||= object.topic_user.last_read_post_number
end
alias_method :include_last_read_post_number?, :has_topic_user?
def posted
object.topic_user.posted?
end
alias_method :include_posted?, :has_topic_user?
def pinned
PinnedCheck.pinned?(object.topic, object.topic_user)
end
def unpinned
PinnedCheck.unpinned?(object.topic, object.topic_user)
end
def actions_summary
object.actions_summary
end
def has_deleted
object.has_deleted?
end
def include_has_deleted?
!object.skip_post_loading && object.guardian.can_see_deleted_posts?
end
def expandable_first_post
true
end
def include_expandable_first_post?
object.topic.expandable_first_post?
end
def bookmarked
object.has_bookmarks?
end
def topic_timer
topic_timer = object.topic.public_topic_timer
return nil if topic_timer.blank?
if topic_timer.publishing_to_category?
return nil if !scope.can_see_category?(Category.find_by(id: topic_timer.category_id))
end
TopicTimerSerializer.new(object.topic.public_topic_timer, root: false)
end
def include_featured_link?
SiteSetting.topic_featured_link_enabled
end
def include_featured_link_root_domain?
SiteSetting.topic_featured_link_enabled && object.topic.featured_link
end
def include_unicode_title?
object.topic.title.match?(/:[\w\-+]+:/)
end
def unicode_title
Emoji.gsub_emoji_to_unicode(object.topic.title)
end
def include_pm_with_non_human_user?
object.personal_message
end
def pm_with_non_human_user
object.topic.pm_with_non_human_user?
end
def participant_count
object.participant_count
end
def destination_category_id
object.topic.shared_draft.category_id
end
def include_destination_category_id?
scope.can_see_shared_draft? && SiteSetting.shared_drafts_enabled? &&
object.topic.shared_draft.present?
end
def is_shared_draft
include_destination_category_id?
end
alias_method :include_is_shared_draft?, :include_destination_category_id?
def include_pending_posts?
scope.authenticated? && object.queued_posts_enabled?
end
def queued_posts_count
object.queued_posts_count
end
def include_queued_posts_count?
scope.is_staff? && object.queued_posts_enabled?
end
def show_read_indicator
object.show_read_indicator?
end
def requested_group_name
Group
.joins(:group_users)
.where(
id: object.topic.custom_fields["requested_group_id"].to_i,
group_users: {
user_id: scope.user.id,
owner: true,
},
)
.pick(:name)
end
def include_requested_group_name?
object.personal_message && object.topic.custom_fields["requested_group_id"]
end
def include_published_page?
SiteSetting.enable_page_publishing? && scope.is_staff? && object.published_page.present? &&
!SiteSetting.secure_uploads
end
def thumbnails
extra_sizes = ThemeModifierHelper.new(request: scope.request).topic_thumbnail_sizes
object.topic.thumbnail_info(enqueue_if_missing: true, extra_sizes: extra_sizes)
end
def user_last_posted_at
object.topic_user.last_posted_at
end
def include_user_last_posted_at?
has_topic_user? && object.topic.slow_mode_seconds.to_i > 0
end
def slow_mode_enabled_until
object.topic.slow_mode_topic_timer&.execute_at
end
def include_categories?
scope.can_lazy_load_categories?
end
def include_visibility_reason_id?
object.topic.visibility_reason_id.present?
end
def has_localized_content
object.has_localized_content?
end
def include_has_localized_content?
SiteSetting.content_localization_enabled
end
def can_localize_topic
true
end
def include_can_localize_topic?
SiteSetting.content_localization_enabled && scope.can_localize_topic?(object.topic)
end
def is_nested_view
true
end
def include_is_nested_view?
object.topic.nested_view?
end
end