i18n-js 2.1.2 → 3.8.3

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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +24 -0
  3. data/.github/FUNDING.yml +3 -0
  4. data/.github/workflows/tests.yaml +100 -0
  5. data/.gitignore +6 -4
  6. data/.npmignore +27 -0
  7. data/Appraisals +44 -0
  8. data/CHANGELOG.md +539 -0
  9. data/Gemfile +1 -1
  10. data/README.md +1086 -0
  11. data/Rakefile +19 -7
  12. data/app/assets/javascripts/i18n.js +1095 -0
  13. data/app/assets/javascripts/i18n/filtered.js.erb +23 -0
  14. data/app/assets/javascripts/i18n/shims.js +240 -0
  15. data/app/assets/javascripts/i18n/translations.js +3 -0
  16. data/gemfiles/i18n_0_6.gemfile +7 -0
  17. data/gemfiles/i18n_0_7.gemfile +7 -0
  18. data/gemfiles/i18n_0_8.gemfile +7 -0
  19. data/gemfiles/i18n_0_9.gemfile +7 -0
  20. data/gemfiles/i18n_1_0.gemfile +7 -0
  21. data/gemfiles/i18n_1_1.gemfile +7 -0
  22. data/gemfiles/i18n_1_2.gemfile +7 -0
  23. data/gemfiles/i18n_1_3.gemfile +7 -0
  24. data/gemfiles/i18n_1_4.gemfile +7 -0
  25. data/gemfiles/i18n_1_5.gemfile +7 -0
  26. data/gemfiles/i18n_1_6.gemfile +7 -0
  27. data/gemfiles/i18n_1_7.gemfile +7 -0
  28. data/gemfiles/i18n_1_8.gemfile +7 -0
  29. data/i18n-js.gemspec +12 -9
  30. data/i18njs.png +0 -0
  31. data/lib/i18n-js.rb +1 -177
  32. data/lib/i18n/js.rb +264 -0
  33. data/lib/i18n/js/dependencies.rb +63 -0
  34. data/lib/i18n/js/engine.rb +87 -0
  35. data/lib/i18n/js/fallback_locales.rb +70 -0
  36. data/lib/i18n/js/formatters/base.rb +25 -0
  37. data/lib/i18n/js/formatters/js.rb +39 -0
  38. data/lib/i18n/js/formatters/json.rb +13 -0
  39. data/lib/{i18n-js → i18n/js}/middleware.rb +32 -9
  40. data/lib/i18n/js/private/config_store.rb +31 -0
  41. data/lib/i18n/js/private/hash_with_symbol_keys.rb +36 -0
  42. data/lib/i18n/js/segment.rb +80 -0
  43. data/lib/i18n/js/utils.rb +78 -0
  44. data/lib/i18n/js/version.rb +7 -0
  45. data/lib/rails/generators/i18n/js/config/config_generator.rb +19 -0
  46. data/{config → lib/rails/generators/i18n/js/config/templates}/i18n-js.yml +11 -6
  47. data/lib/tasks/export.rake +8 -0
  48. data/package.json +25 -0
  49. data/spec/fixtures/custom_path.yml +5 -0
  50. data/spec/fixtures/default.yml +5 -0
  51. data/spec/fixtures/erb.yml +5 -0
  52. data/spec/fixtures/except_condition.yml +7 -0
  53. data/spec/fixtures/js_export_dir_custom.yml +7 -0
  54. data/spec/fixtures/js_export_dir_none.yml +6 -0
  55. data/spec/fixtures/js_extend_parent.yml +6 -0
  56. data/spec/fixtures/js_extend_segment.yml +6 -0
  57. data/spec/fixtures/js_file_per_locale.yml +7 -0
  58. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml +4 -0
  59. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_hash.yml +6 -0
  60. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale.yml +4 -0
  61. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml +4 -0
  62. data/spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml +4 -0
  63. data/spec/fixtures/js_file_per_locale_without_fallbacks.yml +4 -0
  64. data/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml +9 -0
  65. data/spec/fixtures/js_sort_translation_keys_false.yml +6 -0
  66. data/spec/fixtures/js_sort_translation_keys_true.yml +6 -0
  67. data/spec/fixtures/json_only.yml +18 -0
  68. data/spec/{resources → fixtures}/locales.yml +58 -1
  69. data/spec/fixtures/merge_plurals.yml +6 -0
  70. data/spec/fixtures/merge_plurals_with_no_overrides.yml +4 -0
  71. data/spec/fixtures/merge_plurals_with_partial_overrides.yml +4 -0
  72. data/spec/fixtures/millions.yml +4 -0
  73. data/spec/fixtures/multiple_conditions.yml +7 -0
  74. data/spec/fixtures/multiple_conditions_per_locale.yml +7 -0
  75. data/spec/fixtures/multiple_files.yml +7 -0
  76. data/spec/{resources → fixtures}/no_config.yml +0 -0
  77. data/spec/fixtures/no_scope.yml +4 -0
  78. data/spec/fixtures/simple_scope.yml +5 -0
  79. data/spec/js/currency.spec.js +62 -0
  80. data/spec/js/current_locale.spec.js +19 -0
  81. data/spec/js/dates.spec.js +276 -0
  82. data/spec/js/defaults.spec.js +31 -0
  83. data/spec/js/extend.spec.js +110 -0
  84. data/spec/js/interpolation.spec.js +124 -0
  85. data/spec/js/jasmine/MIT.LICENSE +20 -0
  86. data/spec/js/jasmine/jasmine-html.js +190 -0
  87. data/spec/js/jasmine/jasmine.css +166 -0
  88. data/spec/js/jasmine/jasmine.js +2476 -0
  89. data/spec/js/jasmine/jasmine_favicon.png +0 -0
  90. data/spec/js/json_parsable.spec.js +14 -0
  91. data/spec/js/locales.spec.js +31 -0
  92. data/spec/js/localization.spec.js +78 -0
  93. data/spec/js/numbers.spec.js +174 -0
  94. data/spec/js/placeholder.spec.js +24 -0
  95. data/spec/js/pluralization.spec.js +219 -0
  96. data/spec/js/prepare_options.spec.js +41 -0
  97. data/spec/js/require.js +2083 -0
  98. data/spec/js/specs.html +49 -0
  99. data/spec/js/specs_requirejs.html +72 -0
  100. data/spec/js/translate.spec.js +304 -0
  101. data/spec/js/translations.js +188 -0
  102. data/spec/js/utility_functions.spec.js +20 -0
  103. data/spec/ruby/i18n/js/fallback_locales_spec.rb +84 -0
  104. data/spec/ruby/i18n/js/segment_spec.rb +261 -0
  105. data/spec/ruby/i18n/js/utils_spec.rb +106 -0
  106. data/spec/ruby/i18n/js_spec.rb +748 -0
  107. data/spec/spec_helper.rb +75 -14
  108. data/yarn.lock +131 -0
  109. metadata +223 -98
  110. data/.rspec +0 -1
  111. data/Gemfile.lock +0 -51
  112. data/README.rdoc +0 -305
  113. data/lib/i18n-js/engine.rb +0 -62
  114. data/lib/i18n-js/railtie.rb +0 -13
  115. data/lib/i18n-js/rake.rb +0 -16
  116. data/lib/i18n-js/version.rb +0 -10
  117. data/spec/i18n_spec.js +0 -768
  118. data/spec/i18n_spec.rb +0 -205
  119. data/spec/resources/custom_path.yml +0 -4
  120. data/spec/resources/default.yml +0 -4
  121. data/spec/resources/js_file_per_locale.yml +0 -3
  122. data/spec/resources/multiple_conditions.yml +0 -6
  123. data/spec/resources/multiple_files.yml +0 -6
  124. data/spec/resources/no_scope.yml +0 -3
  125. data/spec/resources/simple_scope.yml +0 -4
  126. data/vendor/assets/javascripts/i18n.js +0 -450
  127. data/vendor/assets/javascripts/i18n/translations.js.erb +0 -7
