DEV: Catch missing translations during test runs (#26258)

This configuration makes it so that a missing translation will raise an error during test execution. Better discover there than after deploy.
This commit is contained in:
Ted Johansson 2024-05-24 22:15:53 +08:00 committed by GitHub
parent 9db83c37e4
commit 69205cb1e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 219 additions and 76 deletions

View File

@ -69,7 +69,7 @@ class UserEmail < ActiveRecord::Base
self.errors.add(
:user_id,
I18n.t(
"active_record.errors.model.user_email.attributes.user_id.reassigning_primary_email",
"activerecord.errors.models.user_email.attributes.user_id.reassigning_primary_email",
),
)
end

View File

@ -63,6 +63,9 @@ Discourse::Application.configure do
},
]
# Catch missing translations during test runs.
config.i18n.raise_on_missing_translations = true
config.after_initialize do
ActiveRecord::LogSubscriber.backtrace_cleaner.add_silencer do |line|
line =~ %r{lib/freedom_patches}

View File

@ -556,6 +556,7 @@ en:
private_message_abbrev: "Msg"
rss_description:
hot: "Hot topics"
latest: "Latest topics"
top: "Top topics"
top_all: "All time top topics"
@ -1472,6 +1473,7 @@ en:
description: "External sources that have linked to this site the most."
top_referred_topics:
title: "Top Referred Topics"
xaxis: ""
labels:
num_clicks: "Clicks"
topic: "Topic"

View File

@ -10,6 +10,7 @@ class MigrateOldModeratorPosts < ActiveRecord::Migration[4.2]
end
def up
Rails.application.config.i18n.raise_on_missing_translations = false
migrate_key("closed.enabled")
migrate_key("closed.disabled")
migrate_key("archived.enabled")
@ -18,5 +19,6 @@ class MigrateOldModeratorPosts < ActiveRecord::Migration[4.2]
migrate_key("pinned.disabled")
migrate_key("pinned_globally.enabled")
migrate_key("pinned_globally.disabled")
Rails.application.config.i18n.raise_on_missing_translations = true
end
end

View File

@ -36,6 +36,11 @@ class Archetype
@archetypes[name] = Archetype.new(name, options)
end
def self.deregister(name)
@archetypes ||= {}
@archetypes.delete(name)
end
# default archetypes
register "regular"
register "private_message"

View File

@ -122,7 +122,8 @@ module I18n
dup_options = nil
if options
dup_options = options.dup
should_raise = dup_options.delete(:raise)
should_raise =
dup_options.delete(:raise) || Rails.application.config.i18n.raise_on_missing_translations
locale = dup_options.delete(:locale)
end

View File

@ -3,7 +3,26 @@
describe DiscourseAutomation::AdminAutomationsController do
fab!(:automation)
before { SiteSetting.discourse_automation_enabled = true }
before do
SiteSetting.discourse_automation_enabled = true
I18n.backend.store_translations(
:en,
{
discourse_automation: {
scriptables: {
something_about_us: {
title: "Something about us.",
description: "We rock!",
},
},
triggerables: {
title: "Triggerables",
description: "Triggerables",
},
},
},
)
end
describe "#show" do
context "when logged in as an admin" do

View File

@ -41,6 +41,19 @@ describe DiscourseAutomation::AutomationSerializer do
DiscourseAutomation::Scriptable.add("foo") do
field :bar, component: :text, triggerable: DiscourseAutomation::Triggers::TOPIC
end
I18n.backend.store_translations(
:en,
{
discourse_automation: {
scriptables: {
foo: {
title: "Something about us.",
description: "We rock!",
},
},
},
},
)
end
context "when automation is not using the specific trigger" do

View File

@ -6,6 +6,34 @@ describe "DiscourseAutomation | smoke test", type: :system, js: true do
fab!(:badge) { Fabricate(:badge, name: "badge") }
before do
I18n.backend.store_translations(
:en,
{
discourse_automation: {
scriptables: {
test: {
title: "Test",
description: "Test",
},
something_about_us: {
title: "Something about us.",
description: "We rock!",
},
nothing_about_us: {
title: "Nothing about us.",
description: "We don't rock!",
},
},
triggerables: {
title: "Triggerable",
description: "Triggerable",
user_first_logged_in: {
description: "User first logged in.",
},
},
},
},
)
SiteSetting.discourse_automation_enabled = true
sign_in(admin)
end

View File

@ -23,6 +23,19 @@ describe "StalledWiki" do
before do
automation.upsert_field!("stalled_after", "choices", { value: "PT10H" }, target: "trigger")
automation.upsert_field!("retriggered_after", "choices", { value: "PT1H" }, target: "trigger")
I18n.backend.store_translations(
:en,
{
discourse_automation: {
scriptables: {
something_about_us: {
title: "Something about us.",
description: "We rock!",
},
},
},
},
)
end
it "supports manual triggering" do

