2019-04-29 20:27:42 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-05-19 08:25:08 -04:00
|
|
|
require "mini_racer"
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2022-07-27 22:27:38 -04:00
|
|
|
RSpec.describe JsLocaleHelper do
|
2015-07-15 17:23:41 -04:00
|
|
|
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
|
2017-02-24 05:31:21 -05:00
|
|
|
|
2015-07-15 17:23:41 -04:00
|
|
|
JsLocaleHelper.extend StubLoadTranslations
|
|
|
|
|
DEV: Fix heisentest (#10946)
This should fix the following sporadic spec failure:
```
1) JsLocaleHelper performs fallbacks to English if a translation is not available
Failure/Error: expect(ctx.eval('I18n.translations.uk.js').keys).to contain_exactly("all_three", "english_and_user", "only_user", "site_and_user")
expected collection contained: ["all_three", "english_and_user", "only_user", "site_and_user"]
actual collection contained: ["about", "action_codes", "activity", "admin", "admin_title", "adplugin", "age", "akismet", "all_time..."voting", "week", "week_desc", "weekly", "wizard_required", "year", "year_desc", "yes_value", "you"]
the missing elements were: ["all_three", "english_and_user", "only_user", "site_and_user"]
the extra elements were: ["about", "action_codes", "activity", "admin", "admin_title", "adplugin", "age", "akismet", "all_time..."voting", "week", "week_desc", "weekly", "wizard_required", "year", "year_desc", "yes_value", "you"]
# ./spec/components/js_locale_helper_spec.rb:182:in `block (2 levels) in <main>'
# ./bundle/ruby/2.6.0/gems/webmock-3.9.2/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
```
2020-10-18 06:00:35 -04:00
|
|
|
before { JsLocaleHelper.clear_cache! }
|
|
|
|
after { JsLocaleHelper.clear_cache! }
|
2015-07-15 17:23:41 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
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
|
2013-05-30 01:53:40 -04:00
|
|
|
end
|
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
describe "message format" do
|
2018-01-25 06:09:18 -05:00
|
|
|
def message_format_filename(locale)
|
|
|
|
Rails.root + "lib/javascripts/locale/#{locale}.js"
|
|
|
|
end
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
def setup_message_format(format)
|
2018-01-25 06:09:18 -05:00
|
|
|
filename = message_format_filename("en")
|
|
|
|
compiled = JsLocaleHelper.compile_message_format(filename, "en", format)
|
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
@ctx = MiniRacer::Context.new
|
|
|
|
@ctx.eval("MessageFormat = {locale: {}};")
|
2018-01-25 06:09:18 -05:00
|
|
|
@ctx.load(filename)
|
2017-02-24 05:31:21 -05:00
|
|
|
@ctx.eval("var test = #{compiled}")
|
|
|
|
end
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
def localize(opts)
|
|
|
|
@ctx.eval("test(#{opts.to_json})")
|
|
|
|
end
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
it "handles plurals" do
|
|
|
|
setup_message_format(
|
|
|
|
"{NUM_RESULTS, plural,
|
|
|
|
one {1 result}
|
|
|
|
other {# results}
|
|
|
|
}",
|
|
|
|
)
|
|
|
|
expect(localize(NUM_RESULTS: 1)).to eq("1 result")
|
|
|
|
expect(localize(NUM_RESULTS: 2)).to eq("2 results")
|
|
|
|
end
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
it "handles double plurals" do
|
|
|
|
setup_message_format(
|
|
|
|
"{NUM_RESULTS, plural,
|
|
|
|
one {1 result}
|
|
|
|
other {# results}
|
|
|
|
} and {NUM_APPLES, plural,
|
|
|
|
one {1 apple}
|
|
|
|
other {# apples}
|
|
|
|
}",
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(localize(NUM_RESULTS: 1, NUM_APPLES: 2)).to eq("1 result and 2 apples")
|
|
|
|
expect(localize(NUM_RESULTS: 2, NUM_APPLES: 1)).to eq("2 results and 1 apple")
|
|
|
|
end
|
2015-07-15 17:23:41 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
it "handles select" do
|
|
|
|
setup_message_format("{GENDER, select, male {He} female {She} other {They}} read a book")
|
|
|
|
expect(localize(GENDER: "male")).to eq("He read a book")
|
|
|
|
expect(localize(GENDER: "female")).to eq("She read a book")
|
|
|
|
expect(localize(GENDER: "none")).to eq("They read a book")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "can strip out message formats" do
|
|
|
|
hash = { "a" => "b", "c" => { "d" => { "f_MF" => "bob" } } }
|
|
|
|
expect(JsLocaleHelper.strip_out_message_formats!(hash)).to eq("c.d.f_MF" => "bob")
|
|
|
|
expect(hash["c"]["d"]).to eq({})
|
|
|
|
end
|
|
|
|
|
|
|
|
it "handles message format special keys" do
|
2022-02-07 20:31:08 -05:00
|
|
|
JsLocaleHelper.set_translations(
|
|
|
|
"en",
|
|
|
|
"en" => {
|
2015-07-15 17:23:41 -04:00
|
|
|
"js" => {
|
|
|
|
"hello" => "world",
|
|
|
|
"test_MF" => "{HELLO} {COUNT, plural, one {1 duck} other {# ducks}}",
|
|
|
|
"error_MF" => "{{BLA}",
|
|
|
|
"simple_MF" => "{COUNT, plural, one {1} other {#}}",
|
2017-03-24 15:42:23 -04:00
|
|
|
},
|
|
|
|
"admin_js" => {
|
|
|
|
"foo_MF" => "{HELLO} {COUNT, plural, one {1 duck} other {# ducks}}",
|
2015-07-15 17:23:41 -04:00
|
|
|
},
|
2017-02-24 05:31:21 -05:00
|
|
|
},
|
|
|
|
)
|
2015-07-15 17:23:41 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
ctx = MiniRacer::Context.new
|
|
|
|
ctx.eval("I18n = { pluralizationRules: {} };")
|
|
|
|
ctx.eval(JsLocaleHelper.output_locale("en"))
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
expect(ctx.eval('I18n.translations["en"]["js"]["hello"]')).to eq("world")
|
|
|
|
expect(ctx.eval('I18n.translations["en"]["js"]["test_MF"]')).to eq(nil)
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2017-02-24 05:31:21 -05:00
|
|
|
expect(ctx.eval('I18n.messageFormat("test_MF", { HELLO: "hi", COUNT: 3 })')).to eq(
|
|
|
|
"hi 3 ducks",
|
|
|
|
)
|
|
|
|
expect(ctx.eval('I18n.messageFormat("error_MF", { HELLO: "hi", COUNT: 3 })')).to match(
|
|
|
|
/Invalid Format/,
|
|
|
|
)
|
|
|
|
expect(ctx.eval('I18n.messageFormat("missing", {})')).to match(/missing/)
|
|
|
|
expect(ctx.eval('I18n.messageFormat("simple_MF", {})')).to match(/COUNT/) # error
|
2017-03-24 15:42:23 -04:00
|
|
|
expect(ctx.eval('I18n.messageFormat("foo_MF", { HELLO: "hi", COUNT: 4 })')).to eq(
|
|
|
|
"hi 4 ducks",
|
|
|
|
)
|
2017-02-24 05:31:21 -05:00
|
|
|
end
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2022-02-07 20:31:08 -05:00
|
|
|
it "load pluralization rules before precompile" do
|
2018-01-25 06:09:18 -05:00
|
|
|
message = JsLocaleHelper.compile_message_format(message_format_filename("ru"), "ru", "format")
|
2017-02-24 05:31:21 -05:00
|
|
|
expect(message).not_to match "Plural Function not found"
|
|
|
|
end
|
2019-05-15 17:43:00 -04:00
|
|
|
|
2021-01-20 15:32:22 -05:00
|
|
|
it "uses message formats from fallback locale" do
|
|
|
|
translations = JsLocaleHelper.translations_for(:en_GB)
|
|
|
|
en_gb_message_formats = JsLocaleHelper.remove_message_formats!(translations, :en_GB)
|
|
|
|
expect(en_gb_message_formats).to_not be_empty
|
2019-05-15 17:43:00 -04:00
|
|
|
|
|
|
|
translations = JsLocaleHelper.translations_for(:en)
|
|
|
|
en_message_formats = JsLocaleHelper.remove_message_formats!(translations, :en)
|
2021-01-20 15:32:22 -05:00
|
|
|
expect(en_gb_message_formats).to eq(en_message_formats)
|
2019-05-15 17:43:00 -04:00
|
|
|
end
|
2013-06-15 16:50:59 -04:00
|
|
|
end
|
|
|
|
|
2020-05-06 16:57:14 -04:00
|
|
|
it "performs fallbacks to English if a translation is not available" do
|
2018-12-04 04:48:16 -05:00
|
|
|
JsLocaleHelper.set_translations(
|
|
|
|
"en",
|
|
|
|
"en" => {
|
2015-07-15 17:23:41 -04:00
|
|
|
"js" => {
|
2018-12-04 04:48:16 -05:00
|
|
|
"only_english" => "1-en",
|
|
|
|
"english_and_site" => "3-en",
|
|
|
|
"english_and_user" => "5-en",
|
|
|
|
"all_three" => "7-en",
|
2015-07-15 17:23:41 -04:00
|
|
|
},
|
2017-02-24 05:31:21 -05:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2018-12-04 04:48:16 -05:00
|
|
|
JsLocaleHelper.set_translations(
|
|
|
|
"ru",
|
|
|
|
"ru" => {
|
2015-07-15 17:23:41 -04:00
|
|
|
"js" => {
|
2018-12-04 04:48:16 -05:00
|
|
|
"only_site" => "2-ru",
|
|
|
|
"english_and_site" => "3-ru",
|
|
|
|
"site_and_user" => "6-ru",
|
|
|
|
"all_three" => "7-ru",
|
2015-07-15 17:23:41 -04:00
|
|
|
},
|
2017-02-24 05:31:21 -05:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2018-12-04 04:48:16 -05:00
|
|
|
JsLocaleHelper.set_translations(
|
|
|
|
"uk",
|
|
|
|
"uk" => {
|
2015-07-15 17:23:41 -04:00
|
|
|
"js" => {
|
2018-12-04 04:48:16 -05:00
|
|
|
"only_user" => "4-uk",
|
|
|
|
"english_and_user" => "5-uk",
|
|
|
|
"site_and_user" => "6-uk",
|
|
|
|
"all_three" => "7-uk",
|
2015-07-15 17:23:41 -04:00
|
|
|
},
|
2017-02-24 05:31:21 -05:00
|
|
|
},
|
|
|
|
)
|
2015-07-15 17:23:41 -04:00
|
|
|
|
|
|
|
expected = {
|
2018-12-04 04:48:16 -05:00
|
|
|
"none" => "[uk.js.none]",
|
|
|
|
"only_english" => "1-en",
|
2020-05-06 16:57:14 -04:00
|
|
|
"only_site" => "[uk.js.only_site]",
|
|
|
|
"english_and_site" => "3-en",
|
2018-12-04 04:48:16 -05:00
|
|
|
"only_user" => "4-uk",
|
|
|
|
"english_and_user" => "5-uk",
|
|
|
|
"site_and_user" => "6-uk",
|
2020-05-06 16:57:14 -04:00
|
|
|
"all_three" => "7-uk",
|
2015-07-15 17:23:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
SiteSetting.default_locale = "ru"
|
|
|
|
I18n.locale = :uk
|
|
|
|
|
2016-05-19 08:25:08 -04:00
|
|
|
ctx = MiniRacer::Context.new
|
2015-07-15 17:23:41 -04:00
|
|
|
ctx.eval("var window = this;")
|
|
|
|
ctx.load(Rails.root + "app/assets/javascripts/locales/i18n.js")
|
|
|
|
ctx.eval(JsLocaleHelper.output_locale(I18n.locale))
|
|
|
|
ctx.eval('I18n.defaultLocale = "ru";')
|
|
|
|
|
2020-05-06 16:57:14 -04:00
|
|
|
expect(ctx.eval("I18n.translations").keys).to contain_exactly("uk", "en")
|
2017-02-24 05:31:21 -05:00
|
|
|
expect(ctx.eval("I18n.translations.uk.js").keys).to contain_exactly(
|
|
|
|
"all_three",
|
|
|
|
"english_and_user",
|
|
|
|
"only_user",
|
|
|
|
"site_and_user",
|
2020-05-06 16:57:14 -04:00
|
|
|
)
|
|
|
|
expect(ctx.eval("I18n.translations.en.js").keys).to contain_exactly(
|
|
|
|
"only_english",
|
|
|
|
"english_and_site",
|
2023-01-09 06:18:21 -05:00
|
|
|
)
|
|
|
|
|
2015-07-15 17:23:41 -04:00
|
|
|
expected.each { |key, expect| expect(ctx.eval("I18n.t(#{"js.#{key}".inspect})")).to eq(expect) }
|
2015-07-15 13:21:06 -04:00
|
|
|
end
|
|
|
|
|
2022-02-07 20:31:08 -05:00
|
|
|
it "correctly evaluates message formats in en fallback" do
|
|
|
|
JsLocaleHelper.set_translations("en", "en" => { "js" => { "something_MF" => "en 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
|
|
|
|
|
|
|
|
ctx = MiniRacer::Context.new
|
|
|
|
ctx.eval("var window = this;")
|
|
|
|
ctx.load(Rails.root + "app/assets/javascripts/locales/i18n.js")
|
|
|
|
ctx.eval(JsLocaleHelper.output_locale("de"))
|
|
|
|
ctx.eval(JsLocaleHelper.output_client_overrides("de"))
|
|
|
|
ctx.eval(<<~JS)
|
|
|
|
for (let [key, value] of Object.entries(I18n._mfOverrides || {})) {
|
|
|
|
key = key.replace(/^[a-z_]*js\./, "");
|
|
|
|
I18n._compiledMFs[key] = value;
|
|
|
|
}
|
|
|
|
JS
|
|
|
|
|
|
|
|
expect(ctx.eval("I18n.messageFormat('something_MF', { UNREAD: 1 })")).to eq(
|
|
|
|
"There is one unread",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2013-07-24 21:09:29 -04:00
|
|
|
LocaleSiteSetting.values.each do |locale|
|
2013-08-23 17:35:01 -04:00
|
|
|
it "generates valid date helpers for #{locale[:value]} locale" do
|
|
|
|
js = JsLocaleHelper.output_locale(locale[:value])
|
2016-05-19 08:25:08 -04:00
|
|
|
ctx = MiniRacer::Context.new
|
2014-08-20 15:01:31 -04:00
|
|
|
ctx.eval("var window = this;")
|
2013-07-24 21:09:29 -04:00
|
|
|
ctx.load(Rails.root + "app/assets/javascripts/locales/i18n.js")
|
|
|
|
ctx.eval(js)
|
|
|
|
end
|
2016-02-05 15:49:03 -05:00
|
|
|
|
|
|
|
it "finds moment.js locale file for #{locale[:value]}" do
|
|
|
|
content = JsLocaleHelper.moment_locale(locale[:value])
|
|
|
|
|
2019-05-15 17:43:00 -04:00
|
|
|
if (locale[:value] == SiteSettings::DefaultsProvider::DEFAULT_LOCALE)
|
2016-02-05 15:49:03 -05:00
|
|
|
expect(content).to eq("")
|
|
|
|
else
|
|
|
|
expect(content).to_not eq("")
|
|
|
|
end
|
|
|
|
end
|
2013-07-24 21:09:29 -04:00
|
|
|
end
|
|
|
|
|
2020-01-16 08:40:53 -05:00
|
|
|
describe ".find_message_format_locale" do
|
2022-02-07 20:31:08 -05:00
|
|
|
it "finds locale's message format rules" do
|
|
|
|
locale, filename =
|
|
|
|
JsLocaleHelper.find_message_format_locale([:de], fallback_to_english: false)
|
|
|
|
expect(locale).to eq("de")
|
|
|
|
expect(filename).to end_with("/de.js")
|
|
|
|
end
|
|
|
|
|
2021-01-20 15:32:22 -05:00
|
|
|
it "finds locale for en_GB" do
|
2022-02-07 20:31:08 -05:00
|
|
|
locale, filename =
|
|
|
|
JsLocaleHelper.find_message_format_locale([:en_GB], fallback_to_english: false)
|
2020-01-16 08:40:53 -05:00
|
|
|
expect(locale).to eq("en")
|
|
|
|
expect(filename).to end_with("/en.js")
|
|
|
|
|
2022-02-07 20:31:08 -05:00
|
|
|
locale, filename =
|
|
|
|
JsLocaleHelper.find_message_format_locale(["en_GB"], fallback_to_english: false)
|
|
|
|
expect(locale).to eq("en")
|
|
|
|
expect(filename).to end_with("/en.js")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "falls back to en when locale doesn't have own message format rules" do
|
|
|
|
locale, filename =
|
|
|
|
JsLocaleHelper.find_message_format_locale([:nonexistent], fallback_to_english: true)
|
2020-01-16 08:40:53 -05:00
|
|
|
expect(locale).to eq("en")
|
|
|
|
expect(filename).to end_with("/en.js")
|
|
|
|
end
|
|
|
|
end
|
2013-05-30 01:53:40 -04:00
|
|
|
end
|