i18n 1.9.1 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73eb29528a85ee9bb80e81091573b181a768e7eca83afba1222884afbdc81928
4
- data.tar.gz: a807e1a214d3195204fa37b7bc0ac57d7ac8125889405f943c07901b4e2b3972
3
+ metadata.gz: b92d195deaeca5e93f73cc62be2d2fb4c93d8a9787449e168b493773e5072458
4
+ data.tar.gz: f1172c9fac93f493c8a5b16cff4b27154ff917aed48cbc46a4a702363863111d
5
5
  SHA512:
6
- metadata.gz: 4d7d1ac263e739f2b8a66fe50f0e5947150a9401e5e9a8d74a41d4f1ea0ac49b814a1474ab3b1e0e2c37f2e37692c76ab010cf962486709584580fc9ae72f15a
7
- data.tar.gz: 3a87c0bb4b17b09af88afb9b8223df8f75251b9c073073200ed69ab343f8962ef230b63f93ad214243d1262c014ca6f735b1ef25f7c605c02249d66060fb3640
6
+ metadata.gz: 0174d10f7bac17e29cf7622d3ce69850e51755d53fd3d17d2f672bc9ca3c75823b851f6d04cbd367fea8ff5830c8c755fb9c80433bac9ec3c46590cbe9a874d0
7
+ data.tar.gz: ef22ed98cb7f223753a65b9d63d0f40e4b6821c93a41b1b41627e4c7b4e3de6b7bd411d8a6ed3d46dc6554432a776535c0461248e8d1fbfeee05dcda2ab89235
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Ruby I18n
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/i18n.svg)](https://badge.fury.io/rb/i18n)
3
4
  [![Build Status](https://github.com/ruby-i18n/i18n/workflows/Ruby/badge.svg)](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby)
4
5
 
5
6
  Ruby internationalization and localization (i18n) solution.
@@ -13,7 +13,10 @@ module I18n
13
13
  # for details.
14
14
  def load_translations(*filenames)
15
15
  filenames = I18n.load_path if filenames.empty?
16
- filenames.flatten.each { |filename| load_file(filename) }
16
+ filenames.flatten.each do |filename|
17
+ loaded_translations = load_file(filename)
18
+ yield filename, loaded_translations if block_given?
19
+ end
17
20
  end
18
21
 
19
22
  # This method receives a locale, a data hash and options for storing translations.
@@ -32,7 +35,7 @@ module I18n
32
35
  if entry.nil? && options.key?(:default)
33
36
  entry = default(locale, key, options[:default], options)
34
37
  else
35
- entry = resolve(locale, key, entry, options)
38
+ entry = resolve_entry(locale, key, entry, options)
36
39
  end
37
40
 
38
41
  count = options[:count]
@@ -151,6 +154,7 @@ module I18n
151
154
  end
152
155
  result unless result.is_a?(MissingTranslation)
153
156
  end
157
+ alias_method :resolve_entry, :resolve
154
158
 
155
159
  # Picks a translation from a pluralized mnemonic subkey according to English
156
160
  # pluralization rules :
@@ -226,6 +230,8 @@ module I18n
226
230
  raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
227
231
  end
228
232
  data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) }
233
+
234
+ data
229
235
  end
230
236
 
231
237
  # Loads a plain Ruby translations file. eval'ing the file must yield
@@ -64,13 +64,20 @@ module I18n
64
64
  throw(:exception, I18n::MissingTranslation.new(locale, key, options))
65
65
  end
66
66
 
67
- def resolve(locale, object, subject, options = EMPTY_HASH)
67
+ def resolve_entry(locale, object, subject, options = EMPTY_HASH)
68
68
  return subject if options[:resolve] == false
69
- return super unless subject.is_a?(Symbol)
70
-
71
69
  result = catch(:exception) do
72
- options.delete(:fallback_in_progress)
73
- I18n.translate(subject, **options.merge(locale: options[:fallback_original_locale], throw: true))
70
+ options.delete(:fallback_in_progress) if options.key?(:fallback_in_progress)
71
+
72
+ case subject
73
+ when Symbol
74
+ I18n.translate(subject, **options.merge(:locale => options[:fallback_original_locale], :throw => true))
75
+ when Proc
76
+ date_or_time = options.delete(:object) || object
77
+ resolve_entry(options[:fallback_original_locale], object, subject.call(date_or_time, **options))
78
+ else
79
+ subject
80
+ end
74
81
  end
75
82
  result unless result.is_a?(MissingTranslation)
76
83
  end
@@ -100,7 +107,7 @@ module I18n
100
107
  private
101
108
 
102
109
  # Overwrite on_fallback to add specified logic when the fallback succeeds.
103
- def on_fallback(_original_locale, _fallback_locale, _key, _optoins)
110
+ def on_fallback(_original_locale, _fallback_locale, _key, _options)
104
111
  nil
105
112
  end
106
113
  end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n
4
+ module Backend
5
+ # Backend that lazy loads translations based on the current locale. This
6
+ # implementation avoids loading all translations up front. Instead, it only
7
+ # loads the translations that belong to the current locale. This offers a
8
+ # performance incentive in local development and test environments for
9
+ # applications with many translations for many different locales. It's
10
+ # particularly useful when the application only refers to a single locales'
11
+ # translations at a time (ex. A Rails workload). The implementation
12
+ # identifies which translation files from the load path belong to the
13
+ # current locale by pattern matching against their path name.
14
+ #
15
+ # Specifically, a translation file is considered to belong to a locale if:
16
+ # a) the filename is in the I18n load path
17
+ # b) the filename ends in a supported extension (ie. .yml, .json, .po, .rb)
18
+ # c) the filename starts with the locale identifier
19
+ # d) the locale identifier and optional proceeding text is separated by an underscore, ie. "_".
20
+ #
21
+ # Examples:
22
+ # Valid files that will be selected by this backend:
23
+ #
24
+ # "files/locales/en_translation.yml" (Selected for locale "en")
25
+ # "files/locales/fr.po" (Selected for locale "fr")
26
+ #
27
+ # Invalid files that won't be selected by this backend:
28
+ #
29
+ # "files/locales/translation-file"
30
+ # "files/locales/en-translation.unsupported"
31
+ # "files/locales/french/translation.yml"
32
+ # "files/locales/fr/translation.yml"
33
+ #
34
+ # The implementation uses this assumption to defer the loading of
35
+ # translation files until the current locale actually requires them.
36
+ #
37
+ # The backend has two working modes: lazy_load and eager_load.
38
+ #
39
+ # Note: This backend should only be enabled in test environments!
40
+ # When the mode is set to false, the backend behaves exactly like the
41
+ # Simple backend, with an additional check that the paths being loaded
42
+ # abide by the format. If paths can't be matched to the format, an error is raised.
43
+ #
44
+ # You can configure lazy loaded backends through the initializer or backends
45
+ # accessor:
46
+ #
47
+ # # In test environments
48
+ #
49
+ # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: true)
50
+ #
51
+ # # In other environments, such as production and CI
52
+ #
53
+ # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: false) # default
54
+ #
55
+ class LocaleExtractor
56
+ class << self
57
+ def locale_from_path(path)
58
+ name = File.basename(path, ".*")
59
+ locale = name.split("_").first
60
+ locale.to_sym unless locale.nil?
61
+ end
62
+ end
63
+ end
64
+
65
+ class LazyLoadable < Simple
66
+ def initialize(lazy_load: false)
67
+ @lazy_load = lazy_load
68
+ end
69
+
70
+ # Returns whether the current locale is initialized.
71
+ def initialized?
72
+ if lazy_load?
73
+ initialized_locales[I18n.locale]
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ # Clean up translations and uninitialize all locales.
80
+ def reload!
81
+ if lazy_load?
82
+ @initialized_locales = nil
83
+ @translations = nil
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ # Eager loading is not supported in the lazy context.
90
+ def eager_load!
91
+ if lazy_load?
92
+ raise UnsupportedMethod.new(__method__, self.class, "Cannot eager load translations because backend was configured with lazy_load: true.")
93
+ else
94
+ super
95
+ end
96
+ end
97
+
98
+ # Parse the load path and extract all locales.
99
+ def available_locales
100
+ if lazy_load?
101
+ I18n.load_path.map { |path| LocaleExtractor.locale_from_path(path) }
102
+ else
103
+ super
104
+ end
105
+ end
106
+
107
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
108
+ if lazy_load?
109
+ I18n.with_locale(locale) do
110
+ super
111
+ end
112
+ else
113
+ super
114
+ end
115
+ end
116
+
117
+ protected
118
+
119
+
120
+ # Load translations from files that belong to the current locale.
121
+ def init_translations
122
+ file_errors = if lazy_load?
123
+ initialized_locales[I18n.locale] = true
124
+ load_translations_and_collect_file_errors(filenames_for_current_locale)
125
+ else
126
+ @initialized = true
127
+ load_translations_and_collect_file_errors(I18n.load_path)
128
+ end
129
+
130
+ raise InvalidFilenames.new(file_errors) unless file_errors.empty?
131
+ end
132
+
133
+ def initialized_locales
134
+ @initialized_locales ||= Hash.new(false)
135
+ end
136
+
137
+ private
138
+
139
+ def lazy_load?
140
+ @lazy_load
141
+ end
142
+
143
+ class FilenameIncorrect < StandardError
144
+ def initialize(file, expected_locale, unexpected_locales)
145
+ super "#{file} can only load translations for \"#{expected_locale}\". Found translations for: #{unexpected_locales}."
146
+ end
147
+ end
148
+
149
+ # Loads each file supplied and asserts that the file only loads
150
+ # translations as expected by the name. The method returns a list of
151
+ # errors corresponding to offending files.
152
+ def load_translations_and_collect_file_errors(files)
153
+ errors = []
154
+
155
+ load_translations(files) do |file, loaded_translations|
156
+ assert_file_named_correctly!(file, loaded_translations)
157
+ rescue FilenameIncorrect => e
158
+ errors << e
159
+ end
160
+
161
+ errors
162
+ end
163
+
164
+ # Select all files from I18n load path that belong to current locale.
165
+ # These files must start with the locale identifier (ie. "en", "pt-BR"),
166
+ # followed by an "_" demarcation to separate proceeding text.
167
+ def filenames_for_current_locale
168
+ I18n.load_path.flatten.select do |path|
169
+ LocaleExtractor.locale_from_path(path) == I18n.locale
170
+ end
171
+ end
172
+
173
+ # Checks if a filename is named in correspondence to the translations it loaded.
174
+ # The locale extracted from the path must be the single locale loaded in the translations.
175
+ def assert_file_named_correctly!(file, translations)
176
+ loaded_locales = translations.keys.map(&:to_sym)
177
+ expected_locale = LocaleExtractor.locale_from_path(file)
178
+ unexpected_locales = loaded_locales.reject { |locale| locale == expected_locale }
179
+
180
+ raise FilenameIncorrect.new(file, expected_locale, unexpected_locales) unless unexpected_locales.empty?
181
+ end
182
+ end
183
+ end
184
+ end
@@ -94,7 +94,7 @@ module I18n
94
94
  return nil unless result.has_key?(_key)
95
95
  end
96
96
  result = result[_key]
97
- result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
97
+ result = resolve_entry(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
98
98
  result
99
99
  end
100
100
  end
data/lib/i18n/backend.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  module I18n
4
4
  module Backend
5
5
  autoload :Base, 'i18n/backend/base'
6
- autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
7
6
  autoload :Cache, 'i18n/backend/cache'
8
7
  autoload :CacheFile, 'i18n/backend/cache_file'
9
8
  autoload :Cascade, 'i18n/backend/cascade'
@@ -11,7 +10,9 @@ module I18n
11
10
  autoload :Fallbacks, 'i18n/backend/fallbacks'
12
11
  autoload :Flatten, 'i18n/backend/flatten'
13
12
  autoload :Gettext, 'i18n/backend/gettext'
13
+ autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
14
14
  autoload :KeyValue, 'i18n/backend/key_value'
15
+ autoload :LazyLoadable, 'i18n/backend/lazy_loadable'
15
16
  autoload :Memoize, 'i18n/backend/memoize'
16
17
  autoload :Metadata, 'i18n/backend/metadata'
17
18
  autoload :Pluralization, 'i18n/backend/pluralization'
data/lib/i18n/config.rb CHANGED
@@ -38,7 +38,7 @@ module I18n
38
38
  end
39
39
 
40
40
  # Returns an array of locales for which translations are available.
41
- # Unless you explicitely set these through I18n.available_locales=
41
+ # Unless you explicitly set these through I18n.available_locales=
42
42
  # the call will be delegated to the backend.
43
43
  def available_locales
44
44
  @@available_locales ||= nil
@@ -106,7 +106,7 @@ module I18n
106
106
  # if you don't care about arity.
107
107
  #
108
108
  # == Example:
109
- # You can supress raising an exception and return string instead:
109
+ # You can suppress raising an exception and return string instead:
110
110
  #
111
111
  # I18n.config.missing_interpolation_argument_handler = Proc.new do |key|
112
112
  # "#{key} is missing"
@@ -24,7 +24,7 @@ module I18n
24
24
  been set is likely to display text from the wrong locale to some users.
25
25
 
26
26
  If you have a legitimate reason to access i18n data outside of the user flow, you can do so by passing
27
- the desired locale explictly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)`
27
+ the desired locale explicitly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)`
28
28
  MESSAGE
29
29
  end
30
30
  end
@@ -110,4 +110,38 @@ module I18n
110
110
  super "can not load translations from #{filename}, the file type #{type} is not known"
111
111
  end
112
112
  end
113
+
114
+ class UnsupportedMethod < ArgumentError
115
+ attr_reader :method, :backend_klass, :msg
116
+ def initialize(method, backend_klass, msg)
117
+ @method = method
118
+ @backend_klass = backend_klass
119
+ @msg = msg
120
+ super "#{backend_klass} does not support the ##{method} method. #{msg}"
121
+ end
122
+ end
123
+
124
+ class InvalidFilenames < ArgumentError
125
+ NUMBER_OF_ERRORS_SHOWN = 20
126
+ def initialize(file_errors)
127
+ super <<~MSG
128
+ Found #{file_errors.count} error(s).
129
+ The first #{[file_errors.count, NUMBER_OF_ERRORS_SHOWN].min} error(s):
130
+ #{file_errors.map(&:message).first(NUMBER_OF_ERRORS_SHOWN).join("\n")}
131
+
132
+ To use the LazyLoadable backend:
133
+ 1. Filenames must start with the locale.
134
+ 2. An underscore must separate the locale with any optional text that follows.
135
+ 3. The file must only contain translation data for the single locale.
136
+
137
+ Example:
138
+ "/config/locales/fr.yml" which contains:
139
+ ```yml
140
+ fr:
141
+ dog:
142
+ chien
143
+ ```
144
+ MSG
145
+ end
146
+ end
113
147
  end
@@ -1,5 +1,5 @@
1
1
  # Simple Locale tag implementation that computes subtags by simply splitting
2
- # the locale tag at '-' occurences.
2
+ # the locale tag at '-' occurrences.
3
3
  module I18n
4
4
  module Locale
5
5
  module Tag
@@ -5,12 +5,11 @@ module I18n
5
5
  I18n.available_locales = nil
6
6
  end
7
7
 
8
- test "available_locales returns the locales stored to the backend by default" do
8
+ test "available_locales returns the available_locales produced by the backend, by default" do
9
9
  I18n.backend.store_translations('de', :foo => 'bar')
10
10
  I18n.backend.store_translations('en', :foo => 'foo')
11
11
 
12
- assert I18n.available_locales.include?(:de)
13
- assert I18n.available_locales.include?(:en)
12
+ assert_equal I18n.available_locales, I18n.backend.available_locales
14
13
  end
15
14
 
16
15
  test "available_locales can be set to something else independently from the actual locale data" do
@@ -24,11 +23,10 @@ module I18n
24
23
  assert_equal [:foo, :bar], I18n.available_locales
25
24
 
26
25
  I18n.available_locales = nil
27
- assert I18n.available_locales.include?(:de)
28
- assert I18n.available_locales.include?(:en)
26
+ assert_equal I18n.available_locales, I18n.backend.available_locales
29
27
  end
30
28
 
31
- test "available_locales memoizes when set explicitely" do
29
+ test "available_locales memoizes when set explicitly" do
32
30
  I18n.backend.expects(:available_locales).never
33
31
  I18n.available_locales = [:foo]
34
32
  I18n.backend.store_translations('de', :bar => 'baz')
@@ -36,7 +34,7 @@ module I18n
36
34
  assert_equal [:foo], I18n.available_locales
37
35
  end
38
36
 
39
- test "available_locales delegates to the backend when not set explicitely" do
37
+ test "available_locales delegates to the backend when not set explicitly" do
40
38
  original_available_locales_value = I18n.backend.available_locales
41
39
  I18n.backend.expects(:available_locales).returns(original_available_locales_value).twice
42
40
  assert_equal I18n.backend.available_locales, I18n.available_locales
data/lib/i18n/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18n
4
- VERSION = "1.9.1"
4
+ VERSION = "1.12.0"
5
5
  end
data/lib/i18n.rb CHANGED
@@ -214,18 +214,12 @@ module I18n
214
214
 
215
215
  backend = config.backend
216
216
 
217
- result = catch(:exception) do
218
- if key.is_a?(Array)
219
- key.map { |k| backend.translate(locale, k, options) }
220
- else
221
- backend.translate(locale, key, options)
217
+ if key.is_a?(Array)
218
+ key.map do |k|
219
+ translate_key(k, throw, raise, locale, backend, options)
222
220
  end
223
- end
224
-
225
- if result.is_a?(MissingTranslation)
226
- handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
227
221
  else
228
- result
222
+ translate_key(key, throw, raise, locale, backend, options)
229
223
  end
230
224
  end
231
225
  alias :t :translate
@@ -338,11 +332,11 @@ module I18n
338
332
  def normalize_keys(locale, key, scope, separator = nil)
339
333
  separator ||= I18n.default_separator
340
334
 
341
- keys = []
342
- keys.concat normalize_key(locale, separator)
343
- keys.concat normalize_key(scope, separator)
344
- keys.concat normalize_key(key, separator)
345
- keys
335
+ [
336
+ *normalize_key(locale, separator),
337
+ *normalize_key(scope, separator),
338
+ *normalize_key(key, separator)
339
+ ]
346
340
  end
347
341
 
348
342
  # Returns true when the passed locale, which can be either a String or a
@@ -364,6 +358,18 @@ module I18n
364
358
 
365
359
  private
366
360
 
361
+ def translate_key(key, throw, raise, locale, backend, options)
362
+ result = catch(:exception) do
363
+ backend.translate(locale, key, options)
364
+ end
365
+
366
+ if result.is_a?(MissingTranslation)
367
+ handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
368
+ else
369
+ result
370
+ end
371
+ end
372
+
367
373
  # Any exceptions thrown in translate will be sent to the @@exception_handler
368
374
  # which can be a Symbol, a Proc or any other Object unless they're forced to
369
375
  # be raised or thrown (MissingTranslation).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.1
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -10,10 +10,10 @@ authors:
10
10
  - Stephan Soller
11
11
  - Saimon Moore
12
12
  - Ryan Bigg
13
- autorequire:
13
+ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2022-01-27 00:00:00.000000000 Z
16
+ date: 2022-07-13 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: concurrent-ruby
@@ -49,6 +49,7 @@ files:
49
49
  - lib/i18n/backend/gettext.rb
50
50
  - lib/i18n/backend/interpolation_compiler.rb
51
51
  - lib/i18n/backend/key_value.rb
52
+ - lib/i18n/backend/lazy_loadable.rb
52
53
  - lib/i18n/backend/memoize.rb
53
54
  - lib/i18n/backend/metadata.rb
54
55
  - lib/i18n/backend/pluralization.rb
@@ -90,7 +91,7 @@ metadata:
90
91
  changelog_uri: https://github.com/ruby-i18n/i18n/releases
91
92
  documentation_uri: https://guides.rubyonrails.org/i18n.html
92
93
  source_code_uri: https://github.com/ruby-i18n/i18n
93
- post_install_message:
94
+ post_install_message:
94
95
  rdoc_options: []
95
96
  require_paths:
96
97
  - lib
@@ -105,8 +106,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  - !ruby/object:Gem::Version
106
107
  version: 1.3.5
107
108
  requirements: []
108
- rubygems_version: 3.1.6
109
- signing_key:
109
+ rubygems_version: 3.3.16
110
+ signing_key:
110
111
  specification_version: 4
111
112
  summary: New wave Internationalization support for Ruby
112
113
  test_files: []