FEATURE: Add locale as a filter on topic /filter (#35299)

Adds `locale` as a new filter on /filter for topics.

- locale:en -- single locale filtering
- locale:ja,es -- comma-separated OR logic
- locale:ja locale:es -- space-separated OR logic
- -locale:en -- exclusion of single locale
- -locale:en,ja -- exclusion of multiple locales
- locale:en status:closed -- combination with other filters
This commit is contained in:
Natalie Tay
2025-10-09 20:29:06 +08:00
committed by GitHub
parent 71e81ea9a2
commit c17eb25b6c
3 changed files with 125 additions and 0 deletions
+3
View File
@@ -2952,6 +2952,9 @@ en:
group: "Filter topics by groups"
groups_any: "Show topics with any of the specified groups (comma-separated)"
groups_all: "Show topics with all of the specified groups (plus-separated)"
locale: "Filter topics by original language"
locale_any: "Show topics originally written in any of the specified languages (comma-separated)"
exclude_locale: "Exclude topics originally written in a specific language"
discourse_connect:
login_error: "Login Error"
+35
View File
@@ -87,6 +87,8 @@ class TopicsFilter
filter_tag_groups(values: key_prefixes.zip(filter_values))
when "tag"
filter_tags(values: key_prefixes.zip(filter_values))
when "locale"
filter_locale(values: key_prefixes.zip(filter_values))
when "views-min"
filter_by_number_of_views(min: filter_values)
when "views-max"
@@ -343,6 +345,17 @@ class TopicsFilter
},
)
# Locale filter
results.push(
{
name: "locale:",
description: I18n.t("filter.description.locale"),
type: "text",
delimiters: [{ name: ",", description: I18n.t("filter.description.locale_any") }],
prefixes: [{ name: "-", description: I18n.t("filter.description.exclude_locale") }],
},
)
# this modifier allows custom plugins to add UI tips in the /filter route
DiscoursePluginRegistry.apply_modifier(:topics_filter_options, results, guardian)
end
@@ -902,6 +915,28 @@ class TopicsFilter
.distinct(:id)
end
def filter_locale(values:)
include_locales = []
exclude_locales = []
values.each do |key_prefix, value|
locales = value.split(",").map(&:strip).reject(&:blank?)
next if locales.empty?
if key_prefix == "-"
exclude_locales.concat(locales)
else
include_locales.concat(locales)
end
end
@scope = @scope.where(locale: include_locales) if include_locales.present?
if exclude_locales.present?
@scope = @scope.where("topics.locale IS NULL OR topics.locale NOT IN (?)", exclude_locales)
end
end
ORDER_BY_MAPPINGS = {
"activity" => {
column: "topics.bumped_at",
+87
View File
@@ -1419,6 +1419,93 @@ RSpec.describe TopicsFilter do
).to eq([])
end
end
describe "when filtering by locale" do
fab!(:en_topic) { Fabricate(:topic, locale: "en") }
fab!(:ja_topic) { Fabricate(:topic, locale: "ja") }
fab!(:es_topic) { Fabricate(:topic, locale: "es") }
fab!(:no_locale_topic, :topic)
describe "when query string is `locale:en`" do
it "should only return topics with locale en" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("locale:en")
.pluck(:id),
).to contain_exactly(en_topic.id)
end
end
describe "when query string is `locale:ja,es`" do
it "should return topics with locale ja or es" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("locale:ja,es")
.pluck(:id),
).to contain_exactly(ja_topic.id, es_topic.id)
end
end
describe "when query string is `locale:ja locale:es`" do
it "should return topics with locale ja or es" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("locale:ja locale:es")
.pluck(:id),
).to contain_exactly(ja_topic.id, es_topic.id)
end
end
describe "when query string is `-locale:en`" do
it "should return topics without locale en" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("-locale:en")
.pluck(:id),
).to contain_exactly(ja_topic.id, es_topic.id, no_locale_topic.id)
end
end
describe "when query string is `-locale:en,ja`" do
it "should return topics without locale en or ja" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("-locale:en,ja")
.pluck(:id),
).to contain_exactly(es_topic.id, no_locale_topic.id)
end
end
describe "when query string is `locale:invalid`" do
it "should return no topics" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("locale:invalid")
.pluck(:id),
).to eq([])
end
end
describe "when combining with other filters" do
before { en_topic.update!(closed: true) }
it "should work with status:closed" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("locale:en status:closed")
.pluck(:id),
).to contain_exactly(en_topic.id)
end
end
end
describe "when filtering by topic author" do
fab!(:user2) { Fabricate(:user, username: "username2") }
fab!(:topic_by_user) { Fabricate(:topic, user: user) }