View File

@ -615,7 +615,7 @@ RSpec.describe DiscourseNarrativeBot::TrackSelector do
it "should not trigger the bot" do
post.update!(
raw:
"`@discobot #{I18n.t("discourse_narrative_bot.track_selector.reset_trigger")} #{I18n.t(DiscourseNarrativeBot::NewUserNarrative.reset_trigger)}`",
"`@discobot #{I18n.t("discourse_narrative_bot.track_selector.reset_trigger")} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}`",
)
expect { described_class.new(:reply, user, post_id: post.id).select }.to_not change {
@ -750,7 +750,7 @@ RSpec.describe DiscourseNarrativeBot::TrackSelector do
user: Fabricate(:user),
topic: topic,
raw:
"@discobot #{I18n.t("discourse_narrative_bot.track_selector.reset_trigger")} #{I18n.t(DiscourseNarrativeBot::NewUserNarrative.reset_trigger)}",
"@discobot #{I18n.t("discourse_narrative_bot.track_selector.reset_trigger")} #{DiscourseNarrativeBot::NewUserNarrative.reset_trigger}",
)
user

View File

@ -2,7 +2,9 @@
RSpec.describe Jobs::CheckTranslationOverrides do
fab!(:up_to_date_translation) { Fabricate(:translation_override, translation_key: "title") }
fab!(:deprecated_translation) { Fabricate(:translation_override, translation_key: "foo.bar") }
fab!(:deprecated_translation) do
allow_missing_translations { Fabricate(:translation_override, translation_key: "foo.bar") }
end
fab!(:outdated_translation) do
Fabricate(:translation_override, translation_key: "posts", original_translation: "outdated")
end

View File

@ -34,6 +34,10 @@ RSpec.describe Jobs::BulkUserTitleUpdate do
let(:customized_badge_name) { "Merit Badge" }
before do
I18n.backend.store_translations(
:en,
{ badges: { protector_of_the_realm: { name: "Protector of the Realm" } } },
)
TranslationOverride.upsert!(I18n.locale, Badge.i18n_key(badge.name), customized_badge_name)
BadgeGranter.grant(badge, user)
user.update(title: customized_badge_name)

View File

@ -25,12 +25,13 @@ RSpec.describe Archetype do
end
end
describe "register an archetype" do
describe "register an archetype" do
it "has one more element" do
@list = Archetype.list.dup
Archetype.register("glados")
expect(Archetype.list.size).to eq(@list.size + 1)
expect(Archetype.list.find { |a| a.id == "glados" }).to be_present
Archetype.deregister("glados")
end
end
end

View File

@ -23,16 +23,20 @@ RSpec.describe "translate accelerator" do
I18n::MissingTranslationData,
)
orig = I18n.t("i_am_an_unknown_key99")
allow_missing_translations do
orig = I18n.t("i_am_an_unknown_key99")
expect(I18n.t("i_am_an_unknown_key99").object_id).to eq(orig.object_id)
expect(I18n.t("i_am_an_unknown_key99")).to eq("Translation missing: en.i_am_an_unknown_key99")
expect(I18n.t("i_am_an_unknown_key99").object_id).to eq(orig.object_id)
expect(I18n.t("i_am_an_unknown_key99")).to eq("Translation missing: en.i_am_an_unknown_key99")
end
end
it "has the same 'translation missing' message as upstream" do
expect(I18n.t("this_key_does_not_exist")).to eq(
I18n.translate_no_cache("this_key_does_not_exist"),
)
allow_missing_translations do
expect(I18n.t("this_key_does_not_exist")).to eq(
I18n.translate_no_cache("this_key_does_not_exist"),
)
end
end
it "returns the correct language" do

View File

