FEATURE: Localization fallbacks (client)
This patch sets I18n.defaultLocale in the Discourse.start() script block (it was formerly always 'en') to SiteSetting.default_locale, and patches translate() to perform fallback to defaultLocale followed by english. Additionally, when enable_verbose_localization() is called, no fallbacks will be performed. It also memoizes the file loading operations in JsLocaleHelper and strips out translations from the fallbacks that are also present in a prefered language, to minimize file size.
This commit is contained in:
parent
1851c8d918
commit
728845d008
|
@ -52,6 +52,8 @@ I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
|
||||||
|
|
||||||
I18n.fallbackRules = {};
|
I18n.fallbackRules = {};
|
||||||
|
|
||||||
|
I18n.noFallbacks = false;
|
||||||
|
|
||||||
I18n.pluralizationRules = {
|
I18n.pluralizationRules = {
|
||||||
en: function(n) {
|
en: function(n) {
|
||||||
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
|
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
|
||||||
|
@ -192,6 +194,15 @@ I18n.interpolate = function(message, options) {
|
||||||
I18n.translate = function(scope, options) {
|
I18n.translate = function(scope, options) {
|
||||||
options = this.prepareOptions(options);
|
options = this.prepareOptions(options);
|
||||||
var translation = this.lookup(scope, options);
|
var translation = this.lookup(scope, options);
|
||||||
|
// Fallback to the default locale
|
||||||
|
if (!translation && this.currentLocale() !== this.defaultLocale && !this.noFallbacks) {
|
||||||
|
options.locale = this.defaultLocale;
|
||||||
|
translation = this.lookup(scope, options);
|
||||||
|
}
|
||||||
|
if (!translation && this.currentLocale() !== 'en' && !this.noFallbacks) {
|
||||||
|
options.locale = 'en';
|
||||||
|
translation = this.lookup(scope, options);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof translation === "object") {
|
if (typeof translation === "object") {
|
||||||
|
@ -513,6 +524,7 @@ I18n.enable_verbose_localization = function(){
|
||||||
var keys = {};
|
var keys = {};
|
||||||
var t = I18n.t;
|
var t = I18n.t;
|
||||||
|
|
||||||
|
I18n.noFallbacks = true;
|
||||||
|
|
||||||
I18n.t = I18n.translate = function(scope, value){
|
I18n.t = I18n.translate = function(scope, value){
|
||||||
var current = keys[scope];
|
var current = keys[scope];
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
Discourse.Environment = '<%= Rails.env %>';
|
Discourse.Environment = '<%= Rails.env %>';
|
||||||
Discourse.SiteSettings = PreloadStore.get('siteSettings');
|
Discourse.SiteSettings = PreloadStore.get('siteSettings');
|
||||||
Discourse.LetterAvatarVersion = '<%= LetterAvatar.version %>';
|
Discourse.LetterAvatarVersion = '<%= LetterAvatar.version %>';
|
||||||
|
I18n.defaultLocale = '<%= SiteSetting.default_locale %>';
|
||||||
PreloadStore.get("customEmoji").forEach(function(emoji) {
|
PreloadStore.get("customEmoji").forEach(function(emoji) {
|
||||||
Discourse.Dialect.registerEmoji(emoji.name, emoji.url);
|
Discourse.Dialect.registerEmoji(emoji.name, emoji.url);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +1,89 @@
|
||||||
module JsLocaleHelper
|
module JsLocaleHelper
|
||||||
|
|
||||||
def self.output_locale(locale, translations = nil)
|
def self.load_translations(locale)
|
||||||
|
@loaded_translations ||= {}
|
||||||
|
@loaded_translations[locale] ||= begin
|
||||||
|
locale_str = locale.to_s
|
||||||
|
|
||||||
|
# load default translations
|
||||||
|
translations = YAML::load(File.open("#{Rails.root}/config/locales/client.#{locale_str}.yml"))
|
||||||
|
# load plugins translations
|
||||||
|
plugin_translations = {}
|
||||||
|
Dir["#{Rails.root}/plugins/*/config/locales/client.#{locale_str}.yml"].each do |file|
|
||||||
|
plugin_translations.deep_merge! YAML::load(File.open(file))
|
||||||
|
end
|
||||||
|
|
||||||
|
# merge translations (plugin translations overwrite default translations)
|
||||||
|
translations[locale_str]['js'].deep_merge!(plugin_translations[locale_str]['js']) if translations[locale_str] && plugin_translations[locale_str] && plugin_translations[locale_str]['js']
|
||||||
|
|
||||||
|
# We used to split the admin versus the client side, but it's much simpler to just
|
||||||
|
# include both for now due to the small size of the admin section.
|
||||||
|
#
|
||||||
|
# For now, let's leave it split out in the translation file in case we want to split
|
||||||
|
# it again later, so we'll merge the JSON ourselves.
|
||||||
|
admin_contents = translations[locale_str].delete('admin_js')
|
||||||
|
translations[locale_str]['js'].deep_merge!(admin_contents) if admin_contents.present?
|
||||||
|
translations[locale_str]['js'].deep_merge!(plugin_translations[locale_str]['admin_js']) if translations[locale_str] && plugin_translations[locale_str] && plugin_translations[locale_str]['admin_js']
|
||||||
|
|
||||||
|
translations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# purpose-built recursive algorithm ahoy!
|
||||||
|
def self.deep_delete_matches(deleting_from, *checking_hashes)
|
||||||
|
checking_hashes.compact!
|
||||||
|
|
||||||
|
new_hash = deleting_from.dup
|
||||||
|
deleting_from.each do |key, value|
|
||||||
|
if value.is_a? Hash
|
||||||
|
# Recurse
|
||||||
|
new_at_key = deep_delete_matches(deleting_from[key], *(checking_hashes.map {|h| h[key]}))
|
||||||
|
if new_at_key.empty?
|
||||||
|
new_hash.delete key
|
||||||
|
else
|
||||||
|
new_hash[key] = new_at_key
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if checking_hashes.any? {|h| h.include? key}
|
||||||
|
new_hash.delete key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
new_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.load_translations_merged(*locales)
|
||||||
|
@loaded_merges ||= {}
|
||||||
|
@loaded_merges[locales.join('-')] ||= begin
|
||||||
|
# TODO - this will need to be reworked to support N fallbacks in the future
|
||||||
|
all_translations = locales.map { |l| JsLocaleHelper.load_translations l }
|
||||||
|
merged_translations = {}
|
||||||
|
merged_translations[locales[0].to_s] = all_translations[0][locales[0].to_s]
|
||||||
|
if locales[1]
|
||||||
|
merged_translations[locales[1].to_s] = deep_delete_matches(all_translations[1][locales[1].to_s].dup, merged_translations[locales[0].to_s])
|
||||||
|
end
|
||||||
|
if locales[2]
|
||||||
|
merged_translations[locales[2].to_s] = deep_delete_matches(all_translations[2][locales[2].to_s].dup, merged_translations[locales[0].to_s], merged_translations[locales[1].to_s])
|
||||||
|
end
|
||||||
|
merged_translations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.output_locale(locale, request=nil)
|
||||||
current_locale = I18n.locale
|
current_locale = I18n.locale
|
||||||
I18n.locale = locale.to_sym
|
I18n.locale = locale.to_sym
|
||||||
|
|
||||||
locale_str = locale.to_s
|
locale_str = locale.to_s
|
||||||
|
site_locale = SiteSetting.default_locale.to_sym
|
||||||
|
|
||||||
# load default translations
|
if locale == :en
|
||||||
translations ||= YAML::load(File.open("#{Rails.root}/config/locales/client.#{locale_str}.yml"))
|
translations = load_translations(locale)
|
||||||
# load plugins translations
|
elsif locale == site_locale || site_locale == :en
|
||||||
plugin_translations = {}
|
translations = load_translations_merged(locale, :en)
|
||||||
Dir["#{Rails.root}/plugins/*/config/locales/client.#{locale_str}.yml"].each do |file|
|
else
|
||||||
plugin_translations.deep_merge! YAML::load(File.open(file))
|
translations = load_translations_merged(locale, site_locale, :en)
|
||||||
end
|
end
|
||||||
|
|
||||||
# merge translations (plugin translations overwrite default translations)
|
|
||||||
translations[locale_str]['js'].deep_merge!(plugin_translations[locale_str]['js']) if translations[locale_str] && plugin_translations[locale_str] && plugin_translations[locale_str]['js']
|
|
||||||
|
|
||||||
# We used to split the admin versus the client side, but it's much simpler to just
|
|
||||||
# include both for now due to the small size of the admin section.
|
|
||||||
#
|
|
||||||
# For now, let's leave it split out in the translation file in case we want to split
|
|
||||||
# it again later, so we'll merge the JSON ourselves.
|
|
||||||
admin_contents = translations[locale_str].delete('admin_js')
|
|
||||||
translations[locale_str]['js'].deep_merge!(admin_contents) if admin_contents.present?
|
|
||||||
translations[locale_str]['js'].deep_merge!(plugin_translations[locale_str]['admin_js']) if translations[locale_str] && plugin_translations[locale_str] && plugin_translations[locale_str]['admin_js']
|
|
||||||
message_formats = strip_out_message_formats!(translations[locale_str]['js'])
|
message_formats = strip_out_message_formats!(translations[locale_str]['js'])
|
||||||
|
|
||||||
result = generate_message_format(message_formats, locale_str)
|
result = generate_message_format(message_formats, locale_str)
|
||||||
|
|
Loading…
Reference in New Issue