i18n-js 2.1.2 → 3.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +24 -0
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/tests.yaml +106 -0
- data/.gitignore +6 -4
- data/.npmignore +27 -0
- data/Appraisals +52 -0
- data/CHANGELOG.md +575 -0
- data/Gemfile +1 -1
- data/README.md +1099 -0
- data/Rakefile +19 -7
- data/app/assets/javascripts/i18n/filtered.js.erb +23 -0
- data/app/assets/javascripts/i18n/shims.js +240 -0
- data/app/assets/javascripts/i18n/translations.js +3 -0
- data/app/assets/javascripts/i18n.js +1095 -0
- data/gemfiles/i18n_0_6.gemfile +7 -0
- data/gemfiles/i18n_0_7.gemfile +7 -0
- data/gemfiles/i18n_0_8.gemfile +7 -0
- data/gemfiles/i18n_0_9.gemfile +7 -0
- data/gemfiles/i18n_1_0.gemfile +7 -0
- data/gemfiles/i18n_1_1.gemfile +7 -0
- data/gemfiles/i18n_1_10.gemfile +7 -0
- data/gemfiles/i18n_1_2.gemfile +7 -0
- data/gemfiles/i18n_1_3.gemfile +7 -0
- data/gemfiles/i18n_1_4.gemfile +7 -0
- data/gemfiles/i18n_1_5.gemfile +7 -0
- data/gemfiles/i18n_1_6.gemfile +7 -0
- data/gemfiles/i18n_1_7.gemfile +7 -0
- data/gemfiles/i18n_1_8.gemfile +7 -0
- data/gemfiles/i18n_1_9.gemfile +7 -0
- data/i18n-js.gemspec +13 -10
- data/i18njs.png +0 -0
- data/lib/i18n/js/dependencies.rb +67 -0
- data/lib/i18n/js/engine.rb +87 -0
- data/lib/i18n/js/fallback_locales.rb +70 -0
- data/lib/i18n/js/formatters/base.rb +25 -0
- data/lib/i18n/js/formatters/js.rb +39 -0
- data/lib/i18n/js/formatters/json.rb +13 -0
- data/lib/{i18n-js → i18n/js}/middleware.rb +32 -9
- data/lib/i18n/js/private/config_store.rb +31 -0
- data/lib/i18n/js/private/hash_with_symbol_keys.rb +36 -0
- data/lib/i18n/js/segment.rb +81 -0
- data/lib/i18n/js/utils.rb +91 -0
- data/lib/i18n/js/version.rb +7 -0
- data/lib/i18n/js.rb +274 -0
- data/lib/i18n-js.rb +1 -177
- data/lib/rails/generators/i18n/js/config/config_generator.rb +19 -0
- data/{config → lib/rails/generators/i18n/js/config/templates}/i18n-js.yml +11 -6
- data/lib/tasks/export.rake +8 -0
- data/package.json +25 -0
- data/spec/fixtures/custom_path.yml +5 -0
- data/spec/fixtures/default.yml +5 -0
- data/spec/fixtures/erb.yml +5 -0
- data/spec/fixtures/except_condition.yml +7 -0
- data/spec/fixtures/js_available_locales_custom.yml +1 -0
- data/spec/fixtures/js_export_dir_custom.yml +7 -0
- data/spec/fixtures/js_export_dir_none.yml +6 -0
- data/spec/fixtures/js_extend_parent.yml +6 -0
- data/spec/fixtures/js_extend_segment.yml +6 -0
- data/spec/fixtures/js_file_per_locale.yml +7 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml +4 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_hash.yml +6 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale.yml +4 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml +4 -0
- data/spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml +4 -0
- data/spec/fixtures/js_file_per_locale_without_fallbacks.yml +4 -0
- data/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml +9 -0
- data/spec/fixtures/js_sort_translation_keys_false.yml +6 -0
- data/spec/fixtures/js_sort_translation_keys_true.yml +6 -0
- data/spec/fixtures/json_only.yml +18 -0
- data/spec/{resources → fixtures}/locales.yml +58 -1
- data/spec/fixtures/merge_plurals.yml +6 -0
- data/spec/fixtures/merge_plurals_with_no_overrides.yml +4 -0
- data/spec/fixtures/merge_plurals_with_partial_overrides.yml +4 -0
- data/spec/fixtures/millions.yml +4 -0
- data/spec/fixtures/multiple_conditions.yml +7 -0
- data/spec/fixtures/multiple_conditions_per_locale.yml +7 -0
- data/spec/fixtures/multiple_files.yml +7 -0
- data/spec/{resources → fixtures}/no_config.yml +0 -0
- data/spec/fixtures/no_scope.yml +4 -0
- data/spec/fixtures/simple_scope.yml +5 -0
- data/spec/js/currency.spec.js +62 -0
- data/spec/js/current_locale.spec.js +19 -0
- data/spec/js/dates.spec.js +276 -0
- data/spec/js/defaults.spec.js +31 -0
- data/spec/js/extend.spec.js +110 -0
- data/spec/js/interpolation.spec.js +124 -0
- data/spec/js/jasmine/MIT.LICENSE +20 -0
- data/spec/js/jasmine/jasmine-html.js +190 -0
- data/spec/js/jasmine/jasmine.css +166 -0
- data/spec/js/jasmine/jasmine.js +2476 -0
- data/spec/js/jasmine/jasmine_favicon.png +0 -0
- data/spec/js/json_parsable.spec.js +14 -0
- data/spec/js/locales.spec.js +31 -0
- data/spec/js/localization.spec.js +78 -0
- data/spec/js/numbers.spec.js +174 -0
- data/spec/js/placeholder.spec.js +24 -0
- data/spec/js/pluralization.spec.js +228 -0
- data/spec/js/prepare_options.spec.js +41 -0
- data/spec/js/require.js +2083 -0
- data/spec/js/specs.html +49 -0
- data/spec/js/specs_requirejs.html +72 -0
- data/spec/js/translate.spec.js +304 -0
- data/spec/js/translations.js +188 -0
- data/spec/js/utility_functions.spec.js +20 -0
- data/spec/ruby/i18n/js/fallback_locales_spec.rb +84 -0
- data/spec/ruby/i18n/js/segment_spec.rb +286 -0
- data/spec/ruby/i18n/js/utils_spec.rb +138 -0
- data/spec/ruby/i18n/js_spec.rb +797 -0
- data/spec/spec_helper.rb +75 -14
- data/yarn.lock +138 -0
- metadata +225 -96
- data/.rspec +0 -1
- data/Gemfile.lock +0 -51
- data/README.rdoc +0 -305
- data/lib/i18n-js/engine.rb +0 -62
- data/lib/i18n-js/railtie.rb +0 -13
- data/lib/i18n-js/rake.rb +0 -16
- data/lib/i18n-js/version.rb +0 -10
- data/spec/i18n_spec.js +0 -768
- data/spec/i18n_spec.rb +0 -205
- data/spec/resources/custom_path.yml +0 -4
- data/spec/resources/default.yml +0 -4
- data/spec/resources/js_file_per_locale.yml +0 -3
- data/spec/resources/multiple_conditions.yml +0 -6
- data/spec/resources/multiple_files.yml +0 -6
- data/spec/resources/no_scope.yml +0 -3
- data/spec/resources/simple_scope.yml +0 -4
- data/vendor/assets/javascripts/i18n/translations.js.erb +0 -7
- data/vendor/assets/javascripts/i18n.js +0 -450
@@ -0,0 +1,81 @@
|
|
1
|
+
require "i18n/js/private/hash_with_symbol_keys"
|
2
|
+
require "i18n/js/formatters/js"
|
3
|
+
require "i18n/js/formatters/json"
|
4
|
+
|
5
|
+
module I18n
|
6
|
+
module JS
|
7
|
+
|
8
|
+
# Class which enscapulates a translations hash and outputs a single JSON translation file
|
9
|
+
class Segment
|
10
|
+
OPTIONS = [:namespace, :pretty_print, :js_extend, :prefix, :suffix, :sort_translation_keys, :json_only].freeze
|
11
|
+
LOCALE_INTERPOLATOR = /%\{locale\}/
|
12
|
+
|
13
|
+
attr_reader *([:file, :translations] | OPTIONS)
|
14
|
+
|
15
|
+
def initialize(file, translations, options = {})
|
16
|
+
@file = file
|
17
|
+
# `#slice` will be used
|
18
|
+
# But when activesupport is absent,
|
19
|
+
# the core extension from `i18n` gem will be used instead
|
20
|
+
# And it's causing errors (at least in test)
|
21
|
+
#
|
22
|
+
# So the input is wrapped by our class for better `#slice`
|
23
|
+
@translations = Private::HashWithSymbolKeys.new(translations)
|
24
|
+
@namespace = options[:namespace] || 'I18n'
|
25
|
+
@pretty_print = !!options[:pretty_print]
|
26
|
+
@js_extend = options.key?(:js_extend) ? !!options[:js_extend] : true
|
27
|
+
@prefix = options.key?(:prefix) ? options[:prefix] : nil
|
28
|
+
@suffix = options.key?(:suffix) ? options[:suffix] : nil
|
29
|
+
@sort_translation_keys = options.key?(:sort_translation_keys) ? !!options[:sort_translation_keys] : true
|
30
|
+
@json_only = options.key?(:json_only) ? !!options[:json_only] : false
|
31
|
+
end
|
32
|
+
|
33
|
+
# Saves JSON file containing translations
|
34
|
+
def save!
|
35
|
+
if @file =~ LOCALE_INTERPOLATOR
|
36
|
+
I18n::JS.js_available_locales.each do |locale|
|
37
|
+
write_file(file_for_locale(locale), @translations.slice(locale))
|
38
|
+
end
|
39
|
+
else
|
40
|
+
write_file
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def file_for_locale(locale)
|
47
|
+
@file.gsub(LOCALE_INTERPOLATOR, locale.to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_file(_file = @file, _translations = @translations)
|
51
|
+
FileUtils.mkdir_p File.dirname(_file)
|
52
|
+
_translations = Utils.deep_key_sort(_translations) if @sort_translation_keys
|
53
|
+
_translations = Utils.deep_remove_procs(_translations)
|
54
|
+
contents = formatter.format(_translations)
|
55
|
+
|
56
|
+
return if File.exist?(_file) && File.read(_file) == contents
|
57
|
+
|
58
|
+
File.open(_file, "w+") do |f|
|
59
|
+
f << contents
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def formatter
|
64
|
+
if @json_only
|
65
|
+
Formatters::JSON.new(**formatter_options)
|
66
|
+
else
|
67
|
+
Formatters::JS.new(**formatter_options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def formatter_options
|
72
|
+
{ js_extend: @js_extend,
|
73
|
+
namespace: @namespace,
|
74
|
+
pretty_print: @pretty_print,
|
75
|
+
prefix: @prefix,
|
76
|
+
suffix: @suffix
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module I18n
|
2
|
+
module JS
|
3
|
+
module Utils
|
4
|
+
PLURAL_KEYS = %i[zero one two few many other].freeze
|
5
|
+
|
6
|
+
# Based on deep_merge by Stefan Rusterholz, see <https://www.ruby-forum.com/topic/142809>.
|
7
|
+
# This method is used to handle I18n fallbacks. Given two equivalent path nodes in two locale trees:
|
8
|
+
# 1. If the node in the current locale appears to be an I18n pluralization (:one, :other, etc.),
|
9
|
+
# use the node, but merge in any missing/non-nil keys from the fallback (default) locale.
|
10
|
+
# 2. Else if both nodes are Hashes, combine (merge) the key-value pairs of the two nodes into one,
|
11
|
+
# prioritizing the current locale.
|
12
|
+
# 3. Else if either node is nil, use the other node.
|
13
|
+
PLURAL_MERGER = proc do |_key, v1, v2|
|
14
|
+
v1 || v2
|
15
|
+
end
|
16
|
+
MERGER = proc do |_key, v1, v2|
|
17
|
+
if Hash === v1 && Hash === v2
|
18
|
+
if (v2.keys - PLURAL_KEYS).empty?
|
19
|
+
slice(v2.merge(v1, &PLURAL_MERGER), v2.keys)
|
20
|
+
else
|
21
|
+
v1.merge(v2, &MERGER)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
v2 || v1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
HASH_NIL_VALUE_CLEANER_PROC = proc do |k, v|
|
29
|
+
v.kind_of?(Hash) ? (v.delete_if(&HASH_NIL_VALUE_CLEANER_PROC); false) : v.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.slice(hash, keys)
|
33
|
+
if hash.respond_to?(:slice) # ruby 2.5 onwards
|
34
|
+
hash.slice(*keys)
|
35
|
+
else
|
36
|
+
hash.select {|key, _| keys.include?(key)}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.strip_keys_with_nil_values(hash)
|
41
|
+
hash.dup.delete_if(&HASH_NIL_VALUE_CLEANER_PROC)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.deep_merge(target_hash, hash) # :nodoc:
|
45
|
+
target_hash.merge(hash, &MERGER)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.deep_merge!(target_hash, hash) # :nodoc:
|
49
|
+
target_hash.merge!(hash, &MERGER)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.deep_reject(hash, scopes = [], &block)
|
53
|
+
hash.each_with_object({}) do |(k, v), memo|
|
54
|
+
unless block.call(k, v, scopes + [k.to_s])
|
55
|
+
memo[k] = v.kind_of?(Hash) ? deep_reject(v, scopes + [k.to_s], &block) : v
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.scopes_match?(scopes1, scopes2)
|
61
|
+
if scopes1.length == scopes2.length
|
62
|
+
[scopes1, scopes2].transpose.all? do |scope1, scope2|
|
63
|
+
scope1.to_s == '*' || scope2.to_s == '*' || scope1.to_s == scope2.to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.deep_key_sort(hash)
|
69
|
+
# Avoid things like `true` or `1` from YAML which causes error
|
70
|
+
hash.keys.sort {|a, b| a.to_s <=> b.to_s}.
|
71
|
+
each_with_object({}) do |key, seed|
|
72
|
+
value = hash[key]
|
73
|
+
seed[key] = value.is_a?(Hash) ? deep_key_sort(value) : value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.deep_remove_procs(hash)
|
78
|
+
# procs exist in `i18n.plural.rule` as pluralizer
|
79
|
+
# But having it in translation causes the exported JS/JSON changes every time
|
80
|
+
# https://github.com/ruby-i18n/i18n/blob/v1.8.7/lib/i18n/backend/pluralization.rb#L51
|
81
|
+
hash.keys.
|
82
|
+
each_with_object({}) do |key, seed|
|
83
|
+
value = hash[key]
|
84
|
+
next if value.is_a?(Proc)
|
85
|
+
|
86
|
+
seed[key] = value.is_a?(Hash) ? deep_remove_procs(value) : value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/i18n/js.rb
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "fileutils"
|
3
|
+
require "i18n"
|
4
|
+
|
5
|
+
require "i18n/js/utils"
|
6
|
+
require "i18n/js/private/hash_with_symbol_keys"
|
7
|
+
require "i18n/js/private/config_store"
|
8
|
+
|
9
|
+
module I18n
|
10
|
+
module JS
|
11
|
+
require "i18n/js/dependencies"
|
12
|
+
require "i18n/js/fallback_locales"
|
13
|
+
require "i18n/js/segment"
|
14
|
+
if JS::Dependencies.rails?
|
15
|
+
require "i18n/js/middleware"
|
16
|
+
require "i18n/js/engine"
|
17
|
+
end
|
18
|
+
|
19
|
+
DEFAULT_CONFIG_PATH = "config/i18n-js.yml"
|
20
|
+
DEFAULT_EXPORT_DIR_PATH = "public/javascripts"
|
21
|
+
|
22
|
+
# The configuration file. This defaults to the `config/i18n-js.yml` file.
|
23
|
+
#
|
24
|
+
def self.config_file_path
|
25
|
+
@config_file_path ||= DEFAULT_CONFIG_PATH
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.config_file_path=(new_path)
|
29
|
+
@config_file_path = new_path
|
30
|
+
# new config file path = need to re-read config from new file
|
31
|
+
Private::ConfigStore.instance.flush_cache
|
32
|
+
end
|
33
|
+
|
34
|
+
# Allow using a different backend than the one globally configured
|
35
|
+
def self.backend
|
36
|
+
@backend ||= I18n.backend
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.backend=(alternative_backend)
|
40
|
+
@backend = alternative_backend
|
41
|
+
end
|
42
|
+
|
43
|
+
# Export translations to JavaScript, considering settings
|
44
|
+
# from configuration file
|
45
|
+
def self.export
|
46
|
+
export_i18n_js
|
47
|
+
|
48
|
+
translation_segments.each(&:save!)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.segment_for_scope(scope, exceptions)
|
52
|
+
if scope == "*"
|
53
|
+
exclude(translations, exceptions)
|
54
|
+
else
|
55
|
+
scoped_translations(scope, exceptions)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.configured_segments
|
60
|
+
config[:translations].inject([]) do |segments, options_hash|
|
61
|
+
options_hash_with_symbol_keys = Private::HashWithSymbolKeys.new(options_hash)
|
62
|
+
file = options_hash_with_symbol_keys[:file]
|
63
|
+
only = options_hash_with_symbol_keys[:only] || '*'
|
64
|
+
exceptions = [options_hash_with_symbol_keys[:except] || []].flatten
|
65
|
+
|
66
|
+
result = segment_for_scope(only, exceptions)
|
67
|
+
|
68
|
+
merge_with_fallbacks!(result) if fallbacks
|
69
|
+
|
70
|
+
unless result.empty?
|
71
|
+
segments << Segment.new(
|
72
|
+
file,
|
73
|
+
result,
|
74
|
+
extract_segment_options(options_hash_with_symbol_keys),
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
segments
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# deep_merge! given result with result for fallback locale
|
83
|
+
def self.merge_with_fallbacks!(result)
|
84
|
+
js_available_locales.each do |locale|
|
85
|
+
fallback_locales = FallbackLocales.new(fallbacks, locale)
|
86
|
+
fallback_locales.each do |fallback_locale|
|
87
|
+
# `result[fallback_locale]` could be missing
|
88
|
+
result[locale] = Utils.deep_merge(result[fallback_locale] || {}, result[locale] || {})
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.filtered_translations
|
94
|
+
translations = {}.tap do |result|
|
95
|
+
translation_segments.each do |segment|
|
96
|
+
Utils.deep_merge!(result, segment.translations)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
return Utils.deep_key_sort(translations) if I18n::JS.sort_translation_keys?
|
100
|
+
translations
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.translation_segments
|
104
|
+
if config_file_exists? && config[:translations]
|
105
|
+
configured_segments
|
106
|
+
else
|
107
|
+
[Segment.new("#{DEFAULT_EXPORT_DIR_PATH}/translations.js", translations)]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Load configuration file for partial exporting and
|
112
|
+
# custom output directory
|
113
|
+
def self.config
|
114
|
+
Private::ConfigStore.instance.fetch do
|
115
|
+
if config_file_exists?
|
116
|
+
erb_result_from_yaml_file = ERB.new(File.read(config_file_path)).result
|
117
|
+
Private::HashWithSymbolKeys.new(
|
118
|
+
(::YAML.load(erb_result_from_yaml_file) || {})
|
119
|
+
)
|
120
|
+
else
|
121
|
+
Private::HashWithSymbolKeys.new({})
|
122
|
+
end.freeze
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# @api private
|
127
|
+
# Check if configuration file exist
|
128
|
+
def self.config_file_exists?
|
129
|
+
File.file? config_file_path
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.scoped_translations(scopes, exceptions = []) # :nodoc:
|
133
|
+
result = {}
|
134
|
+
|
135
|
+
[scopes].flatten.each do |scope|
|
136
|
+
translations_without_exceptions = exclude(translations, exceptions)
|
137
|
+
filtered_translations = filter(translations_without_exceptions, scope) || {}
|
138
|
+
|
139
|
+
Utils.deep_merge!(result, filtered_translations)
|
140
|
+
end
|
141
|
+
|
142
|
+
result
|
143
|
+
end
|
144
|
+
|
145
|
+
# Exclude keys from translations listed in the `except:` section in the config file
|
146
|
+
def self.exclude(translations, exceptions)
|
147
|
+
return translations if exceptions.empty?
|
148
|
+
|
149
|
+
exceptions.inject(translations) do |memo, exception|
|
150
|
+
exception_scopes = exception.to_s.split(".")
|
151
|
+
Utils.deep_reject(memo) do |key, value, scopes|
|
152
|
+
Utils.scopes_match?(scopes, exception_scopes)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Filter translations according to the specified scope.
|
158
|
+
def self.filter(translations, scopes)
|
159
|
+
scopes = scopes.split(".") if scopes.is_a?(String)
|
160
|
+
scopes = scopes.clone
|
161
|
+
scope = scopes.shift
|
162
|
+
|
163
|
+
if scope == "*"
|
164
|
+
results = {}
|
165
|
+
translations.each do |scope, translations|
|
166
|
+
tmp = scopes.empty? ? translations : filter(translations, scopes)
|
167
|
+
results[scope.to_sym] = tmp unless tmp.nil?
|
168
|
+
end
|
169
|
+
return results
|
170
|
+
elsif translations.respond_to?(:key?) && translations.key?(scope.to_sym)
|
171
|
+
return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)}
|
172
|
+
end
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
|
176
|
+
# Initialize and return translations
|
177
|
+
def self.translations
|
178
|
+
self.backend.instance_eval do
|
179
|
+
init_translations unless initialized?
|
180
|
+
# When activesupport is absent,
|
181
|
+
# the core extension (`#slice`) from `i18n` gem will be used instead
|
182
|
+
# And it's causing errors (at least in test)
|
183
|
+
#
|
184
|
+
# So the input is wrapped by our class for better `#slice`
|
185
|
+
Private::HashWithSymbolKeys.new(translations).
|
186
|
+
slice(*::I18n::JS.js_available_locales).
|
187
|
+
to_h
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.use_fallbacks?
|
192
|
+
fallbacks != false
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.json_only
|
196
|
+
config.fetch(:json_only) do
|
197
|
+
# default value
|
198
|
+
false
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.fallbacks
|
203
|
+
config.fetch(:fallbacks) do
|
204
|
+
# default value
|
205
|
+
true
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.js_extend
|
210
|
+
config.fetch(:js_extend) do
|
211
|
+
# default value
|
212
|
+
true
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Get all available locales.
|
217
|
+
#
|
218
|
+
# @return [Array<Symbol>] the locales.
|
219
|
+
def self.js_available_locales
|
220
|
+
config.fetch(:js_available_locales) do
|
221
|
+
# default value
|
222
|
+
I18n.available_locales
|
223
|
+
end.map(&:to_sym)
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.sort_translation_keys?
|
227
|
+
@sort_translation_keys ||= (config[:sort_translation_keys]) if config.key?(:sort_translation_keys)
|
228
|
+
@sort_translation_keys = true if @sort_translation_keys.nil?
|
229
|
+
@sort_translation_keys
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.sort_translation_keys=(value)
|
233
|
+
@sort_translation_keys = !!value
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.extract_segment_options(options)
|
237
|
+
segment_options = Private::HashWithSymbolKeys.new({
|
238
|
+
js_extend: js_extend,
|
239
|
+
sort_translation_keys: sort_translation_keys?,
|
240
|
+
json_only: json_only
|
241
|
+
}).freeze
|
242
|
+
segment_options.merge(options.slice(*Segment::OPTIONS))
|
243
|
+
end
|
244
|
+
|
245
|
+
### Export i18n.js
|
246
|
+
begin
|
247
|
+
|
248
|
+
# Copy i18n.js
|
249
|
+
def self.export_i18n_js
|
250
|
+
return unless export_i18n_js_dir_path.is_a? String
|
251
|
+
|
252
|
+
FileUtils.mkdir_p(export_i18n_js_dir_path)
|
253
|
+
|
254
|
+
i18n_js_path = File.expand_path('../../../app/assets/javascripts/i18n.js', __FILE__)
|
255
|
+
destination_path = File.expand_path("i18n.js", export_i18n_js_dir_path)
|
256
|
+
return if File.exist?(destination_path) && FileUtils.identical?(i18n_js_path, destination_path)
|
257
|
+
|
258
|
+
FileUtils.cp(i18n_js_path, export_i18n_js_dir_path)
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.export_i18n_js_dir_path
|
262
|
+
@export_i18n_js_dir_path ||= (config[:export_i18n_js] || :none) if config.key?(:export_i18n_js)
|
263
|
+
@export_i18n_js_dir_path ||= DEFAULT_EXPORT_DIR_PATH
|
264
|
+
@export_i18n_js_dir_path
|
265
|
+
end
|
266
|
+
|
267
|
+
# Setting this to nil would disable i18n.js exporting
|
268
|
+
def self.export_i18n_js_dir_path=(new_path)
|
269
|
+
new_path = :none unless new_path.is_a? String
|
270
|
+
@export_i18n_js_dir_path = new_path
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
data/lib/i18n-js.rb
CHANGED
@@ -1,177 +1 @@
|
|
1
|
-
require "
|
2
|
-
|
3
|
-
module SimplesIdeias
|
4
|
-
module I18n
|
5
|
-
extend self
|
6
|
-
|
7
|
-
require "i18n-js/railtie" if Rails.version >= "3.0"
|
8
|
-
require "i18n-js/engine" if Rails.version >= "3.1"
|
9
|
-
require "i18n-js/middleware"
|
10
|
-
|
11
|
-
# deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
12
|
-
MERGER = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 }
|
13
|
-
|
14
|
-
# Under rails 3.1.1 and higher, perform a check to ensure that the
|
15
|
-
# full environment will be available during asset compilation.
|
16
|
-
# This is required to ensure I18n is loaded.
|
17
|
-
def assert_usable_configuration!
|
18
|
-
@usable_configuration ||= Rails.version >= "3.1.1" &&
|
19
|
-
Rails.configuration.assets.initialize_on_precompile ||
|
20
|
-
raise("Cannot precompile i18n-js translations unless environment is initialized. Please set config.assets.initialize_on_precompile to true.")
|
21
|
-
end
|
22
|
-
|
23
|
-
def has_asset_pipeline?
|
24
|
-
Rails.configuration.respond_to?(:assets) && Rails.configuration.assets.enabled
|
25
|
-
end
|
26
|
-
|
27
|
-
def config_file
|
28
|
-
Rails.root.join("config/i18n-js.yml")
|
29
|
-
end
|
30
|
-
|
31
|
-
def export_dir
|
32
|
-
if has_asset_pipeline?
|
33
|
-
"app/assets/javascripts/i18n"
|
34
|
-
else
|
35
|
-
"public/javascripts"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def javascript_file
|
40
|
-
Rails.root.join(export_dir, "i18n.js")
|
41
|
-
end
|
42
|
-
|
43
|
-
# Export translations to JavaScript, considering settings
|
44
|
-
# from configuration file
|
45
|
-
def export!
|
46
|
-
translation_segments.each do |filename, translations|
|
47
|
-
save(translations, filename)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def segments_per_locale(pattern,scope)
|
52
|
-
::I18n.available_locales.each_with_object({}) do |locale,segments|
|
53
|
-
result = scoped_translations("#{locale}.#{scope}")
|
54
|
-
unless result.empty?
|
55
|
-
segment_name = ::I18n.interpolate(pattern,{:locale => locale})
|
56
|
-
segments[segment_name] = result
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def segment_for_scope(scope)
|
62
|
-
if scope == "*"
|
63
|
-
translations
|
64
|
-
else
|
65
|
-
scoped_translations(scope)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def configured_segments
|
70
|
-
config[:translations].each_with_object({}) do |options,segments|
|
71
|
-
options.reverse_merge!(:only => "*")
|
72
|
-
if options[:file] =~ ::I18n::INTERPOLATION_PATTERN
|
73
|
-
segments.merge!(segments_per_locale(options[:file],options[:only]))
|
74
|
-
else
|
75
|
-
result = segment_for_scope(options[:only])
|
76
|
-
segments[options[:file]] = result unless result.empty?
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def translation_segments
|
82
|
-
if config? && config[:translations]
|
83
|
-
configured_segments
|
84
|
-
else
|
85
|
-
{"#{export_dir}/translations.js" => translations}
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# Load configuration file for partial exporting and
|
90
|
-
# custom output directory
|
91
|
-
def config
|
92
|
-
if config?
|
93
|
-
(YAML.load_file(config_file) || {}).with_indifferent_access
|
94
|
-
else
|
95
|
-
{}
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Check if configuration file exist
|
100
|
-
def config?
|
101
|
-
File.file? config_file
|
102
|
-
end
|
103
|
-
|
104
|
-
# Copy configuration and JavaScript library files to
|
105
|
-
# <tt>config/i18n-js.yml</tt> and <tt>public/javascripts/i18n.js</tt>.
|
106
|
-
def setup!
|
107
|
-
FileUtils.cp(File.dirname(__FILE__) + "/../vendor/assets/javascripts/i18n.js", javascript_file) unless Rails.version >= "3.1"
|
108
|
-
FileUtils.cp(File.dirname(__FILE__) + "/../config/i18n-js.yml", config_file) unless config?
|
109
|
-
end
|
110
|
-
|
111
|
-
# Retrieve an updated JavaScript library from Github.
|
112
|
-
def update!
|
113
|
-
require "open-uri"
|
114
|
-
contents = open("https://raw.github.com/fnando/i18n-js/master/vendor/assets/javascripts/i18n.js").read
|
115
|
-
File.open(javascript_file, "w+") {|f| f << contents}
|
116
|
-
end
|
117
|
-
|
118
|
-
# Convert translations to JSON string and save file.
|
119
|
-
def save(translations, file)
|
120
|
-
file = Rails.root.join(file)
|
121
|
-
FileUtils.mkdir_p File.dirname(file)
|
122
|
-
|
123
|
-
File.open(file, "w+") do |f|
|
124
|
-
f << %(var I18n = I18n || {};\n)
|
125
|
-
f << %(I18n.translations = );
|
126
|
-
f << translations.to_json
|
127
|
-
f << %(;)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def scoped_translations(scopes) # :nodoc:
|
132
|
-
result = {}
|
133
|
-
|
134
|
-
[scopes].flatten.each do |scope|
|
135
|
-
deep_merge! result, filter(translations, scope)
|
136
|
-
end
|
137
|
-
|
138
|
-
result
|
139
|
-
end
|
140
|
-
|
141
|
-
# Filter translations according to the specified scope.
|
142
|
-
def filter(translations, scopes)
|
143
|
-
scopes = scopes.split(".") if scopes.is_a?(String)
|
144
|
-
scopes = scopes.clone
|
145
|
-
scope = scopes.shift
|
146
|
-
|
147
|
-
if scope == "*"
|
148
|
-
results = {}
|
149
|
-
translations.each do |scope, translations|
|
150
|
-
tmp = scopes.empty? ? translations : filter(translations, scopes)
|
151
|
-
results[scope.to_sym] = tmp unless tmp.nil?
|
152
|
-
end
|
153
|
-
return results
|
154
|
-
elsif translations.has_key?(scope.to_sym)
|
155
|
-
return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)}
|
156
|
-
end
|
157
|
-
nil
|
158
|
-
end
|
159
|
-
|
160
|
-
# Initialize and return translations
|
161
|
-
def translations
|
162
|
-
::I18n.backend.instance_eval do
|
163
|
-
init_translations unless initialized?
|
164
|
-
translations
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def deep_merge(target, hash) # :nodoc:
|
169
|
-
target.merge(hash, &MERGER)
|
170
|
-
end
|
171
|
-
|
172
|
-
def deep_merge!(target, hash) # :nodoc:
|
173
|
-
target.merge!(hash, &MERGER)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
1
|
+
require "i18n/js"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module I18n
|
2
|
+
module Js
|
3
|
+
class ConfigGenerator < Rails::Generators::Base
|
4
|
+
# Copied files come from templates folder
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
# Generator desc
|
8
|
+
desc <<-DESC
|
9
|
+
Creates a default i18n-js.yml configuration file in your app's config
|
10
|
+
folder. This file allows you to customize i18n:js:export rake task
|
11
|
+
outputted files.
|
12
|
+
DESC
|
13
|
+
|
14
|
+
def copy_initializer_file
|
15
|
+
copy_file "i18n-js.yml", "config/i18n-js.yml"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# Split context in several files.
|
2
|
+
#
|
2
3
|
# By default only one file with all translations is exported and
|
3
4
|
# no configuration is required. Your settings for asset pipeline
|
4
5
|
# are automatically recognized.
|
@@ -7,16 +8,20 @@
|
|
7
8
|
# locale contexts that will be exported, just use this file to do
|
8
9
|
# so.
|
9
10
|
#
|
11
|
+
# For more informations about the export options with this file, please
|
12
|
+
# refer to the README
|
13
|
+
#
|
14
|
+
#
|
10
15
|
# If you're going to use the Rails 3.1 asset pipeline, change
|
11
16
|
# the following configuration to something like this:
|
12
17
|
#
|
13
|
-
#
|
14
|
-
#
|
18
|
+
# translations:
|
19
|
+
# - file: "app/assets/javascripts/i18n/translations.js"
|
15
20
|
#
|
16
21
|
# If you're running an old version, you can use something
|
17
22
|
# like this:
|
18
23
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
24
|
+
# translations:
|
25
|
+
# - file: "app/assets/javascripts/i18n/translations.js"
|
26
|
+
# only: "*"
|
27
|
+
#
|