mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-06-19 05:35:40 +08:00
This PR changes Discourse AI translations from an opt-in category model to an opt-out model: instead of translating only selected `ai_translation_target_categories`, it introduces `ai_translation_excluded_categories`, updates the admin UI copy and save flow, changes topic/post/category candidate selection and detection jobs to translate all non-excluded categories by default, and adds a migration that converts existing target-category settings into the equivalent excluded-category list for existing sites. It also updates all related specs. --------- Co-authored-by: discourse-patch-triage[bot] <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
393 lines
12 KiB
Ruby
Vendored
393 lines
12 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
describe DiscourseAi::Admin::AiTranslationsController do
|
|
fab!(:admin)
|
|
fab!(:user)
|
|
|
|
before do
|
|
enable_current_plugin
|
|
assign_fake_provider_to(:ai_default_llm_model)
|
|
end
|
|
|
|
describe "#show" do
|
|
context "when logged in as admin" do
|
|
fab!(:excluded_category, :category)
|
|
|
|
before do
|
|
sign_in(admin)
|
|
SiteSetting.discourse_ai_enabled = true
|
|
SiteSetting.ai_translation_enabled = true
|
|
SiteSetting.content_localization_supported_locales = "en|fr|es"
|
|
SiteSetting.ai_translation_excluded_categories = excluded_category.id.to_s
|
|
end
|
|
|
|
it "returns base configuration data without progress" do
|
|
SiteSetting.ai_translation_backfill_max_age_days = 30
|
|
SiteSetting.ai_translation_backfill_hourly_rate = 100
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json["translation_id"]).to eq(DiscourseAi::Configuration::Module::TRANSLATION_ID)
|
|
expect(json["enabled"]).to eq(true)
|
|
expect(json["translation_enabled"]).to eq(true)
|
|
expect(json["hourly_rate"]).to eq(100)
|
|
expect(json["backfill_enabled"]).to be_in([true, false])
|
|
|
|
expect(json).not_to have_key("translation_progress")
|
|
expect(json).not_to have_key("total")
|
|
expect(json).not_to have_key("posts_with_detected_locale")
|
|
end
|
|
|
|
it "returns no_locales_configured when no locales are supported" do
|
|
SiteSetting.content_localization_supported_locales = ""
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json["no_locales_configured"]).to eq(true)
|
|
end
|
|
|
|
it "does not include no_locales_configured when locales are set" do
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json).not_to have_key("no_locales_configured")
|
|
end
|
|
|
|
it "returns translation_enabled field" do
|
|
SiteSetting.ai_translation_backfill_max_age_days = 30
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json["translation_enabled"]).to eq(true)
|
|
|
|
SiteSetting.ai_translation_enabled = false
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json["translation_enabled"]).to eq(false)
|
|
end
|
|
|
|
it "correctly indicates if backfill is enabled" do
|
|
SiteSetting.ai_translation_backfill_hourly_rate = 30
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["backfill_enabled"]).to eq(true)
|
|
|
|
SiteSetting.ai_translation_backfill_hourly_rate = 0
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["backfill_enabled"]).to eq(false)
|
|
end
|
|
|
|
it "returns excluded_category_ids from ai_translation_excluded_categories" do
|
|
other_category = Fabricate(:category)
|
|
SiteSetting.ai_translation_excluded_categories =
|
|
"#{excluded_category.id}|#{other_category.id}"
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["excluded_category_ids"]).to contain_exactly(
|
|
excluded_category.id,
|
|
other_category.id,
|
|
)
|
|
end
|
|
|
|
it "returns an empty array when no excluded categories are configured" do
|
|
SiteSetting.ai_translation_excluded_categories = ""
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["excluded_category_ids"]).to eq([])
|
|
end
|
|
|
|
it "correctly indicates if feature is enabled" do
|
|
SiteSetting.ai_translation_backfill_max_age_days = 30
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["enabled"]).to eq(true)
|
|
|
|
SiteSetting.ai_translation_backfill_max_age_days = 0
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["enabled"]).to eq(false)
|
|
end
|
|
end
|
|
|
|
context "when not logged in as admin" do
|
|
it "returns 404 for anonymous users" do
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
|
|
it "returns 404 for regular users" do
|
|
sign_in(user)
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
|
|
context "when plugin is disabled" do
|
|
before do
|
|
sign_in(admin)
|
|
SiteSetting.discourse_ai_enabled = false
|
|
end
|
|
|
|
it "returns 404" do
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
|
|
context "when AI translation is disabled" do
|
|
before do
|
|
sign_in(admin)
|
|
SiteSetting.discourse_ai_enabled = true
|
|
SiteSetting.ai_translation_enabled = false
|
|
end
|
|
|
|
it "still returns data but with enabled flag set to false" do
|
|
get "/admin/plugins/discourse-ai/ai-translations.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["enabled"]).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#progress" do
|
|
context "when logged in as admin" do
|
|
fab!(:target_category, :category)
|
|
|
|
before do
|
|
Discourse.cache.clear
|
|
sign_in(admin)
|
|
SiteSetting.discourse_ai_enabled = true
|
|
SiteSetting.ai_translation_enabled = true
|
|
SiteSetting.content_localization_supported_locales = "en|fr|es"
|
|
SiteSetting.ai_translation_excluded_categories = ""
|
|
end
|
|
|
|
it "returns translation progress data" do
|
|
SiteSetting.ai_translation_backfill_max_age_days = 30
|
|
SiteSetting.ai_translation_personal_messages = "group"
|
|
SiteSetting.ai_translation_backfill_hourly_rate = 100
|
|
|
|
english_posts =
|
|
Fabricate.times(
|
|
14,
|
|
:post,
|
|
locale: "en",
|
|
topic: Fabricate(:topic, category: target_category),
|
|
)
|
|
french_post =
|
|
Fabricate(:post, locale: "fr", topic: Fabricate(:topic, category: target_category))
|
|
Fabricate.times(4, :post, topic: Fabricate(:topic, category: target_category))
|
|
|
|
PostLocalization.create!(
|
|
post: french_post,
|
|
locale: "en",
|
|
raw: "Translated to English",
|
|
cooked: "<p>Translated to English</p>",
|
|
post_version: french_post.version,
|
|
localizer_user_id: admin.id,
|
|
)
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations/progress.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json["translation_progress"]).to be_an(Array)
|
|
expect(json["translation_progress"].length).to eq(3)
|
|
expect(json["total"]).to eq(19)
|
|
expect(json["posts_with_detected_locale"]).to eq(15)
|
|
|
|
locale_data = json["translation_progress"].first
|
|
expect(locale_data["locale"]).to eq("en")
|
|
expect(locale_data["total"]).to eq(1)
|
|
expect(locale_data["done"]).to eq(1)
|
|
end
|
|
|
|
it "shows only posts requiring translation for all locales (consistent behavior)" do
|
|
SiteSetting.ai_translation_backfill_max_age_days = 30
|
|
SiteSetting.ai_translation_personal_messages = "group"
|
|
SiteSetting.default_locale = "en"
|
|
|
|
english_posts =
|
|
Fabricate.times(
|
|
100,
|
|
:post,
|
|
locale: "en",
|
|
topic: Fabricate(:topic, category: target_category),
|
|
)
|
|
french_posts =
|
|
Fabricate.times(
|
|
10,
|
|
:post,
|
|
locale: "fr",
|
|
topic: Fabricate(:topic, category: target_category),
|
|
)
|
|
spanish_posts =
|
|
Fabricate.times(
|
|
5,
|
|
:post,
|
|
locale: "es",
|
|
topic: Fabricate(:topic, category: target_category),
|
|
)
|
|
|
|
french_posts
|
|
.take(8)
|
|
.each do |post|
|
|
PostLocalization.create!(
|
|
post: post,
|
|
locale: "en",
|
|
raw: "Translated to English",
|
|
cooked: "<p>Translated to English</p>",
|
|
post_version: post.version,
|
|
localizer_user_id: admin.id,
|
|
)
|
|
end
|
|
spanish_posts
|
|
.take(3)
|
|
.each do |post|
|
|
PostLocalization.create!(
|
|
post: post,
|
|
locale: "en",
|
|
raw: "Translated to English",
|
|
cooked: "<p>Translated to English</p>",
|
|
post_version: post.version,
|
|
localizer_user_id: admin.id,
|
|
)
|
|
end
|
|
|
|
english_posts
|
|
.take(50)
|
|
.each do |post|
|
|
PostLocalization.create!(
|
|
post: post,
|
|
locale: "fr",
|
|
raw: "Translated to French",
|
|
cooked: "<p>Translated to French</p>",
|
|
post_version: post.version,
|
|
localizer_user_id: admin.id,
|
|
)
|
|
end
|
|
|
|
english_posts
|
|
.drop(50)
|
|
.take(30)
|
|
.each do |post|
|
|
PostLocalization.create!(
|
|
post: post,
|
|
locale: "es",
|
|
raw: "Translated to Spanish",
|
|
cooked: "<p>Translated to Spanish</p>",
|
|
post_version: post.version,
|
|
localizer_user_id: admin.id,
|
|
)
|
|
end
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations/progress.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
progress = json["translation_progress"]
|
|
|
|
en_data = progress.find { |p| p["locale"] == "en" }
|
|
fr_data = progress.find { |p| p["locale"] == "fr" }
|
|
es_data = progress.find { |p| p["locale"] == "es" }
|
|
|
|
expect(en_data["total"]).to eq(15)
|
|
expect(en_data["done"]).to eq(11)
|
|
|
|
expect(fr_data["total"]).to eq(105)
|
|
expect(fr_data["done"]).to eq(50)
|
|
|
|
expect(es_data["total"]).to eq(110)
|
|
expect(es_data["done"]).to eq(30)
|
|
end
|
|
|
|
it "returns empty when no locales are supported" do
|
|
SiteSetting.content_localization_supported_locales = ""
|
|
|
|
get "/admin/plugins/discourse-ai/ai-translations/progress.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json["translation_progress"]).to eq([])
|
|
expect(json["total"]).to eq(0)
|
|
expect(json["posts_with_detected_locale"]).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "when not logged in as admin" do
|
|
it "returns 404 for anonymous users" do
|
|
get "/admin/plugins/discourse-ai/ai-translations/progress.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
|
|
it "returns 404 for regular users" do
|
|
sign_in(user)
|
|
get "/admin/plugins/discourse-ai/ai-translations/progress.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
|
|
context "when plugin is disabled" do
|
|
before do
|
|
sign_in(admin)
|
|
SiteSetting.discourse_ai_enabled = false
|
|
end
|
|
|
|
it "returns 404" do
|
|
get "/admin/plugins/discourse-ai/ai-translations/progress.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
|
|
context "when translation is not fully enabled" do
|
|
before do
|
|
sign_in(admin)
|
|
SiteSetting.discourse_ai_enabled = true
|
|
SiteSetting.ai_translation_enabled = false
|
|
SiteSetting.content_localization_supported_locales = "en|fr"
|
|
end
|
|
|
|
it "returns empty progress" do
|
|
get "/admin/plugins/discourse-ai/ai-translations/progress.json"
|
|
|
|
expect(response.status).to eq(200)
|
|
json = response.parsed_body
|
|
|
|
expect(json["translation_progress"]).to eq([])
|
|
expect(json["total"]).to eq(0)
|
|
expect(json["posts_with_detected_locale"]).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
end
|