i18n 1.9.1 → 1.10.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: bcce939890b82f2f78ef93aa98b2be5995f172f6c37b95c53800c155f2036eec
4
+ data.tar.gz: ae4d72446e698fc7c0666f0e9dad6f11fa669d768fe9272d0c89e14aba8d2274
5
5
  SHA512:
6
- metadata.gz: 4d7d1ac263e739f2b8a66fe50f0e5947150a9401e5e9a8d74a41d4f1ea0ac49b814a1474ab3b1e0e2c37f2e37692c76ab010cf962486709584580fc9ae72f15a
7
- data.tar.gz: 3a87c0bb4b17b09af88afb9b8223df8f75251b9c073073200ed69ab343f8962ef230b63f93ad214243d1262c014ca6f735b1ef25f7c605c02249d66060fb3640
6
+ metadata.gz: bf36e9389526d5a48bbfc4043bc2c4587ddbfbfab41a2ccaafa801570c3a838530a001c6c3df1669d5e0beb14b63b394253cef6d6fdd12aa936ae8c07ecdbf72
7
+ data.tar.gz: 82b972e13f2342ca864e4ead724008aa75ad24bbf6d4f14603330e2999501cfb5c05ee6a9e5cd2cffbf48f07f6da868fccc33b4771f7b4b085989c4def36a7b9
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
@@ -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'
@@ -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
@@ -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,8 +23,7 @@ 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
29
  test "available_locales memoizes when set explicitely" do
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.10.0"
5
5
  end
data/lib/i18n.rb CHANGED
@@ -338,11 +338,11 @@ module I18n
338
338
  def normalize_keys(locale, key, scope, separator = nil)
339
339
  separator ||= I18n.default_separator
340
340
 
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
341
+ [
342
+ *normalize_key(locale, separator),
343
+ *normalize_key(scope, separator),
344
+ *normalize_key(key, separator)
345
+ ]
346
346
  end
347
347
 
348
348
  # Returns true when the passed locale, which can be either a String or a
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.10.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-02-14 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
@@ -106,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
107
  version: 1.3.5
107
108
  requirements: []
108
109
  rubygems_version: 3.1.6
109
- signing_key:
110
+ signing_key:
110
111
  specification_version: 4
111
112
  summary: New wave Internationalization support for Ruby
112
113
  test_files: []