FEATURE: Allow plugins to register a new locale

This commit is contained in:
Gerhard Schlager 2018-01-25 12:09:18 +01:00
parent ba6cd83e3a
commit eb52c5469e
29 changed files with 480 additions and 71 deletions

View File

@ -1,7 +1,5 @@
app/assets/javascripts/env.js
app/assets/javascripts/main_include.js
app/assets/javascripts/main_include_admin.js
app/assets/javascripts/pagedown_custom.js
app/assets/javascripts/vendor.js
app/assets/javascripts/locales/i18n.js
app/assets/javascripts/ember-addons/
@ -11,11 +9,9 @@ lib/javascripts/messageformat.js
lib/javascripts/moment.js
lib/javascripts/moment_locale/
lib/highlight_js/
plugins/**/lib/javascripts/locale
public/javascripts/
spec/phantom_js/smoke_test.js
vendor/
test/javascripts/test_helper.js
test/javascripts/test_helper.js
test/javascripts/fixtures
test/javascripts/helpers/assertions.js
app/assets/javascripts/ember-addons/

View File

@ -15,6 +15,7 @@ I18n.pluralizationRules = {
// Set current locale to null
I18n.locale = null;
I18n.fallbackLocale = null;
// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
@ -143,6 +144,10 @@ I18n.translate = function(scope, options) {
var translation = this.lookup(scope, options);
if (!this.noFallbacks) {
if (!translation && this.fallbackLocale) {
options.locale = this.fallbackLocale;
translation = this.lookup(scope, options);
}
if (!translation && this.currentLocale() !== this.defaultLocale) {
options.locale = this.defaultLocale;
translation = this.lookup(scope, options);

View File

@ -7,9 +7,12 @@ class LocaleSiteSetting < EnumSiteSetting
end
def self.values
supported_locales.map do |l|
lang = language_names[l] || language_names[l[0..1]]
{ name: lang ? lang['nativeName'] : l, value: l }
@values ||= supported_locales.map do |locale|
lang = language_names[locale] || language_names[locale.split("_")[0]]
{
name: lang ? lang['nativeName'] : locale,
value: locale
}
end
end
@ -19,43 +22,41 @@ class LocaleSiteSetting < EnumSiteSetting
return @language_names if @language_names
@lock.synchronize do
@language_names ||= YAML.load(File.read(File.join(Rails.root, 'config', 'locales', 'names.yml')))
@language_names ||= begin
names = YAML.load(File.read(File.join(Rails.root, 'config', 'locales', 'names.yml')))
DiscoursePluginRegistry.locales.each do |locale, options|
if !names.key?(locale) && options[:name] && options[:nativeName]
names[locale] = { "name" => options[:name], "nativeName" => options[:nativeName] }
end
end
names
end
end
end
def self.supported_locales
@lock.synchronize do
@supported_locales ||= begin
app_client_files = Dir.glob(
locales = Dir.glob(
File.join(Rails.root, 'config', 'locales', 'client.*.yml')
)
).map { |x| x.split('.')[-2] }
unless ignore_plugins?
app_client_files += Dir.glob(
File.join(Rails.root, 'plugins', '*', 'config', 'locales', 'client.*.yml')
)
end
app_client_files.map { |x| x.split('.')[-2] }
.uniq
.select { |locale| valid_locale?(locale) }
.sort
locales += DiscoursePluginRegistry.locales.keys
locales.uniq.sort
end
end
end
def self.valid_locale?(locale)
assets = Rails.configuration.assets
assets.precompile.grep(/locales\/#{locale}(?:\.js)?/).present? &&
(Dir.glob(File.join(Rails.root, 'app', 'assets', 'javascripts', 'locales', "#{locale}.js.erb")).present? ||
Dir.glob(File.join(Rails.root, 'plugins', '*', 'assets', 'locales', "#{locale}.js.erb")).present?)
def self.reset!
@lock.synchronize do
@values = @language_names = @supported_locales = nil
end
end
def self.ignore_plugins?
Rails.env.test? && ENV['LOAD_PLUGINS'] != "1"
def self.fallback_locale(locale)
plugin_locale = DiscoursePluginRegistry.locales[locale.to_s]
plugin_locale ? plugin_locale[:fallbackLocale]&.to_sym : nil
end
private_class_method :valid_locale?
private_class_method :ignore_plugins?
end

View File

@ -21,7 +21,8 @@ class TranslationOverride < ActiveRecord::Base
data = { value: value }
if key.end_with?('_MF')
data[:compiled_js] = JsLocaleHelper.compile_message_format(locale, value)
_, filename = JsLocaleHelper.find_message_format_locale(['en'], false)
data[:compiled_js] = JsLocaleHelper.compile_message_format(filename, locale, value)
end
translation_override = find_or_initialize_by(params)

View File

@ -119,9 +119,6 @@ eo:
es:
name: Spanish
nativeName: Español
es_MX:
name: Spanish
nativeName: Español (MX)
et:
name: Estonian
nativeName: eesti

View File

@ -14,6 +14,7 @@ class DiscoursePluginRegistry
attr_writer :handlebars
attr_writer :serialized_current_user_fields
attr_writer :seed_data
attr_writer :locales
attr_accessor :custom_html
def plugins
@ -65,6 +66,10 @@ class DiscoursePluginRegistry
@seed_data ||= HashWithIndifferentAccess.new({})
end
def locales
@locales ||= HashWithIndifferentAccess.new({})
end
def html_builders
@html_builders ||= {}
end
@ -92,6 +97,10 @@ class DiscoursePluginRegistry
self.class.stylesheets << filename
end
def self.register_locale(locale, options = {})
self.locales[locale] = options
end
def register_archetype(name, options = {})
Archetype.register(name, options)
end
@ -171,6 +180,10 @@ class DiscoursePluginRegistry
result.uniq
end
def locales
self.class.locales
end
def javascripts
self.class.javascripts
end
@ -207,6 +220,7 @@ class DiscoursePluginRegistry
self.desktop_stylesheets = nil
self.sass_variables = nil
self.handlebars = nil
self.locales = nil
end
def self.reset!
@ -222,6 +236,7 @@ class DiscoursePluginRegistry
html_builders.clear
vendored_pretty_text.clear
seed_path_builders.clear
locales.clear
end
def self.setup(plugin_class)

View File

@ -39,6 +39,13 @@ module I18n
if @loaded_locales.empty?
# load all rb files
I18n.backend.load_translations(I18n.load_path.grep(/\.rb$/))
# load plural rules from plugins
DiscoursePluginRegistry.locales.each do |locale, options|
if options[:plural]
I18n.backend.store_translations(locale, i18n: { plural: options[:plural] })
end
end
end
# load it

View File

@ -3,7 +3,8 @@ module I18n
# Configure custom fallback order
class FallbackLocaleList < Hash
def [](locale)
[locale, SiteSetting.default_locale.to_sym, :en].uniq.compact
fallback_locale = LocaleSiteSetting.fallback_locale(locale)
[locale, fallback_locale, SiteSetting.default_locale.to_sym, :en].uniq.compact
end
end
end

View File

@ -85,6 +85,7 @@ module JsLocaleHelper
end
def self.load_translations_merged(*locales)
locales = locales.compact
@loaded_merges ||= {}
@loaded_merges[locales.join('-')] ||= begin
all_translations = {}
@ -104,6 +105,7 @@ module JsLocaleHelper
current_locale = I18n.locale
locale_sym = locale_str.to_sym
site_locale = SiteSetting.default_locale.to_sym
fallback_locale = LocaleSiteSetting.fallback_locale(locale_str)
I18n.locale = locale_sym
@ -113,9 +115,9 @@ module JsLocaleHelper
elsif locale_sym == :en
load_translations(locale_sym)
elsif locale_sym == site_locale || site_locale == :en
load_translations_merged(locale_sym, :en)
load_translations_merged(locale_sym, fallback_locale, :en)
else
load_translations_merged(locale_sym, site_locale, :en)
load_translations_merged(locale_sym, fallback_locale, site_locale, :en)
end
I18n.locale = current_locale
@ -125,11 +127,13 @@ module JsLocaleHelper
def self.output_locale(locale)
locale_str = locale.to_s
fallback_locale_str = LocaleSiteSetting.fallback_locale(locale_str)&.to_s
translations = Marshal.load(Marshal.dump(translations_for(locale_str)))
message_formats = strip_out_message_formats!(translations[locale_str]['js'])
message_formats.merge!(strip_out_message_formats!(translations[locale_str]['admin_js']))
result = generate_message_format(message_formats, locale_str)
mf_locale, mf_filename = find_message_format_locale([locale_str], true)
result = generate_message_format(message_formats, mf_locale, mf_filename)
translations.keys.each do |l|
translations[l].keys.each do |k|
@ -140,7 +144,8 @@ module JsLocaleHelper
# I18n
result << "I18n.translations = #{translations.to_json};\n"
result << "I18n.locale = '#{locale_str}';\n"
result << "I18n.pluralizationRules.#{locale_str} = MessageFormat.locale.#{locale_str};\n" if locale_str != "en"
result << "I18n.fallbackLocale = '#{fallback_locale_str}';\n" if fallback_locale_str && fallback_locale_str != "en"
result << "I18n.pluralizationRules.#{locale_str} = MessageFormat.locale.#{mf_locale};\n" if mf_locale != "en"
# moment
result << File.read("#{Rails.root}/lib/javascripts/moment.js")
@ -150,6 +155,41 @@ module JsLocaleHelper
result
end
def self.find_moment_locale(locale_chain)
path = "#{Rails.root}/lib/javascripts/moment_locale"
# moment.js uses a different naming scheme for locale files
locale_chain = locale_chain.map { |l| l.tr('_', '-').downcase }
find_locale(locale_chain, path, :moment_js, false)
end
def self.find_message_format_locale(locale_chain, fallback_to_english)
path = "#{Rails.root}/lib/javascripts/locale"
find_locale(locale_chain, path, :message_format, fallback_to_english)
end
def self.find_locale(locale_chain, path, type, fallback_to_english)
locale_chain.each do |locale|
plugin_locale = DiscoursePluginRegistry.locales[locale]
return plugin_locale[type] if plugin_locale&.has_key?(type)
filename = File.join(path, "#{locale}.js")
return [locale, filename] if File.exist?(filename)
end
# try again, but this time only with the language itself
locale_chain = locale_chain.map { |l| l.split(/[-_]/)[0] }
.uniq.reject { |l| locale_chain.include?(l) }
unless locale_chain.empty?
locale_data = find_locale(locale_chain, path, type, false)
return locale_data if locale_data
end
# English should alyways work
["en", File.join(path, "en.js")] if fallback_to_english
end
def self.moment_formats
result = ""
result << moment_format_function('short_date_no_year')
@ -163,23 +203,13 @@ module JsLocaleHelper
"moment.fn.#{name.camelize(:lower)} = function(){ return this.format('#{format}'); };\n"
end
def self.moment_locale(locale_str)
# moment.js uses a different naming scheme for locale files
locale_str = locale_str.tr('_', '-').downcase
filename = "#{Rails.root}/lib/javascripts/moment_locale/#{locale_str}.js"
# try the language without the territory
locale_str = locale_str.split("-")[0]
filename = "#{Rails.root}/lib/javascripts/moment_locale/#{locale_str}.js" unless File.exists?(filename)
File.exists?(filename) ? File.read(filename) << "\n" : ""
def self.moment_locale(locale)
_, filename = find_moment_locale([locale])
filename && File.exist?(filename) ? File.read(filename) << "\n" : ""
end
def self.generate_message_format(message_formats, locale_str)
formats = message_formats.map { |k, v| k.inspect << " : " << compile_message_format(locale_str, v) }.join(", ")
filename = "#{Rails.root}/lib/javascripts/locale/#{locale_str}.js"
filename = "#{Rails.root}/lib/javascripts/locale/en.js" unless File.exists?(filename)
def self.generate_message_format(message_formats, locale, filename)
formats = message_formats.map { |k, v| k.inspect << " : " << compile_message_format(filename, locale, v) }.join(", ")
result = "MessageFormat = {locale: {}};\n"
result << "I18n._compiledMFs = {#{formats}};\n"
@ -203,10 +233,9 @@ module JsLocaleHelper
end
end
def self.compile_message_format(locale, format)
def self.compile_message_format(path, locale, format)
with_context do |ctx|
path = "#{Rails.root}/lib/javascripts/locale/#{locale}.js"
ctx.load(path) if File.exists?(path)
ctx.load(path) if File.exist?(path)
ctx.eval("mf = new MessageFormat('#{locale}');")
ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
end

View File

@ -29,6 +29,7 @@ class Plugin::Instance
:color_schemes,
:initializers,
:javascripts,
:locales,
:service_workers,
:styles,
:themes].each do |att|
@ -319,6 +320,14 @@ class Plugin::Instance
javascripts << js
end
# @option opts [String] :name
# @option opts [String] :nativeName
# @option opts [String] :fallbackLocale
# @option opts [Hash] :plural
def register_locale(locale, opts = {})
locales << [locale, opts]
end
def register_custom_html(hash)
DiscoursePluginRegistry.custom_html ||= {}
DiscoursePluginRegistry.custom_html.merge!(hash)
@ -427,7 +436,7 @@ JS
end
register_assets! unless assets.blank?
register_locales!
register_service_workers!
seed_data.each do |key, value|
@ -532,6 +541,33 @@ JS
end
end
def register_locales!
root_path = File.dirname(@path)
locales.each do |locale, opts|
opts = opts.dup
opts[:client_locale_file] = File.join(root_path, "config/locales/client.#{locale}.yml")
opts[:server_locale_file] = File.join(root_path, "config/locales/server.#{locale}.yml")
opts[:js_locale_file] = File.join(root_path, "assets/locales/#{locale}.js.erb")
locale_chain = opts[:fallbackLocale] ? [locale, opts[:fallbackLocale]] : [locale]
lib_locale_path = File.join(root_path, "lib/javascripts/locale")
path = File.join(lib_locale_path, "message_format")
opts[:message_format] = find_locale_file(locale_chain, path)
opts[:message_format] = JsLocaleHelper.find_message_format_locale(locale_chain, false) unless opts[:message_format]
path = File.join(lib_locale_path, "moment_js")
opts[:moment_js] = find_locale_file(locale_chain, path)
opts[:moment_js] = JsLocaleHelper.find_moment_locale(locale_chain) unless opts[:moment_js]
if valid_locale?(opts)
DiscoursePluginRegistry.register_locale(locale, opts)
Rails.configuration.assets.precompile << "locales/#{locale}.js"
end
end
end
private
def write_asset(path, contents)
@ -553,4 +589,18 @@ JS
yield plugin
end
def valid_locale?(custom_locale)
File.exist?(custom_locale[:client_locale_file]) &&
File.exist?(custom_locale[:server_locale_file]) &&
File.exist?(custom_locale[:js_locale_file]) &&
custom_locale[:message_format] && custom_locale[:moment_js]
end
def find_locale_file(locale_chain, path)
locale_chain.each do |locale|
filename = File.join(path, "#{locale}.js")
return [locale, filename] if File.exist?(filename)
end
nil
end
end

View File

@ -0,0 +1,48 @@
require 'rails_helper'
require 'i18n/backend/fallback_locale_list'
describe I18n::Backend::FallbackLocaleList do
let(:list) { I18n::Backend::FallbackLocaleList.new }
it "works when default_locale is English" do
SiteSetting.default_locale = :en
expect(list[:ru]).to eq([:ru, :en])
expect(list[:en]).to eq([:en])
end
it "works when default_locale is not English" do
SiteSetting.default_locale = :de
expect(list[:ru]).to eq([:ru, :de, :en])
expect(list[:de]).to eq([:de, :en])
expect(list[:en]).to eq([:en, :de])
end
context "when plugin registered fallback locale" do
before do
DiscoursePluginRegistry.register_locale("es_MX", fallbackLocale: "es")
DiscoursePluginRegistry.register_locale("de_AT", fallbackLocale: "de")
end
after do
DiscoursePluginRegistry.reset!
end
it "works when default_locale is English" do
SiteSetting.default_locale = :en
expect(list[:de_AT]).to eq([:de_AT, :de, :en])
expect(list[:de]).to eq([:de, :en])
expect(list[:en]).to eq([:en])
end
it "works when default_locale is not English" do
SiteSetting.default_locale = :de
expect(list[:es_MX]).to eq([:es_MX, :es, :de, :en])
expect(list[:es]).to eq([:es, :de, :en])
expect(list[:en]).to eq([:en, :de])
end
end
end

View File

@ -1,6 +1,9 @@
require "rails_helper"
describe "translate accelerator" do
after do
I18n.reload!
end
it "overrides for both string and symbol keys" do
key = "user.email.not_allowed"
@ -32,4 +35,39 @@ describe "translate accelerator" do
end
end
context "plugins" do
before do
DiscoursePluginRegistry.register_locale(
"foo",
name: "Foo",
nativeName: "Foo Bar",
plural: {
keys: [:one, :few, :other],
rule: lambda do |n|
return :one if n == 1
return :few if n < 10
:other
end
}
)
LocaleSiteSetting.reset!
I18n.reload!
end
after do
DiscoursePluginRegistry.reset!
LocaleSiteSetting.reset!
end
it "loads plural rules from plugins" do
I18n.backend.store_translations(:foo, items: { one: 'one item', few: 'some items', other: "%{count} items" })
I18n.locale = :foo
expect(I18n.t('i18n.plural.keys')).to eq([:one, :few, :other])
expect(I18n.t('items', count: 1)).to eq('one item')
expect(I18n.t('items', count: 3)).to eq('some items')
expect(I18n.t('items', count: 20)).to eq('20 items')
end
end
end

View File

@ -34,12 +34,17 @@ describe JsLocaleHelper do
end
context "message format" do
def message_format_filename(locale)
Rails.root + "lib/javascripts/locale/#{locale}.js"
end
def setup_message_format(format)
filename = message_format_filename('en')
compiled = JsLocaleHelper.compile_message_format(filename, 'en', format)
@ctx = MiniRacer::Context.new
@ctx.eval('MessageFormat = {locale: {}};')
@ctx.load(Rails.root + 'lib/javascripts/locale/en.js')
compiled = JsLocaleHelper.compile_message_format('en', format)
@ctx.load(filename)
@ctx.eval("var test = #{compiled}")
end
@ -110,7 +115,7 @@ describe JsLocaleHelper do
end
it 'load pluralizations rules before precompile' do
message = JsLocaleHelper.compile_message_format('ru', 'format')
message = JsLocaleHelper.compile_message_format(message_format_filename('ru'), 'ru', 'format')
expect(message).not_to match 'Plural Function not found'
end
end

View File

@ -10,8 +10,8 @@ describe Plugin::Instance do
context "find_all" do
it "can find plugins correctly" do
plugins = Plugin::Instance.find_all("#{Rails.root}/spec/fixtures/plugins")
expect(plugins.count).to eq(1)
plugin = plugins[0]
expect(plugins.count).to eq(2)
plugin = plugins[1]
expect(plugin.name).to eq("plugin-name")
expect(plugin.path).to eq("#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb")
@ -268,4 +268,108 @@ describe Plugin::Instance do
expect(called).to eq(1)
end
end
context "locales" do
let(:plugin_path) { "#{Rails.root}/spec/fixtures/plugins/custom_locales" }
let!(:plugin) { Plugin::Instance.new(nil, "#{plugin_path}/plugin.rb") }
let(:plural) do
{
keys: [:one, :few, :other],
rule: lambda do |n|
return :one if n == 1
return :few if n < 10
:other
end
}
end
def register_locale(locale, opts)
plugin.register_locale(locale, opts)
plugin.activate!
DiscoursePluginRegistry.locales[locale]
end
it "enables the registered locales only on activate" do
plugin.register_locale("foo", name: "Foo", nativeName: "Foo Bar", plural: plural)
plugin.register_locale("es_MX", name: "Spanish (Mexico)", nativeName: "Español (México)", fallbackLocale: "es")
expect(DiscoursePluginRegistry.locales.count).to eq(0)
plugin.activate!
expect(DiscoursePluginRegistry.locales.count).to eq(2)
end
it "allows finding the locale by string and symbol" do
register_locale("foo", name: "Foo", nativeName: "Foo Bar", plural: plural)
expect(DiscoursePluginRegistry.locales).to have_key(:foo)
expect(DiscoursePluginRegistry.locales).to have_key('foo')
end
it "correctly registers a new locale" do
locale = register_locale("foo", name: "Foo", nativeName: "Foo Bar", plural: plural)
expect(DiscoursePluginRegistry.locales.count).to eq(1)
expect(DiscoursePluginRegistry.locales).to have_key(:foo)
expect(locale[:fallbackLocale]).to be_nil
expect(locale[:message_format]).to eq(["foo", "#{plugin_path}/lib/javascripts/locale/message_format/foo.js"])
expect(locale[:moment_js]).to eq(["foo", "#{plugin_path}/lib/javascripts/locale/moment_js/foo.js"])
expect(locale[:plural]).to eq(plural.with_indifferent_access)
expect(Rails.configuration.assets.precompile).to include("locales/foo.js")
end
it "correctly registers a new locale using a fallback locale" do
locale = register_locale("es_MX", name: "Spanish (Mexico)", nativeName: "Español (México)", fallbackLocale: "es")
expect(DiscoursePluginRegistry.locales.count).to eq(1)
expect(DiscoursePluginRegistry.locales).to have_key(:es_MX)
expect(locale[:fallbackLocale]).to eq("es")
expect(locale[:message_format]).to eq(["es", "#{Rails.root}/lib/javascripts/locale/es.js"])
expect(locale[:moment_js]).to eq(["es", "#{Rails.root}/lib/javascripts/moment_locale/es.js"])
expect(locale[:plural]).to be_nil
expect(Rails.configuration.assets.precompile).to include("locales/es_MX.js")
end
it "correctly registers a new locale when some files exist in core" do
locale = register_locale("tlh", name: "Klingon", nativeName: "tlhIngan Hol", plural: plural)
expect(DiscoursePluginRegistry.locales.count).to eq(1)
expect(DiscoursePluginRegistry.locales).to have_key(:tlh)
expect(locale[:fallbackLocale]).to be_nil
expect(locale[:message_format]).to eq(["tlh", "#{plugin_path}/lib/javascripts/locale/message_format/tlh.js"])
expect(locale[:moment_js]).to eq(["tlh", "#{Rails.root}/lib/javascripts/moment_locale/tlh.js"])
expect(locale[:plural]).to eq(plural.with_indifferent_access)
expect(Rails.configuration.assets.precompile).to include("locales/tlh.js")
end
it "does not register a new locale when the fallback locale does not exist" do
register_locale("bar", name: "Bar", nativeName: "Bar", fallbackLocale: "foo")
expect(DiscoursePluginRegistry.locales.count).to eq(0)
end
[
"config/locales/client.foo.yml",
"config/locales/server.foo.yml",
"lib/javascripts/locale/message_format/foo.js",
"lib/javascripts/locale/moment_js/foo.js",
"assets/locales/foo.js.erb"
].each do |path|
it "does not register a new locale when #{path} is missing" do
path = "#{plugin_path}/#{path}"
File.stubs('exist?').returns(false)
File.stubs('exist?').with(regexp_matches(/#{Regexp.quote(plugin_path)}.*/)).returns(true)
File.stubs('exist?').with(path).returns(false)
register_locale("foo", name: "Foo", nativeName: "Foo Bar", plural: plural)
expect(DiscoursePluginRegistry.locales.count).to eq(0)
end
end
end
end

View File

@ -0,0 +1,2 @@
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:es_MX) %>

View File

@ -0,0 +1,2 @@
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:foo) %>

View File

@ -0,0 +1,2 @@
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:tlh) %>

View File

@ -0,0 +1 @@
es_MX:

View File

@ -0,0 +1 @@
foo:

View File

@ -0,0 +1 @@
tlh:

View File

@ -0,0 +1 @@
es_MX:

View File

@ -0,0 +1 @@
foo:

View File

@ -0,0 +1 @@
tlh:

View File

@ -0,0 +1 @@
// this file should contain plural rules

View File

@ -0,0 +1 @@
// this file should contain plural rules

View File

@ -0,0 +1 @@
// this file should contain the locale configuration for moment.js

View File

@ -0,0 +1,4 @@
# name: custom-locales
# about: Fixtures for plugin that adds new locales
# version: 1.0
# authors: Gerhard Schlager

View File

@ -1,6 +1,15 @@
require 'rails_helper'
describe LocaleSiteSetting do
def core_locales
pattern = File.join(Rails.root, 'config', 'locales', 'client.*.yml')
Dir.glob(pattern).map { |x| x.split('.')[-2] }
end
def native_locale_name(locale)
value = LocaleSiteSetting.values.find { |v| v[:value] == locale }
value[:name]
end
describe 'valid_value?' do
it 'returns true for a locale that we have translations for' do
@ -14,8 +23,69 @@ describe LocaleSiteSetting do
describe 'values' do
it 'returns all the locales that we have translations for' do
expect(LocaleSiteSetting.values.map { |x| x[:value] }).to include(*Dir.glob(File.join(Rails.root, 'config', 'locales', 'client.*.yml')).map { |x| x.split('.')[-2] })
expect(LocaleSiteSetting.values.map { |x| x[:value] }).to include(*core_locales)
end
it 'returns native names' do
expect(native_locale_name('de')).to eq('Deutsch')
expect(native_locale_name('zh_CN')).to eq('中文')
expect(native_locale_name('zh_TW')).to eq('中文 (TW)')
end
end
context 'with locales from plugin' do
before do
DiscoursePluginRegistry.register_locale("foo", name: "Foo", nativeName: "Native Foo")
DiscoursePluginRegistry.register_locale("bar", name: "Bar", nativeName: "Native Bar")
DiscoursePluginRegistry.register_locale("de", name: "Renamed German", nativeName: "Native renamed German")
DiscoursePluginRegistry.register_locale("de_AT", name: "German (Austria)", nativeName: "Österreichisch", fallbackLocale: "de")
DiscoursePluginRegistry.register_locale("tlh")
# Plugins normally register a locale before LocaleSiteSetting is initialized.
# That's not happening in tests, so we need to call reset!
LocaleSiteSetting.reset!
end
after do
DiscoursePluginRegistry.reset!
end
describe 'valid_value?' do
it 'returns true for locales from core' do
expect(LocaleSiteSetting.valid_value?('en')).to eq(true)
expect(LocaleSiteSetting.valid_value?('de')).to eq(true)
end
it 'returns true for locales added by plugins' do
expect(LocaleSiteSetting.valid_value?('foo')).to eq(true)
expect(LocaleSiteSetting.valid_value?('bar')).to eq(true)
end
end
describe 'values' do
it 'returns native names added by plugin' do
expect(native_locale_name('foo')).to eq('Native Foo')
expect(native_locale_name('bar')).to eq('Native Bar')
end
it 'does not allow plugins to override native names that exist in core' do
expect(native_locale_name('de')).to eq('Deutsch')
end
it 'returns the language code when no nativeName is set' do
expect(native_locale_name('tlh')).to eq('tlh')
end
end
describe 'fallback_locale' do
it 'returns the fallback locale registered by plugin' do
expect(LocaleSiteSetting.fallback_locale('de_AT')).to eq(:de)
expect(LocaleSiteSetting.fallback_locale(:de_AT)).to eq(:de)
end
it 'returns nothing when no fallback locale was registered' do
expect(LocaleSiteSetting.fallback_locale('foo')).to be_nil
end
end
end
end

View File

@ -1,17 +1,30 @@
QUnit.module("lib:i18n", {
_locale: I18n.locale,
_fallbackLocale: I18n.fallbackLocale,
_translations: I18n.translations,
beforeEach() {
I18n.locale = "fr";
I18n.translations = {
"fr_FOO": {
"js": {
"topic": {
"reply": {
"title": "Foo"
}
},
}
},
"fr": {
"js": {
"hello": "Bonjour",
"topic": {
"reply": {
"title": "Répondre",
"title": "Répondre"
},
"share": {
"title": "Partager"
}
},
"character_count": {
@ -56,6 +69,7 @@ QUnit.module("lib:i18n", {
afterEach() {
I18n.locale = this._locale;
I18n.fallbackLocale = this._fallbackLocale;
I18n.translations = this._translations;
}
});
@ -93,3 +107,12 @@ QUnit.test("pluralizations", assert => {
assert.equal(I18n.t("word_count", { count: 10 }), "10 words");
assert.equal(I18n.t("word_count", { count: 100 }), "100 words");
});
QUnit.test("fallback", assert => {
I18n.locale = "fr_FOO";
I18n.fallbackLocale = "fr";
assert.equal(I18n.t("topic.reply.title"), "Foo", "uses locale translations when they exist");
assert.equal(I18n.t("topic.share.title"), "Partager", "falls back to fallbackLocale translations when they exist");
assert.equal(I18n.t("topic.reply.help"), "begin composing a reply to this topic", "falls back to English translations");
});