289 lines
8.7 KiB
Ruby
289 lines
8.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "mini_racer"
|
|
|
|
RSpec.describe JsLocaleHelper do
|
|
let(:v8_ctx) do
|
|
discourse_node_modules = "#{Rails.root}/app/assets/javascripts/discourse/node_modules"
|
|
mf_runtime = "#{discourse_node_modules}/@messageformat/runtime"
|
|
transpiler = DiscourseJsProcessor::Transpiler.new
|
|
ctx = MiniRacer::Context.new
|
|
ctx.load("#{discourse_node_modules}/loader.js/dist/loader/loader.js")
|
|
ctx.eval("var window = globalThis;")
|
|
{
|
|
"@messageformat/runtime/messages": "#{mf_runtime}/esm/messages.js",
|
|
"@messageformat/runtime": "#{mf_runtime}/esm/runtime.js",
|
|
"@messageformat/runtime/lib/cardinals": "#{mf_runtime}/esm/cardinals.js",
|
|
"make-plural/cardinals": "#{discourse_node_modules}/make-plural/cardinals.mjs",
|
|
"discourse-i18n": "#{Rails.root}/app/assets/javascripts/discourse-i18n/src/index.js",
|
|
}.each do |module_name, path|
|
|
ctx.eval(transpiler.perform(File.read(path), "", module_name.to_s))
|
|
end
|
|
ctx.eval <<~JS
|
|
define("discourse/loader-shims", () => {})
|
|
JS
|
|
# As there are circular references in the return value, this raises an
|
|
# error if we let MiniRacer try to convert the value to JSON. Forcing
|
|
# returning `null` from `#eval` will prevent that.
|
|
ctx.eval("#{File.read("#{Rails.root}/app/assets/javascripts/locales/i18n.js")};null")
|
|
ctx
|
|
end
|
|
|
|
module StubLoadTranslations
|
|
def set_translations(locale, translations)
|
|
@loaded_translations ||= HashWithIndifferentAccess.new
|
|
@loaded_translations[locale] = translations
|
|
end
|
|
|
|
def clear_cache!
|
|
@loaded_translations = nil
|
|
@loaded_merges = nil
|
|
end
|
|
end
|
|
|
|
JsLocaleHelper.extend StubLoadTranslations
|
|
|
|
before { JsLocaleHelper.clear_cache! }
|
|
after { JsLocaleHelper.clear_cache! }
|
|
|
|
describe "#output_locale" do
|
|
it "doesn't change the cached translations hash" do
|
|
I18n.locale = :fr
|
|
expect(JsLocaleHelper.output_locale("fr").length).to be > 0
|
|
expect(JsLocaleHelper.translations_for("fr")["fr"].keys).to contain_exactly(
|
|
"js",
|
|
"admin_js",
|
|
"wizard_js",
|
|
)
|
|
end
|
|
end
|
|
|
|
it "performs fallbacks to English if a translation is not available" do
|
|
JsLocaleHelper.set_translations(
|
|
"en",
|
|
"en" => {
|
|
"js" => {
|
|
"only_english" => "1-en",
|
|
"english_and_site" => "3-en",
|
|
"english_and_user" => "5-en",
|
|
"all_three" => "7-en",
|
|
},
|
|
},
|
|
)
|
|
|
|
JsLocaleHelper.set_translations(
|
|
"ru",
|
|
"ru" => {
|
|
"js" => {
|
|
"only_site" => "2-ru",
|
|
"english_and_site" => "3-ru",
|
|
"site_and_user" => "6-ru",
|
|
"all_three" => "7-ru",
|
|
},
|
|
},
|
|
)
|
|
|
|
JsLocaleHelper.set_translations(
|
|
"uk",
|
|
"uk" => {
|
|
"js" => {
|
|
"only_user" => "4-uk",
|
|
"english_and_user" => "5-uk",
|
|
"site_and_user" => "6-uk",
|
|
"all_three" => "7-uk",
|
|
},
|
|
},
|
|
)
|
|
|
|
expected = {
|
|
"none" => "[uk.js.none]",
|
|
"only_english" => "1-en",
|
|
"only_site" => "[uk.js.only_site]",
|
|
"english_and_site" => "3-en",
|
|
"only_user" => "4-uk",
|
|
"english_and_user" => "5-uk",
|
|
"site_and_user" => "6-uk",
|
|
"all_three" => "7-uk",
|
|
}
|
|
|
|
SiteSetting.default_locale = "ru"
|
|
I18n.locale = :uk
|
|
|
|
v8_ctx.eval(JsLocaleHelper.output_locale(I18n.locale))
|
|
v8_ctx.eval('I18n.defaultLocale = "ru";')
|
|
|
|
expect(v8_ctx.eval("I18n.translations").keys).to contain_exactly("uk", "en")
|
|
expect(v8_ctx.eval("I18n.translations.uk.js").keys).to contain_exactly(
|
|
"all_three",
|
|
"english_and_user",
|
|
"only_user",
|
|
"site_and_user",
|
|
)
|
|
expect(v8_ctx.eval("I18n.translations.en.js").keys).to contain_exactly(
|
|
"only_english",
|
|
"english_and_site",
|
|
)
|
|
|
|
expected.each do |key, expect|
|
|
expect(v8_ctx.eval("I18n.t(#{"js.#{key}".inspect})")).to eq(expect)
|
|
end
|
|
end
|
|
|
|
LocaleSiteSetting.values.each do |locale|
|
|
it "generates valid date helpers for #{locale[:value]} locale" do
|
|
js = JsLocaleHelper.output_locale(locale[:value])
|
|
v8_ctx.eval(js)
|
|
end
|
|
|
|
it "finds moment.js locale file for #{locale[:value]}" do
|
|
content = JsLocaleHelper.moment_locale(locale[:value])
|
|
|
|
if (locale[:value] == SiteSettings::DefaultsProvider::DEFAULT_LOCALE)
|
|
expect(content).to eq("")
|
|
else
|
|
expect(content).to_not eq("")
|
|
end
|
|
end
|
|
|
|
it "generates valid MF locales for the '#{locale[:value]}' locale" do
|
|
expect(described_class.output_MF(locale[:value])).not_to match(/Failed to compile/)
|
|
end
|
|
end
|
|
|
|
describe ".output_MF" do
|
|
fab!(:overriden_translation_en) do
|
|
Fabricate(
|
|
:translation_override,
|
|
translation_key: "admin_js.admin.user.penalty_history_MF",
|
|
value: "OVERRIDEN",
|
|
)
|
|
end
|
|
fab!(:overriden_translation_ja) do
|
|
Fabricate(:translation_override, locale: "ja", translation_key: "js.posts_likes_MF")
|
|
end
|
|
fab!(:overriden_translation_he) do
|
|
Fabricate(:translation_override, locale: "he", translation_key: "js.posts_likes_MF")
|
|
end
|
|
let(:output) { described_class.output_MF(locale).gsub(/^import.*$/, "") }
|
|
let(:generated_locales) { v8_ctx.eval("Object.keys(I18n._mfMessages._data)") }
|
|
let(:translated_message) do
|
|
v8_ctx.eval("I18n._mfMessages.get('posts_likes_MF', {count: 3, ratio: 'med'})")
|
|
end
|
|
|
|
before do
|
|
overriden_translation_ja.update_columns(
|
|
value: "{ count, plural, one {返信 # 件、} other {返信 # 件、} }",
|
|
)
|
|
overriden_translation_he.update_columns(value: "{ count, plural, ")
|
|
v8_ctx.eval(output)
|
|
end
|
|
|
|
context "when locale is 'en'" do
|
|
let(:locale) { "en" }
|
|
|
|
it "generates messages for the 'en' locale only" do
|
|
expect(generated_locales).to eq %w[en]
|
|
end
|
|
|
|
it "translates messages properly" do
|
|
expect(
|
|
translated_message,
|
|
).to eq "3 replies, very high like to post ratio, jump to the first or last post…\n"
|
|
end
|
|
|
|
context "when the translation is overriden" do
|
|
let(:translated_message) do
|
|
v8_ctx.eval(
|
|
"I18n._mfMessages.get('admin.user.penalty_history_MF', { SUSPENDED: 3, SILENCED: 2 })",
|
|
)
|
|
end
|
|
|
|
it "returns the overriden translation" do
|
|
expect(translated_message).to eq "OVERRIDEN"
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when locale is not 'en'" do
|
|
let(:locale) { "fr" }
|
|
|
|
it "generates messages for the current locale and uses 'en' as fallback" do
|
|
expect(generated_locales).to match(%w[fr en])
|
|
end
|
|
|
|
it "translates messages properly" do
|
|
expect(
|
|
translated_message,
|
|
).to eq "3 réponses, avec un taux très élevé de « J'aime » par publication, accéder à la première ou dernière publication...\n"
|
|
end
|
|
|
|
context "when a translation is missing" do
|
|
before { v8_ctx.eval("delete I18n._mfMessages._data.fr.posts_likes_MF") }
|
|
|
|
it "returns the fallback translation" do
|
|
expect(
|
|
translated_message,
|
|
).to eq "3 replies, very high like to post ratio, jump to the first or last post…\n"
|
|
end
|
|
|
|
context "when the fallback translation is overriden" do
|
|
let(:translated_message) do
|
|
v8_ctx.eval(
|
|
"I18n._mfMessages.get('admin.user.penalty_history_MF', { SUSPENDED: 3, SILENCED: 2 })",
|
|
)
|
|
end
|
|
|
|
before do
|
|
v8_ctx.eval("delete I18n._mfMessages._data.fr['admin.user.penalty_history_MF']")
|
|
end
|
|
|
|
it "returns the overriden fallback translation" do
|
|
expect(translated_message).to eq "OVERRIDEN"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when locale contains invalid plural keys" do
|
|
let(:locale) { "ja" }
|
|
|
|
it "does not raise an error" do
|
|
expect(generated_locales).to match(%w[ja en])
|
|
end
|
|
end
|
|
|
|
context "when locale contains malformed messages" do
|
|
let(:locale) { "he" }
|
|
|
|
it "raises an error" do
|
|
expect(output).to match(/Failed to compile message formats/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".output_client_overrides" do
|
|
subject(:client_overrides) { described_class.output_client_overrides("en") }
|
|
|
|
before do
|
|
Fabricate(
|
|
:translation_override,
|
|
locale: "en",
|
|
translation_key: "js.user.preferences.title",
|
|
value: "SHOULD_SHOW",
|
|
)
|
|
Fabricate(
|
|
:translation_override,
|
|
locale: "en",
|
|
translation_key: "js.user.preferences",
|
|
value: "SHOULD_NOT_SHOW",
|
|
status: "deprecated",
|
|
)
|
|
end
|
|
|
|
it "does not output deprecated translation overrides" do
|
|
expect(client_overrides).to include("SHOULD_SHOW")
|
|
expect(client_overrides).not_to include("SHOULD_NOT_SHOW")
|
|
end
|
|
end
|
|
end
|