2013-05-30 01:53:40 -04:00
|
|
|
module JsLocaleHelper
|
|
|
|
|
2015-08-20 12:03:43 -04:00
|
|
|
def self.load_translations(locale, opts=nil)
|
|
|
|
opts ||= {}
|
|
|
|
|
|
|
|
@loaded_translations = nil if opts[:force]
|
|
|
|
|
2015-07-15 17:23:41 -04:00
|
|
|
@loaded_translations ||= HashWithIndifferentAccess.new
|
2015-07-15 11:56:46 -04:00
|
|
|
@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
|
2015-07-15 17:30:16 -04:00
|
|
|
all_translations = {}
|
2015-07-15 11:56:46 -04:00
|
|
|
merged_translations = {}
|
2015-07-15 17:30:16 -04:00
|
|
|
loaded_locales = []
|
|
|
|
|
|
|
|
locales.map(&:to_s).each do |locale|
|
|
|
|
all_translations[locale] = JsLocaleHelper.load_translations locale
|
|
|
|
merged_translations[locale] = deep_delete_matches(all_translations[locale][locale], *loaded_locales.map { |l| merged_translations[l] })
|
|
|
|
loaded_locales << locale
|
2015-07-15 11:56:46 -04:00
|
|
|
end
|
|
|
|
merged_translations
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-15 17:23:41 -04:00
|
|
|
def self.output_locale(locale)
|
|
|
|
locale_sym = locale.to_sym
|
|
|
|
locale_str = locale.to_s
|
|
|
|
|
2014-08-07 12:08:15 -04:00
|
|
|
current_locale = I18n.locale
|
2015-07-15 17:23:41 -04:00
|
|
|
I18n.locale = locale_sym
|
2014-08-07 12:08:15 -04:00
|
|
|
|
2015-07-15 11:56:46 -04:00
|
|
|
site_locale = SiteSetting.default_locale.to_sym
|
2013-05-30 01:53:40 -04:00
|
|
|
|
2015-07-15 17:25:24 -04:00
|
|
|
if Rails.env.development?
|
2015-08-20 12:03:43 -04:00
|
|
|
translations = load_translations(locale_sym, force: true)
|
2015-07-15 11:56:46 -04:00
|
|
|
else
|
2015-07-15 17:25:24 -04:00
|
|
|
if locale_sym == :en
|
|
|
|
translations = load_translations(locale_sym)
|
|
|
|
elsif locale_sym == site_locale || site_locale == :en
|
|
|
|
translations = load_translations_merged(locale_sym, :en)
|
|
|
|
else
|
|
|
|
translations = load_translations_merged(locale_sym, site_locale, :en)
|
|
|
|
end
|
2013-11-19 10:42:28 -05:00
|
|
|
end
|
2014-12-11 11:08:47 -05:00
|
|
|
|
2013-05-30 01:53:40 -04:00
|
|
|
message_formats = strip_out_message_formats!(translations[locale_str]['js'])
|
|
|
|
|
|
|
|
result = generate_message_format(message_formats, locale_str)
|
|
|
|
|
|
|
|
result << "I18n.translations = #{translations.to_json};\n"
|
2013-06-11 03:25:50 -04:00
|
|
|
result << "I18n.locale = '#{locale_str}';\n"
|
|
|
|
# loading moment here cause we must customize it
|
|
|
|
result << File.read("#{Rails.root}/lib/javascripts/moment.js")
|
2013-06-07 04:03:09 -04:00
|
|
|
result << moment_locale(locale_str)
|
2013-06-11 03:25:50 -04:00
|
|
|
result << moment_formats
|
2014-08-07 12:08:15 -04:00
|
|
|
|
|
|
|
I18n.locale = current_locale
|
|
|
|
|
2013-05-30 01:53:40 -04:00
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2013-06-11 03:25:50 -04:00
|
|
|
def self.moment_formats
|
|
|
|
result = ""
|
|
|
|
result << moment_format_function('short_date_no_year')
|
|
|
|
result << moment_format_function('short_date')
|
|
|
|
result << moment_format_function('long_date')
|
2013-06-12 02:38:02 -04:00
|
|
|
result << "moment.fn.relativeAge = function(opts){ return Discourse.Formatter.relativeAge(this.toDate(), opts)};\n"
|
2013-06-11 03:25:50 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.moment_format_function(name)
|
2014-03-26 10:55:40 -04:00
|
|
|
format = I18n.t("dates.#{name}")
|
2015-08-20 12:03:43 -04:00
|
|
|
"moment.fn.#{name.camelize(:lower)} = function(){ return this.format('#{format}'); };\n"
|
2013-06-11 03:25:50 -04:00
|
|
|
end
|
|
|
|
|
2013-06-07 04:03:09 -04:00
|
|
|
def self.moment_locale(locale_str)
|
2013-07-24 21:09:29 -04:00
|
|
|
filename = Rails.root + "lib/javascripts/moment_locale/#{locale_str}.js"
|
2013-06-07 04:03:09 -04:00
|
|
|
if File.exists?(filename)
|
|
|
|
File.read(filename) << "\n"
|
|
|
|
end || ""
|
|
|
|
end
|
|
|
|
|
2013-05-30 01:53:40 -04:00
|
|
|
def self.generate_message_format(message_formats, locale_str)
|
|
|
|
formats = message_formats.map{|k,v| k.inspect << " : " << compile_message_format(locale_str ,v)}.join(" , ")
|
|
|
|
|
|
|
|
result = "MessageFormat = {locale: {}};\n"
|
2013-07-24 21:16:07 -04:00
|
|
|
|
|
|
|
filename = Rails.root + "lib/javascripts/locale/#{locale_str}.js"
|
|
|
|
filename = Rails.root + "lib/javascripts/locale/en.js" unless File.exists?(filename)
|
|
|
|
|
|
|
|
result << File.read(filename) << "\n"
|
2013-05-30 01:53:40 -04:00
|
|
|
|
|
|
|
result << "I18n.messageFormat = (function(formats){
|
|
|
|
var f = formats;
|
|
|
|
return function(key, options) {
|
|
|
|
var fn = f[key];
|
|
|
|
if(fn){
|
|
|
|
try {
|
|
|
|
return fn(options);
|
|
|
|
} catch(err) {
|
|
|
|
return err.message;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return 'Missing Key: ' + key
|
|
|
|
}
|
|
|
|
return f[key](options);
|
|
|
|
};
|
|
|
|
})({#{formats}});"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.compile_message_format(locale, format)
|
|
|
|
ctx = V8::Context.new
|
|
|
|
ctx.load(Rails.root + 'lib/javascripts/messageformat.js')
|
2013-06-15 18:46:56 -04:00
|
|
|
path = Rails.root + "lib/javascripts/locale/#{locale}.js"
|
|
|
|
ctx.load(path) if File.exists?(path)
|
2013-05-30 01:53:40 -04:00
|
|
|
ctx.eval("mf = new MessageFormat('#{locale}');")
|
|
|
|
ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
|
|
|
|
|
|
|
|
rescue V8::Error => e
|
|
|
|
message = "Invalid Format: " << e.message
|
|
|
|
"function(){ return #{message.inspect};}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.strip_out_message_formats!(hash, prefix = "", rval = {})
|
2014-03-26 15:20:41 -04:00
|
|
|
if hash.is_a?(Hash)
|
|
|
|
hash.each do |key, value|
|
|
|
|
if value.is_a?(Hash)
|
|
|
|
rval.merge!(strip_out_message_formats!(value, prefix + (prefix.length > 0 ? "." : "") << key, rval))
|
|
|
|
elsif key.to_s.end_with?("_MF")
|
|
|
|
rval[prefix + (prefix.length > 0 ? "." : "") << key] = value
|
|
|
|
hash.delete(key)
|
2013-05-30 01:53:40 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
rval
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|