@ -236,31 +236,32 @@ RSpec.describe JsLocaleHelper do
end
it "correctly evaluates message formats in en fallback" do
JsLocaleHelper.set_translations("en", "en" => { "js" => { "something_MF" => "en mf" } })
allow_missing_translations do
JsLocaleHelper.set_translations("en", "en" => { "js" => { "something_MF" => "en mf" } })
JsLocaleHelper.set_translations("de", "de" => { "js" => { "something_MF" => "de mf" } })
JsLocaleHelper.set_translations("de", "de" => { "js" => { "something_MF" => "de mf" } })
TranslationOverride.upsert!("en", "js.something_MF", <<~MF.strip)
There {
UNREAD, plural,
=0 {are no}
one {is one unread}
other {are # unread}
}
MF
TranslationOverride.upsert!("en", "js.something_MF", <<~MF.strip)
There {
UNREAD, plural,
=0 {are no}
one {is one unread}
other {are # unread}
}
MF
v8_ctx.eval(JsLocaleHelper.output_locale("de"))
v8_ctx.eval(JsLocaleHelper.output_client_overrides("de"))
v8_ctx.eval(<<~JS)
for (let [key, value] of Object.entries(I18n._mfOverrides || {})) {
key = key.replace(/^[a-z_]*js\./, "");
I18n._compiledMFs[key] = value;
}
JS
v8_ctx.eval(JsLocaleHelper.output_locale("de"))
v8_ctx.eval(JsLocaleHelper.output_client_overrides("de"))
v8_ctx.eval(<<~JS)
for (let [key, value] of Object.entries(I18n._mfOverrides || {})) {
key = key.replace(/^[a-z_]*js\./, "");
I18n._compiledMFs[key] = value;
}
JS
expect(v8_ctx.eval("I18n.messageFormat('something_MF', { UNREAD: 1 })")).to eq(
"There is one unread",
)
expect(v8_ctx.eval("I18n.messageFormat('something_MF', { UNREAD: 1 })")).to eq(
"There is one unread",
)
end
end
LocaleSiteSetting.values.each do |locale|

View File

@ -17,6 +17,8 @@ RSpec.describe Plugin::Instance do
some_ruby
TEXT
around { |example| allow_missing_translations(&example) }
after { DiscoursePluginRegistry.reset! }
# NOTE: sample_plugin_site_settings.yml is always loaded in tests in site_setting.rb

View File

@ -330,6 +330,8 @@ RSpec.describe SiteSettingExtension do
describe "string setting with regex" do
it "Supports custom validation errors" do
I18n.backend.store_translations(:en, { oops: "oops" })
settings.setting(:test_str, "bob", regex: "hi", regex_error: "oops")
settings.refresh!

View File

@ -282,7 +282,7 @@ RSpec.describe IncomingLinksReport do
it "returns localized titles" do
stub_empty_referred_topics_data
expect(top_referred_topics[:title]).to be_present
expect(top_referred_topics[:xaxis]).to be_present
expect(top_referred_topics[:xaxis]).to be_blank
expect(top_referred_topics[:ytitles]).to be_present
expect(top_referred_topics[:ytitles][:num_clicks]).to be_present
end

View File

@ -6,7 +6,7 @@ RSpec.describe TranslationOverride do
before do
I18n.backend.store_translations(
I18n.locale,
"user_notifications.user_did_something" => "%{first} %{second}",
{ user_notifications: { user_did_something: "%{first} %{second}" } },
)
I18n.backend.store_translations(
@ -21,7 +21,11 @@ RSpec.describe TranslationOverride do
describe "when interpolation keys are missing" do
it "should not be valid" do
translation_override =
TranslationOverride.upsert!(I18n.locale, "some_key", "%{key} %{omg}")
TranslationOverride.upsert!(
I18n.locale,
"user_notifications.user_did_something",
"%{key} %{omg}",
)
expect(translation_override.errors.full_messages).to include(
I18n.t(
@ -129,15 +133,17 @@ RSpec.describe TranslationOverride do
describe "invalid keys" do
it "does not transform 'tonz'" do
translation_override =
TranslationOverride.upsert!(I18n.locale, "something.tonz", "%{key3} %{key4} hello")
expect(translation_override.errors.full_messages).to include(
I18n.t(
"activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys",
keys: "key3, key4",
count: 2,
),
)
allow_missing_translations do
translation_override =
TranslationOverride.upsert!(I18n.locale, "something.tonz", "%{key3} %{key4} hello")
expect(translation_override.errors.full_messages).to include(
I18n.t(
"activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys",
keys: "key3, key4",
count: 2,
),
)
end
end
end
end
@ -145,6 +151,7 @@ RSpec.describe TranslationOverride do
end
it "upserts values" do
I18n.backend.store_translations(:en, { some: { key: "initial value" } })
TranslationOverride.upsert!("en", "some.key", "some value")
ovr = TranslationOverride.where(locale: "en", translation_key: "some.key").first
@ -164,6 +171,7 @@ RSpec.describe TranslationOverride do
end
it "stores js for a message format key" do
I18n.backend.store_translations(:en, { some: { key_MF: "initial value" } })
TranslationOverride.upsert!(
"ru",
"some.key_MF",
@ -300,7 +308,9 @@ RSpec.describe TranslationOverride do
end
context "when the original translation no longer exists" do
fab!(:translation) { Fabricate(:translation_override, translation_key: "foo.bar") }
fab!(:translation) do
allow_missing_translations { Fabricate(:translation_override, translation_key: "foo.bar") }
end
it { expect(translation.original_translation_deleted?).to eq(true) }
end

View File

@ -2746,7 +2746,7 @@ RSpec.describe User do
end
describe "#title=" do
fab!(:badge) { Fabricate(:badge, name: "Badge", allow_title: false) }
fab!(:badge) { Badge.find_by(name: "Welcome") }
it "sets granted_title_badge_id correctly" do
BadgeGranter.grant(badge, user)

View File

@ -204,6 +204,7 @@ RSpec.configure do |config|
config.include FastImageHelpers
config.include WithServiceHelper
config.include ServiceMatchers
config.include I18nHelpers
config.mock_framework = :mocha
config.order = "random"

View File

@ -108,33 +108,35 @@ RSpec.describe Admin::SiteTextsController do
end
it "does not return overrides for keys that do not exist in English" do
SiteSetting.default_locale = :ru
TranslationOverride.create!(
locale: :ru,
translation_key: "missing_plural_key.one",
value: "ONE",
)
TranslationOverride.create!(
locale: :ru,
translation_key: "another_missing_key",
value: "foo",
)
allow_missing_translations do
SiteSetting.default_locale = :ru
TranslationOverride.create!(
locale: :ru,
translation_key: "missing_plural_key.one",
value: "ONE",
)
TranslationOverride.create!(
locale: :ru,
translation_key: "another_missing_key",
value: "foo",
)
get "/admin/customize/site_texts.json",
params: {
q: "missing_plural_key",
locale: default_locale,
}
expect(response.status).to eq(200)
expect(response.parsed_body["site_texts"]).to be_empty
get "/admin/customize/site_texts.json",
params: {
q: "missing_plural_key",
locale: default_locale,
}
expect(response.status).to eq(200)
expect(response.parsed_body["site_texts"]).to be_empty
get "/admin/customize/site_texts.json",
params: {
q: "another_missing_key",
locale: default_locale,
}
expect(response.status).to eq(200)
expect(response.parsed_body["site_texts"]).to be_empty
get "/admin/customize/site_texts.json",
params: {
q: "another_missing_key",
locale: default_locale,
}
expect(response.status).to eq(200)
expect(response.parsed_body["site_texts"]).to be_empty
end
end
it "returns site text from fallback locale if current locale doesn't have a translation" do

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe ExtraLocalesController do
around { |example| allow_missing_translations(&example) }
describe "#show" do
it "won't work with a weird parameter" do
get "/extra-locales/-invalid..character!!"

View File

@ -3172,7 +3172,10 @@ RSpec.describe UsersController do
fab!(:badge) { Fabricate(:badge, name: "Demogorgon", allow_title: true) }
let(:user_badge) { BadgeGranter.grant(badge, user1) }
before { TranslationOverride.upsert!("en", "badges.demogorgon.name", "Boss") }
before do
I18n.backend.store_translations(:en, { badges: { demogorgon: { name: "D'Artagnan" } } })
TranslationOverride.upsert!("en", "badges.demogorgon.name", "Boss")
end
after { TranslationOverride.revert!("en", ["badges.demogorgon.name"]) }

View File

@ -3,7 +3,10 @@
require_relative "../../../script/import_scripts/base"
RSpec.describe ImportScripts::Base do
before { STDOUT.stubs(:write) }
before do
I18n.backend.store_translations(:en, { test: "Test" })
STDOUT.stubs(:write)
end
class MockSpecImporter < ImportScripts::Base
def initialize(data)

View File

@ -330,6 +330,10 @@ RSpec.describe BadgeGranter do
let(:customized_badge_name) { "Merit Badge" }
before do
I18n.backend.store_translations(
:en,
{ badges: { Badge.i18n_name(badge.name) => { name: "Badge 0" } } },
)
TranslationOverride.upsert!(I18n.locale, Badge.i18n_key(badge.name), customized_badge_name)
end
@ -381,6 +385,10 @@ RSpec.describe BadgeGranter do
it "removes custom badge titles" do
custom_badge_title = "this is a badge title"
I18n.backend.store_translations(
:en,
{ badges: { Badge.i18n_name(badge.name) => { name: "Badge 0" } } },
)
TranslationOverride.create!(
translation_key: badge.translation_key,
value: custom_badge_title,

View File

@ -3,6 +3,8 @@
RSpec.describe ProblemCheck::TranslationOverrides do
subject(:check) { described_class.new }
around { |example| allow_missing_translations(&example) }
describe ".call" do
before { Fabricate(:translation_override, status: status) }

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module I18nHelpers
def allow_missing_translations
Rails.application.config.i18n.raise_on_missing_translations = false
yield
ensure
Rails.application.config.i18n.raise_on_missing_translations = true
end
end