mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 21:45:25 +08:00
Previously, the custom sidebar section editor and the server assumed a section's base title and link names were written in the site's current *default* locale, so changing the default locale scrambled which language each stored value mapped to and offered the wrong set of translation targets ([meta](https://meta.discourse.org/t/408327)). This change keys the editor, the same-locale de-duplication and the collision cleanup off each record's own source locale — now surfaced as an editable "Section language" — and reorganises the translation UI around languages rather than fields. ### Notable behaviour changes - Links keep their own source language, so saving a section no longer relabels a link authored in a different one. - A localization colliding with its record's source is now destroyed instead of being left behind to shadow the base string. The collision is matched **exactly**, so a regional variant like `en_GB` under an `en` source is preserved. - Localization permissions are evaluated against the visibility being *submitted*, so making a section public and translating it in one save no longer 403s and loses the edit. - `create` now enforces localization permissions, which it previously skipped entirely — an admin could write localizations onto a private section. A non-admin submitting localizations on create now gets a `403` instead of a silent drop, matching `update`. - A submitted locale is validated at the request boundary: an unsupported or over-long value returns `400` rather than an unhandled `500`. A value already stored on the record is still accepted, so AI-detected locales outside the supported list stay editable. ### UI <img width="821" height="646" alt="2026-07-29 @ 14 56 10" src="https://github.com/user-attachments/assets/19dad480-9f77-4f5b-b1a6-ed29689839a8" /> <img width="820" height="666" alt="2026-07-29 @ 14 56 16" src="https://github.com/user-attachments/assets/a9377282-d4b2-43af-91a6-700e10c50ce6" /> Translations moved from inline per-field rows to a single "Manage translations" entry point opening a language-major panel — one group per language holding the section title and every link name. The old layout rendered `1 + links × languages` rows with an add button per field, which stopped being usable at three links and two languages. A blank field means "not translated yet" rather than invalid, and clearing a saved translation now asks for confirmation before removing it.
94 lines
3.1 KiB
Ruby
Vendored
94 lines
3.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module LocalizationGuardian
|
|
# Users that pass this guard are allowed to localize all content site-wide
|
|
def can_localize_content?
|
|
return false if !SiteSetting.content_localization_enabled
|
|
return false if anonymous?
|
|
|
|
@user.in_any_groups?(SiteSetting.content_localization_allowed_groups_map)
|
|
end
|
|
|
|
def can_localize_site_settings?
|
|
SiteSetting.content_localization_enabled && is_admin?
|
|
end
|
|
|
|
def can_localize_post?(post_or_post_id)
|
|
return false if !SiteSetting.content_localization_enabled
|
|
return false if anonymous?
|
|
|
|
post = post_or_post_id.is_a?(Post) ? post_or_post_id : Post.find_by(id: post_or_post_id)
|
|
return false if !can_see_post?(post)
|
|
|
|
return true if @user.in_any_groups?(SiteSetting.content_localization_allowed_groups_map)
|
|
return false if !SiteSetting.content_localization_allow_author_localization
|
|
|
|
post.user_id == @user.id
|
|
end
|
|
|
|
def can_localize_topic?(topic_or_topic_id)
|
|
return false if !SiteSetting.content_localization_enabled
|
|
return false if anonymous?
|
|
|
|
topic =
|
|
topic_or_topic_id.is_a?(Topic) ? topic_or_topic_id : Topic.find_by(id: topic_or_topic_id)
|
|
return false if !can_see_topic?(topic)
|
|
|
|
return true if @user.in_any_groups?(SiteSetting.content_localization_allowed_groups_map)
|
|
return false if !SiteSetting.content_localization_allow_author_localization
|
|
|
|
topic.user_id == @user.id
|
|
end
|
|
|
|
def can_localize_tag?(tag_or_tag_id)
|
|
return false if !SiteSetting.content_localization_enabled
|
|
return false if anonymous?
|
|
return false if !@user.in_any_groups?(SiteSetting.content_localization_allowed_groups_map)
|
|
|
|
tag = tag_or_tag_id.is_a?(Tag) ? tag_or_tag_id : Tag.find_by(id: tag_or_tag_id)
|
|
!hidden_tag_names.include?(tag.name)
|
|
end
|
|
|
|
def can_localize_sidebar_section?(section_or_section_id)
|
|
section = find_sidebar_section(section_or_section_id)
|
|
|
|
can_localize_sidebar_section_title?(section)
|
|
end
|
|
|
|
def can_localize_sidebar_section_title?(section_or_section_id, public: nil)
|
|
return false if !SiteSetting.content_localization_enabled
|
|
return false if anonymous?
|
|
return false if !@user.admin?
|
|
|
|
section = find_sidebar_section(section_or_section_id)
|
|
return false if section.blank?
|
|
|
|
(public.nil? ? section.public? : public) && section.custom_section?
|
|
end
|
|
|
|
def can_localize_sidebar_section_link?(section_or_section_id, link_value, public: nil)
|
|
return false if !SiteSetting.content_localization_enabled
|
|
return false if anonymous?
|
|
return false if !@user.admin?
|
|
|
|
section = find_sidebar_section(section_or_section_id)
|
|
return false if section.blank?
|
|
return false if !(public.nil? ? section.public? : public)
|
|
return true if section.custom_section?
|
|
|
|
section.community_section? && !SidebarUrl.built_in_community_section_link_value?(link_value)
|
|
end
|
|
|
|
private
|
|
|
|
def find_sidebar_section(section_or_section_id)
|
|
section =
|
|
(
|
|
if section_or_section_id.is_a?(SidebarSection)
|
|
section_or_section_id
|
|
else
|
|
SidebarSection.find_by(id: section_or_section_id)
|
|
end
|
|
)
|
|
end
|
|
end
|