mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 16:02:46 +08:00
FEATURE: Allow plugins to register a new locale
This commit is contained in:
parent
ba6cd83e3a
commit
eb52c5469e
|
@ -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/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -119,9 +119,6 @@ eo:
|
|||
es:
|
||||
name: Spanish
|
||||
nativeName: Español
|
||||
es_MX:
|
||||
name: Spanish
|
||||
nativeName: Español (MX)
|
||||
et:
|
||||
name: Estonian
|
||||
nativeName: eesti
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
@ -101,9 +102,10 @@ module JsLocaleHelper
|
|||
end
|
||||
|
||||
def self.translations_for(locale_str)
|
||||
current_locale = I18n.locale
|
||||
locale_sym = locale_str.to_sym
|
||||
site_locale = SiteSetting.default_locale.to_sym
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
48
spec/components/fallback_locale_list_spec.rb
Normal file
48
spec/components/fallback_locale_list_spec.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
2
spec/fixtures/plugins/custom_locales/assets/locales/es_MX.js.erb
vendored
Normal file
2
spec/fixtures/plugins/custom_locales/assets/locales/es_MX.js.erb
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:es_MX) %>
|
2
spec/fixtures/plugins/custom_locales/assets/locales/foo.js.erb
vendored
Normal file
2
spec/fixtures/plugins/custom_locales/assets/locales/foo.js.erb
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:foo) %>
|
2
spec/fixtures/plugins/custom_locales/assets/locales/tlh.js.erb
vendored
Normal file
2
spec/fixtures/plugins/custom_locales/assets/locales/tlh.js.erb
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:tlh) %>
|
1
spec/fixtures/plugins/custom_locales/config/locales/client.es_MX.yml
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/config/locales/client.es_MX.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
es_MX:
|
1
spec/fixtures/plugins/custom_locales/config/locales/client.foo.yml
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/config/locales/client.foo.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo:
|
1
spec/fixtures/plugins/custom_locales/config/locales/client.tlh.yml
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/config/locales/client.tlh.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
tlh:
|
1
spec/fixtures/plugins/custom_locales/config/locales/server.es_MX.yml
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/config/locales/server.es_MX.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
es_MX:
|
1
spec/fixtures/plugins/custom_locales/config/locales/server.foo.yml
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/config/locales/server.foo.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo:
|
1
spec/fixtures/plugins/custom_locales/config/locales/server.tlh.yml
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/config/locales/server.tlh.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
tlh:
|
1
spec/fixtures/plugins/custom_locales/lib/javascripts/locale/message_format/foo.js
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/lib/javascripts/locale/message_format/foo.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
// this file should contain plural rules
|
1
spec/fixtures/plugins/custom_locales/lib/javascripts/locale/message_format/tlh.js
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/lib/javascripts/locale/message_format/tlh.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
// this file should contain plural rules
|
1
spec/fixtures/plugins/custom_locales/lib/javascripts/locale/moment_js/foo.js
vendored
Normal file
1
spec/fixtures/plugins/custom_locales/lib/javascripts/locale/moment_js/foo.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
// this file should contain the locale configuration for moment.js
|
4
spec/fixtures/plugins/custom_locales/plugin.rb
vendored
Normal file
4
spec/fixtures/plugins/custom_locales/plugin.rb
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# name: custom-locales
|
||||
# about: Fixtures for plugin that adds new locales
|
||||
# version: 1.0
|
||||
# authors: Gerhard Schlager
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -92,4 +106,13 @@ QUnit.test("pluralizations", assert => {
|
|||
assert.equal(I18n.t("word_count", { count: 3 }), "3 words");
|
||||
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");
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user