data/lib/i18n/js.rb ADDED
@@ -0,0 +1,264 @@
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
+ I18n.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.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
+ def self.sort_translation_keys?
217
+ @sort_translation_keys ||= (config[:sort_translation_keys]) if config.key?(:sort_translation_keys)
218
+ @sort_translation_keys = true if @sort_translation_keys.nil?
219
+ @sort_translation_keys
220
+ end
221
+
222
+ def self.sort_translation_keys=(value)
223
+ @sort_translation_keys = !!value
224
+ end
225
+
226
+ def self.extract_segment_options(options)
227
+ segment_options = Private::HashWithSymbolKeys.new({
228
+ js_extend: js_extend,
229
+ sort_translation_keys: sort_translation_keys?,
230
+ json_only: json_only
231
+ }).freeze
232
+ segment_options.merge(options.slice(*Segment::OPTIONS))
233
+ end
234
+
235
+ ### Export i18n.js
236
+ begin
237
+
238
+ # Copy i18n.js
239
+ def self.export_i18n_js
240
+ return unless export_i18n_js_dir_path.is_a? String
241
+
242
+ FileUtils.mkdir_p(export_i18n_js_dir_path)
243
+
244
+ i18n_js_path = File.expand_path('../../../app/assets/javascripts/i18n.js', __FILE__)
245
+ destination_path = File.expand_path("i18n.js", export_i18n_js_dir_path)
246
+ return if File.exist?(destination_path) && FileUtils.identical?(i18n_js_path, destination_path)
247
+
248
+ FileUtils.cp(i18n_js_path, export_i18n_js_dir_path)
249
+ end
250
+
251
+ def self.export_i18n_js_dir_path
252
+ @export_i18n_js_dir_path ||= (config[:export_i18n_js] || :none) if config.key?(:export_i18n_js)
253
+ @export_i18n_js_dir_path ||= DEFAULT_EXPORT_DIR_PATH
254
+ @export_i18n_js_dir_path
255
+ end
256
+
257
+ # Setting this to nil would disable i18n.js exporting
258
+ def self.export_i18n_js_dir_path=(new_path)
259
+ new_path = :none unless new_path.is_a? String
260
+ @export_i18n_js_dir_path = new_path
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,63 @@
1
+ module I18n
2
+ module JS
3
+ # When using `safe_gem_check` to check for a pre-release version of gem,
4
+ # we need to specify pre-release version suffix in version constraint
5
+ module Dependencies
6
+ class << self
7
+ def rails?
8
+ defined?(Rails) && Rails.respond_to?(:version)
9
+ end
10
+
11
+ def sprockets_rails_v2_plus?
12
+ safe_gem_check("sprockets-rails", ">= 2")
13
+ end
14
+
15
+ # This cannot be called at class definition time
16
+ # Since not all libraries are loaded
17
+ #
18
+ # Call this in an initializer
19
+ def using_asset_pipeline?
20
+ assets_pipeline_available =
21
+ (rails3? || rails4? || rails5? || rails6?) &&
22
+ Rails.respond_to?(:application) &&
23
+ Rails.application.config.respond_to?(:assets)
24
+ rails3_assets_enabled =
25
+ rails3? &&
26
+ assets_pipeline_available &&
27
+ Rails.application.config.assets.enabled != false
28
+
29
+ assets_pipeline_available && (rails4? || rails5? || rails6? || rails3_assets_enabled)
30
+ end
31
+
32
+ private
33
+
34
+ def rails3?
35
+ rails? && Rails.version.to_i == 3
36
+ end
37
+
38
+ def rails4?
39
+ rails? && Rails.version.to_i == 4
40
+ end
41
+
42
+ def rails5?
43
+ rails? && Rails.version.to_i == 5
44
+ end
45
+
46
+ def rails6?
47
+ rails? && Rails.version.to_i == 6
48
+ end
49
+
50
+ def safe_gem_check(*args)
51
+ if Gem::Specification.respond_to?(:find_by_name)
52
+ Gem::Specification.find_by_name(*args)
53
+ elsif Gem.respond_to?(:available?)
54
+ Gem.available?(*args)
55
+ end
56
+ rescue Gem::LoadError
57
+ false
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,87 @@
1
+ require "i18n/js"
2
+
3
+ module I18n
4
+ module JS
5
+ # @api private
6
+ # The class cannot be private
7
+ class SprocketsExtension
8
+ # Actual definition is placed below
9
+ end
10
+
11
+ class Engine < ::Rails::Engine
12
+ # See https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors
13
+ # for reference of supporting multiple versions
14
+
15
+ # `sprockets.environment` was used for 1.x of `sprockets-rails`
16
+ # https://github.com/rails/sprockets-rails/issues/227
17
+ #
18
+ # References for current values:
19
+ #
20
+ # Here is where sprockets are attached with Rails. There is no 'sprockets.environment' mentioned.
21
+ # https://github.com/rails/sprockets-rails/blob/master/lib/sprockets/railtie.rb
22
+ #
23
+ # Finisher hook is the place which should be used as border.
24
+ # http://guides.rubyonrails.org/configuring.html#initializers
25
+ #
26
+ # For detail see Pull Request:
27
+ # https://github.com/fnando/i18n-js/pull/371
28
+ initializer "i18n-js.register_preprocessor", after: :engines_blank_point, before: :finisher_hook do
29
+ # This must be called inside initializer block
30
+ # For details see comments for `using_asset_pipeline?`
31
+ next unless JS::Dependencies.using_asset_pipeline?
32
+
33
+ # From README of 2.x & 3.x of `sprockets-rails`
34
+ # It seems the `configure` block is preferred way to call `register_preprocessor`
35
+ # Not sure if this will break older versions of rails
36
+ #
37
+ # https://github.com/rails/sprockets-rails/blob/v2.3.3/README.md
38
+ # https://github.com/rails/sprockets-rails/blob/v3.0.0/README.md
39
+ if JS::Dependencies.sprockets_rails_v2_plus?
40
+ Rails.application.config.assets.configure do |config|
41
+ config.register_preprocessor("application/javascript", ::I18n::JS::SprocketsExtension)
42
+ end
43
+ elsif Rails.application.assets.respond_to?(:register_preprocessor)
44
+ Rails.application.assets.register_preprocessor("application/javascript", ::I18n::JS::SprocketsExtension)
45
+ end
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ class SprocketsExtension
51
+ def initialize(filename, &block)
52
+ @filename = filename
53
+ @source = block.call
54
+ end
55
+
56
+ def render(context, empty_hash_wtf)
57
+ self.class.run(@filename, @source, context)
58
+ end
59
+
60
+ def self.run(filename, source, context)
61
+ if context.logical_path == "i18n/filtered"
62
+ ::I18n.load_path.each { |path| context.depend_on(File.expand_path(path)) }
63
+
64
+ # Absolute path is required or
65
+ # Sprockets assumes it's a logical path
66
+ #
67
+ # Calling `depend on` with an absent file
68
+ # will invoke `resolve` and will throw an error in the end
69
+ if I18n::JS.config_file_exists?
70
+ context.depend_on(File.expand_path(I18n::JS.config_file_path))
71
+ end
72
+ end
73
+
74
+ source
75
+ end
76
+
77
+ def self.call(input)
78
+ filename = input[:filename]
79
+ source = input[:data]
80
+ context = input[:environment].context_class.new(input)
81
+
82
+ result = run(filename, source, context)
83
+ context.metadata.merge(data: result)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,70 @@
1
+ module I18n
2
+ module JS
3
+ class FallbackLocales
4
+ attr_reader :fallbacks, :locale
5
+
6
+ def initialize(fallbacks, locale)
7
+ @fallbacks = fallbacks
8
+ @locale = locale
9
+ end
10
+
11
+ def each
12
+ locales.each { |locale| yield(locale) }
13
+ end
14
+
15
+ # @return [Array<String, Symbol>]
16
+ # An Array of locales to use as fallbacks for given locale.
17
+ def locales
18
+ locales = case fallbacks
19
+ when true
20
+ default_fallbacks
21
+ when :default_locale
22
+ [::I18n.default_locale]
23
+ when Symbol, String
24
+ [fallbacks.to_sym]
25
+ when Array
26
+ ensure_valid_fallbacks_as_array!
27
+ fallbacks
28
+ when Hash
29
+ Array(fallbacks[locale] || default_fallbacks)
30
+ else
31
+ fail ArgumentError, "fallbacks must be: true, :default_locale an Array or a Hash - given: #{fallbacks}"
32
+ end
33
+
34
+ locales.map! { |locale| locale.to_sym }
35
+ locales
36
+ end
37
+
38
+ private
39
+
40
+ # @return [Array<String, Symbol>] An Array of locales.
41
+ def default_fallbacks
42
+ if using_i18n_fallbacks_module?
43
+ I18n.fallbacks[locale]
44
+ else
45
+ [::I18n.default_locale]
46
+ end
47
+ end
48
+
49
+ # @return
50
+ # true if we can safely use I18n.fallbacks, false otherwise.
51
+ #
52
+ # @note
53
+ # We should implement this as `I18n.respond_to?(:fallbacks)`, but
54
+ # once I18n::Backend::Fallbacks is included, I18n will _always_
55
+ # respond to :fallbacks. Even if we switch the backend to one
56
+ # without fallbacks!
57
+ #
58
+ # Maybe this should be fixed within I18n.
59
+ def using_i18n_fallbacks_module?
60
+ I18n::JS.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
61
+ end
62
+
63
+ def ensure_valid_fallbacks_as_array!
64
+ return if fallbacks.all? { |e| e.is_a?(String) || e.is_a?(Symbol) }
65
+
66
+ fail ArgumentError, "If fallbacks is passed as Array, it must ony include Strings or Symbols. Given: #{fallbacks}"
67
+ end
68
+ end
69
+ end
70
+ end