i18n 1.8.7 → 1.12.0
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 +4 -4
- data/README.md +1 -0
- data/lib/i18n/backend/base.rb +24 -10
- data/lib/i18n/backend/cache.rb +3 -3
- data/lib/i18n/backend/cache_file.rb +2 -2
- data/lib/i18n/backend/chain.rb +2 -4
- data/lib/i18n/backend/fallbacks.rb +23 -3
- data/lib/i18n/backend/gettext.rb +2 -4
- data/lib/i18n/backend/key_value.rb +4 -6
- data/lib/i18n/backend/lazy_loadable.rb +184 -0
- data/lib/i18n/backend/simple.rb +6 -9
- data/lib/i18n/backend.rb +2 -1
- data/lib/i18n/config.rb +2 -2
- data/lib/i18n/exceptions.rb +38 -2
- data/lib/i18n/interpolate/ruby.rb +2 -2
- data/lib/i18n/locale/fallbacks.rb +13 -15
- data/lib/i18n/locale/tag/simple.rb +1 -1
- data/lib/i18n/tests/basics.rb +5 -7
- data/lib/i18n/tests/defaults.rb +1 -1
- data/lib/i18n/tests/interpolation.rb +7 -7
- data/lib/i18n/tests/localization/date.rb +3 -3
- data/lib/i18n/tests/localization/date_time.rb +1 -1
- data/lib/i18n/tests/localization/procs.rb +1 -0
- data/lib/i18n/tests/localization/time.rb +1 -1
- data/lib/i18n/tests/lookup.rb +2 -2
- data/lib/i18n/tests/pluralization.rb +1 -1
- data/lib/i18n/tests/procs.rb +7 -1
- data/lib/i18n/utils.rb +55 -0
- data/lib/i18n/version.rb +1 -1
- data/lib/i18n.rb +48 -27
- metadata +6 -22
- data/lib/i18n/core_ext/hash.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b92d195deaeca5e93f73cc62be2d2fb4c93d8a9787449e168b493773e5072458
|
|
4
|
+
data.tar.gz: f1172c9fac93f493c8a5b16cff4b27154ff917aed48cbc46a4a702363863111d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0174d10f7bac17e29cf7622d3ce69850e51755d53fd3d17d2f672bc9ca3c75823b851f6d04cbd367fea8ff5830c8c755fb9c80433bac9ec3c46590cbe9a874d0
|
|
7
|
+
data.tar.gz: ef22ed98cb7f223753a65b9d63d0f40e4b6821c93a41b1b41627e4c7b4e3de6b7bd411d8a6ed3d46dc6554432a776535c0461248e8d1fbfeee05dcda2ab89235
|
data/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Ruby I18n
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/i18n)
|
|
3
4
|
[](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby)
|
|
4
5
|
|
|
5
6
|
Ruby internationalization and localization (i18n) solution.
|
data/lib/i18n/backend/base.rb
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require 'yaml'
|
|
4
4
|
require 'json'
|
|
5
|
-
require 'i18n/core_ext/hash'
|
|
6
5
|
|
|
7
6
|
module I18n
|
|
8
7
|
module Backend
|
|
9
8
|
module Base
|
|
10
|
-
using I18n::HashRefinements
|
|
11
9
|
include I18n::Backend::Transliterator
|
|
12
10
|
|
|
13
11
|
# Accepts a list of paths to translation files. Loads translations from
|
|
@@ -15,7 +13,10 @@ module I18n
|
|
|
15
13
|
# for details.
|
|
16
14
|
def load_translations(*filenames)
|
|
17
15
|
filenames = I18n.load_path if filenames.empty?
|
|
18
|
-
filenames.flatten.each
|
|
16
|
+
filenames.flatten.each do |filename|
|
|
17
|
+
loaded_translations = load_file(filename)
|
|
18
|
+
yield filename, loaded_translations if block_given?
|
|
19
|
+
end
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
# This method receives a locale, a data hash and options for storing translations.
|
|
@@ -34,7 +35,7 @@ module I18n
|
|
|
34
35
|
if entry.nil? && options.key?(:default)
|
|
35
36
|
entry = default(locale, key, options[:default], options)
|
|
36
37
|
else
|
|
37
|
-
entry =
|
|
38
|
+
entry = resolve_entry(locale, key, entry, options)
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
count = options[:count]
|
|
@@ -53,7 +54,7 @@ module I18n
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
deep_interpolation = options[:deep_interpolation]
|
|
56
|
-
values =
|
|
57
|
+
values = Utils.except(options, *RESERVED_KEYS)
|
|
57
58
|
if values
|
|
58
59
|
entry = if deep_interpolation
|
|
59
60
|
deep_interpolate(locale, entry, values)
|
|
@@ -153,6 +154,7 @@ module I18n
|
|
|
153
154
|
end
|
|
154
155
|
result unless result.is_a?(MissingTranslation)
|
|
155
156
|
end
|
|
157
|
+
alias_method :resolve_entry, :resolve
|
|
156
158
|
|
|
157
159
|
# Picks a translation from a pluralized mnemonic subkey according to English
|
|
158
160
|
# pluralization rules :
|
|
@@ -223,24 +225,31 @@ module I18n
|
|
|
223
225
|
def load_file(filename)
|
|
224
226
|
type = File.extname(filename).tr('.', '').downcase
|
|
225
227
|
raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
|
|
226
|
-
data = send(:"load_#{type}", filename)
|
|
228
|
+
data, keys_symbolized = send(:"load_#{type}", filename)
|
|
227
229
|
unless data.is_a?(Hash)
|
|
228
230
|
raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
|
|
229
231
|
end
|
|
230
|
-
data.each { |locale, d| store_translations(locale, d || {}) }
|
|
232
|
+
data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) }
|
|
233
|
+
|
|
234
|
+
data
|
|
231
235
|
end
|
|
232
236
|
|
|
233
237
|
# Loads a plain Ruby translations file. eval'ing the file must yield
|
|
234
238
|
# a Hash containing translation data with locales as toplevel keys.
|
|
235
239
|
def load_rb(filename)
|
|
236
|
-
eval(IO.read(filename), binding, filename)
|
|
240
|
+
translations = eval(IO.read(filename), binding, filename)
|
|
241
|
+
[translations, false]
|
|
237
242
|
end
|
|
238
243
|
|
|
239
244
|
# Loads a YAML translations file. The data must have locales as
|
|
240
245
|
# toplevel keys.
|
|
241
246
|
def load_yml(filename)
|
|
242
247
|
begin
|
|
243
|
-
YAML.
|
|
248
|
+
if YAML.respond_to?(:unsafe_load_file) # Psych 4.0 way
|
|
249
|
+
[YAML.unsafe_load_file(filename, symbolize_names: true, freeze: true), true]
|
|
250
|
+
else
|
|
251
|
+
[YAML.load_file(filename), false]
|
|
252
|
+
end
|
|
244
253
|
rescue TypeError, ScriptError, StandardError => e
|
|
245
254
|
raise InvalidLocaleData.new(filename, e.inspect)
|
|
246
255
|
end
|
|
@@ -251,7 +260,12 @@ module I18n
|
|
|
251
260
|
# toplevel keys.
|
|
252
261
|
def load_json(filename)
|
|
253
262
|
begin
|
|
254
|
-
|
|
263
|
+
# Use #load_file as a proxy for a version of JSON where symbolize_names and freeze are supported.
|
|
264
|
+
if ::JSON.respond_to?(:load_file)
|
|
265
|
+
[::JSON.load_file(filename, symbolize_names: true, freeze: true), true]
|
|
266
|
+
else
|
|
267
|
+
[::JSON.parse(File.read(filename)), false]
|
|
268
|
+
end
|
|
255
269
|
rescue TypeError, StandardError => e
|
|
256
270
|
raise InvalidLocaleData.new(filename, e.inspect)
|
|
257
271
|
end
|
data/lib/i18n/backend/cache.rb
CHANGED
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
#
|
|
18
18
|
# The cache_key implementation by default assumes you pass values that return
|
|
19
19
|
# a valid key from #hash (see
|
|
20
|
-
#
|
|
20
|
+
# https://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
|
|
21
21
|
# configure your own digest method via which responds to #hexdigest (see
|
|
22
|
-
#
|
|
22
|
+
# https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/Digest.html):
|
|
23
23
|
#
|
|
24
|
-
# I18n.cache_key_digest = Digest::
|
|
24
|
+
# I18n.cache_key_digest = OpenSSL::Digest::SHA256.new
|
|
25
25
|
#
|
|
26
26
|
# If you use a lambda as a default value in your translation like this:
|
|
27
27
|
#
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'openssl'
|
|
4
4
|
|
|
5
5
|
module I18n
|
|
6
6
|
module Backend
|
|
@@ -19,7 +19,7 @@ module I18n
|
|
|
19
19
|
key = I18n::Backend::Flatten.escape_default_separator(normalized_path(filename))
|
|
20
20
|
old_mtime, old_digest = initialized && lookup(:i18n, key, :load_file)
|
|
21
21
|
return if (mtime = File.mtime(filename).to_i) == old_mtime ||
|
|
22
|
-
(digest = Digest::
|
|
22
|
+
(digest = OpenSSL::Digest::SHA256.file(filename).hexdigest) == old_digest
|
|
23
23
|
super
|
|
24
24
|
store_translations(:i18n, load_file: { key => [mtime, digest] })
|
|
25
25
|
end
|
data/lib/i18n/backend/chain.rb
CHANGED
|
@@ -17,8 +17,6 @@ module I18n
|
|
|
17
17
|
# The implementation assumes that all backends added to the Chain implement
|
|
18
18
|
# a lookup method with the same API as Simple backend does.
|
|
19
19
|
class Chain
|
|
20
|
-
using I18n::HashRefinements
|
|
21
|
-
|
|
22
20
|
module Implementation
|
|
23
21
|
include Base
|
|
24
22
|
|
|
@@ -55,7 +53,7 @@ module I18n
|
|
|
55
53
|
|
|
56
54
|
def translate(locale, key, default_options = EMPTY_HASH)
|
|
57
55
|
namespace = nil
|
|
58
|
-
options =
|
|
56
|
+
options = Utils.except(default_options, :default)
|
|
59
57
|
|
|
60
58
|
backends.each do |backend|
|
|
61
59
|
catch(:exception) do
|
|
@@ -101,7 +99,7 @@ module I18n
|
|
|
101
99
|
init_translations unless initialized?
|
|
102
100
|
translations
|
|
103
101
|
end
|
|
104
|
-
|
|
102
|
+
Utils.deep_merge!(memo, partial_translations) { |_, a, b| b || a }
|
|
105
103
|
end
|
|
106
104
|
end
|
|
107
105
|
|
|
@@ -16,11 +16,13 @@ module I18n
|
|
|
16
16
|
# Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
|
|
17
17
|
def fallbacks
|
|
18
18
|
@@fallbacks ||= I18n::Locale::Fallbacks.new
|
|
19
|
+
Thread.current[:i18n_fallbacks] || @@fallbacks
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
# Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
|
|
22
23
|
def fallbacks=(fallbacks)
|
|
23
24
|
@@fallbacks = fallbacks.is_a?(Array) ? I18n::Locale::Fallbacks.new(fallbacks) : fallbacks
|
|
25
|
+
Thread.current[:i18n_fallbacks] = @@fallbacks
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
|
|
@@ -41,13 +43,13 @@ module I18n
|
|
|
41
43
|
return super if options[:fallback_in_progress]
|
|
42
44
|
default = extract_non_symbol_default!(options) if options[:default]
|
|
43
45
|
|
|
44
|
-
fallback_options = options.merge(:fallback_in_progress => true)
|
|
46
|
+
fallback_options = options.merge(:fallback_in_progress => true, fallback_original_locale: locale)
|
|
45
47
|
I18n.fallbacks[locale].each do |fallback|
|
|
46
48
|
begin
|
|
47
49
|
catch(:exception) do
|
|
48
50
|
result = super(fallback, key, fallback_options)
|
|
49
51
|
unless result.nil?
|
|
50
|
-
on_fallback(locale, fallback, key, options) if locale != fallback
|
|
52
|
+
on_fallback(locale, fallback, key, options) if locale.to_s != fallback.to_s
|
|
51
53
|
return result
|
|
52
54
|
end
|
|
53
55
|
end
|
|
@@ -62,6 +64,24 @@ module I18n
|
|
|
62
64
|
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
|
63
65
|
end
|
|
64
66
|
|
|
67
|
+
def resolve_entry(locale, object, subject, options = EMPTY_HASH)
|
|
68
|
+
return subject if options[:resolve] == false
|
|
69
|
+
result = catch(:exception) do
|
|
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
|
|
81
|
+
end
|
|
82
|
+
result unless result.is_a?(MissingTranslation)
|
|
83
|
+
end
|
|
84
|
+
|
|
65
85
|
def extract_non_symbol_default!(options)
|
|
66
86
|
defaults = [options[:default]].flatten
|
|
67
87
|
first_non_symbol_default = defaults.detect{|default| !default.is_a?(Symbol)}
|
|
@@ -87,7 +107,7 @@ module I18n
|
|
|
87
107
|
private
|
|
88
108
|
|
|
89
109
|
# Overwrite on_fallback to add specified logic when the fallback succeeds.
|
|
90
|
-
def on_fallback(_original_locale, _fallback_locale, _key,
|
|
110
|
+
def on_fallback(_original_locale, _fallback_locale, _key, _options)
|
|
91
111
|
nil
|
|
92
112
|
end
|
|
93
113
|
end
|
data/lib/i18n/backend/gettext.rb
CHANGED
|
@@ -31,8 +31,6 @@ module I18n
|
|
|
31
31
|
# Without it strings containing periods (".") will not be translated.
|
|
32
32
|
|
|
33
33
|
module Gettext
|
|
34
|
-
using I18n::HashRefinements
|
|
35
|
-
|
|
36
34
|
class PoData < Hash
|
|
37
35
|
def set_comment(msgid_or_sym, comment)
|
|
38
36
|
# ignore
|
|
@@ -43,7 +41,7 @@ module I18n
|
|
|
43
41
|
def load_po(filename)
|
|
44
42
|
locale = ::File.basename(filename, '.po').to_sym
|
|
45
43
|
data = normalize(locale, parse(filename))
|
|
46
|
-
{ locale => data }
|
|
44
|
+
[{ locale => data }, false]
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
def parse(filename)
|
|
@@ -61,7 +59,7 @@ module I18n
|
|
|
61
59
|
{ part => _normalized.empty? ? value : _normalized }
|
|
62
60
|
end
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
Utils.deep_merge!(result, normalized)
|
|
65
63
|
end
|
|
66
64
|
result
|
|
67
65
|
end
|
|
@@ -67,8 +67,6 @@ module I18n
|
|
|
67
67
|
#
|
|
68
68
|
# This is useful if you are using a KeyValue backend chained to a Simple backend.
|
|
69
69
|
class KeyValue
|
|
70
|
-
using I18n::HashRefinements
|
|
71
|
-
|
|
72
70
|
module Implementation
|
|
73
71
|
attr_accessor :store
|
|
74
72
|
|
|
@@ -91,7 +89,7 @@ module I18n
|
|
|
91
89
|
when Hash
|
|
92
90
|
if @subtrees && (old_value = @store[key])
|
|
93
91
|
old_value = JSON.decode(old_value)
|
|
94
|
-
value =
|
|
92
|
+
value = Utils.deep_merge!(Utils.deep_symbolize_keys(old_value), value) if old_value.is_a?(Hash)
|
|
95
93
|
end
|
|
96
94
|
when Proc
|
|
97
95
|
raise "Key-value stores cannot handle procs"
|
|
@@ -115,12 +113,12 @@ module I18n
|
|
|
115
113
|
# them into a hash such as the one returned from loading the
|
|
116
114
|
# haml files
|
|
117
115
|
def translations
|
|
118
|
-
@translations = @store.keys.clone.map do |main_key|
|
|
116
|
+
@translations = Utils.deep_symbolize_keys(@store.keys.clone.map do |main_key|
|
|
119
117
|
main_value = JSON.decode(@store[main_key])
|
|
120
118
|
main_key.to_s.split(".").reverse.inject(main_value) do |value, key|
|
|
121
119
|
{key.to_sym => value}
|
|
122
120
|
end
|
|
123
|
-
end.inject{|hash, elem|
|
|
121
|
+
end.inject{|hash, elem| Utils.deep_merge!(hash, elem)})
|
|
124
122
|
end
|
|
125
123
|
|
|
126
124
|
def init_translations
|
|
@@ -141,7 +139,7 @@ module I18n
|
|
|
141
139
|
value = JSON.decode(value) if value
|
|
142
140
|
|
|
143
141
|
if value.is_a?(Hash)
|
|
144
|
-
|
|
142
|
+
Utils.deep_symbolize_keys(value)
|
|
145
143
|
elsif !value.nil?
|
|
146
144
|
value
|
|
147
145
|
elsif !@subtrees
|
|
@@ -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
|
data/lib/i18n/backend/simple.rb
CHANGED
|
@@ -19,8 +19,6 @@ module I18n
|
|
|
19
19
|
#
|
|
20
20
|
# I18n::Backend::Simple.include(I18n::Backend::Pluralization)
|
|
21
21
|
class Simple
|
|
22
|
-
using I18n::HashRefinements
|
|
23
|
-
|
|
24
22
|
module Implementation
|
|
25
23
|
include Base
|
|
26
24
|
|
|
@@ -35,14 +33,13 @@ module I18n
|
|
|
35
33
|
def store_translations(locale, data, options = EMPTY_HASH)
|
|
36
34
|
if I18n.enforce_available_locales &&
|
|
37
35
|
I18n.available_locales_initialized? &&
|
|
38
|
-
!I18n.
|
|
39
|
-
!I18n.available_locales.include?(locale.to_s)
|
|
36
|
+
!I18n.locale_available?(locale)
|
|
40
37
|
return data
|
|
41
38
|
end
|
|
42
39
|
locale = locale.to_sym
|
|
43
|
-
translations[locale] ||=
|
|
44
|
-
data = data.
|
|
45
|
-
translations[locale]
|
|
40
|
+
translations[locale] ||= Concurrent::Hash.new
|
|
41
|
+
data = Utils.deep_symbolize_keys(data) unless options.fetch(:skip_symbolize_keys, false)
|
|
42
|
+
Utils.deep_merge!(translations[locale], data)
|
|
46
43
|
end
|
|
47
44
|
|
|
48
45
|
# Get available locales from the translations hash
|
|
@@ -71,7 +68,7 @@ module I18n
|
|
|
71
68
|
# call `init_translations`
|
|
72
69
|
init_translations if do_init && !initialized?
|
|
73
70
|
|
|
74
|
-
@translations ||= {}
|
|
71
|
+
@translations ||= Concurrent::Hash.new { |h, k| h[k] = Concurrent::Hash.new }
|
|
75
72
|
end
|
|
76
73
|
|
|
77
74
|
protected
|
|
@@ -97,7 +94,7 @@ module I18n
|
|
|
97
94
|
return nil unless result.has_key?(_key)
|
|
98
95
|
end
|
|
99
96
|
result = result[_key]
|
|
100
|
-
result =
|
|
97
|
+
result = resolve_entry(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
|
|
101
98
|
result
|
|
102
99
|
end
|
|
103
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
|
|
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
|
|
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"
|
data/lib/i18n/exceptions.rb
CHANGED
|
@@ -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
|
|
27
|
+
the desired locale explicitly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)`
|
|
28
28
|
MESSAGE
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -47,10 +47,12 @@ module I18n
|
|
|
47
47
|
|
|
48
48
|
class MissingTranslation < ArgumentError
|
|
49
49
|
module Base
|
|
50
|
+
PERMITTED_KEYS = [:scope].freeze
|
|
51
|
+
|
|
50
52
|
attr_reader :locale, :key, :options
|
|
51
53
|
|
|
52
54
|
def initialize(locale, key, options = EMPTY_HASH)
|
|
53
|
-
@key, @locale, @options = key, locale, options.
|
|
55
|
+
@key, @locale, @options = key, locale, options.slice(*PERMITTED_KEYS)
|
|
54
56
|
options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
|
|
55
57
|
end
|
|
56
58
|
|
|
@@ -108,4 +110,38 @@ module I18n
|
|
|
108
110
|
super "can not load translations from #{filename}, the file type #{type} is not known"
|
|
109
111
|
end
|
|
110
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
|
|
111
147
|
end
|
|
@@ -5,7 +5,7 @@ module I18n
|
|
|
5
5
|
DEFAULT_INTERPOLATION_PATTERNS = [
|
|
6
6
|
/%%/,
|
|
7
7
|
/%\{([\w|]+)\}/, # matches placeholders like "%{foo} or %{foo|word}"
|
|
8
|
-
/%<(\w+)>(
|
|
8
|
+
/%<(\w+)>([^\d]*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
|
|
9
9
|
].freeze
|
|
10
10
|
INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS)
|
|
11
11
|
deprecate_constant :INTERPOLATION_PATTERN
|
|
@@ -14,7 +14,7 @@ module I18n
|
|
|
14
14
|
# Return String or raises MissingInterpolationArgument exception.
|
|
15
15
|
# Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler.
|
|
16
16
|
def interpolate(string, values)
|
|
17
|
-
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~
|
|
17
|
+
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ I18n.reserved_keys_pattern
|
|
18
18
|
raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
|
|
19
19
|
interpolate_hash(string, values)
|
|
20
20
|
end
|
|
@@ -15,19 +15,12 @@
|
|
|
15
15
|
# * all parent locales of a given locale (e.g. :es for :"es-MX") first,
|
|
16
16
|
# * the current default locales and all of their parents second
|
|
17
17
|
#
|
|
18
|
-
# The default locales are set to [
|
|
19
|
-
# set to something else.
|
|
18
|
+
# The default locales are set to [] by default but can be set to something else.
|
|
20
19
|
#
|
|
21
20
|
# One can additionally add any number of additional fallback locales manually.
|
|
22
21
|
# These will be added before the default locales to the fallback chain. For
|
|
23
22
|
# example:
|
|
24
23
|
#
|
|
25
|
-
# # using the default locale as default fallback locale
|
|
26
|
-
#
|
|
27
|
-
# I18n.default_locale = :"en-US"
|
|
28
|
-
# I18n.fallbacks = I18n::Locale::Fallbacks.new(:"de-AT" => :"de-DE")
|
|
29
|
-
# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :de, :"de-DE"]
|
|
30
|
-
#
|
|
31
24
|
# # using a custom locale as default fallback locale
|
|
32
25
|
#
|
|
33
26
|
# I18n.fallbacks = I18n::Locale::Fallbacks.new(:"en-GB", :"de-AT" => :de, :"de-CH" => :de)
|
|
@@ -46,7 +39,7 @@
|
|
|
46
39
|
# fallbacks[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
|
|
47
40
|
# fallbacks[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
|
|
48
41
|
#
|
|
49
|
-
# # people speaking Sami as spoken in
|
|
42
|
+
# # people speaking Sami as spoken in Finland also speak Swedish and Finnish as spoken in Finland
|
|
50
43
|
# fallbacks.map(:sms => [:"se-FI", :"fi-FI"])
|
|
51
44
|
# fallbacks[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
|
|
52
45
|
|
|
@@ -71,13 +64,18 @@ module I18n
|
|
|
71
64
|
super || store(locale, compute(locale))
|
|
72
65
|
end
|
|
73
66
|
|
|
74
|
-
def map(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
67
|
+
def map(*args, &block)
|
|
68
|
+
if args.count == 1 && !block_given?
|
|
69
|
+
mappings = args.first
|
|
70
|
+
mappings.each do |from, to|
|
|
71
|
+
from, to = from.to_sym, Array(to)
|
|
72
|
+
to.each do |_to|
|
|
73
|
+
@map[from] ||= []
|
|
74
|
+
@map[from] << _to.to_sym
|
|
75
|
+
end
|
|
80
76
|
end
|
|
77
|
+
else
|
|
78
|
+
@map.map(*args, &block)
|
|
81
79
|
end
|
|
82
80
|
end
|
|
83
81
|
|
data/lib/i18n/tests/basics.rb
CHANGED
|
@@ -5,12 +5,11 @@ module I18n
|
|
|
5
5
|
I18n.available_locales = nil
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
test "available_locales returns the
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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/tests/defaults.rb
CHANGED
|
@@ -37,7 +37,7 @@ module I18n
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
test "defaults: given an array of missing keys it raises a MissingTranslationData exception" do
|
|
40
|
-
|
|
40
|
+
assert_raises I18n::MissingTranslationData do
|
|
41
41
|
I18n.t(:does_not_exist, :default => [:does_not_exist_2, :does_not_exist_3], :raise => true)
|
|
42
42
|
end
|
|
43
43
|
end
|
|
@@ -41,7 +41,7 @@ module I18n
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
test "interpolation: given values but missing a key it raises I18n::MissingInterpolationArgument" do
|
|
44
|
-
|
|
44
|
+
assert_raises(I18n::MissingInterpolationArgument) do
|
|
45
45
|
interpolate(:default => '%{foo}', :bar => 'bar')
|
|
46
46
|
end
|
|
47
47
|
end
|
|
@@ -77,13 +77,13 @@ module I18n
|
|
|
77
77
|
|
|
78
78
|
if Object.const_defined?(:Encoding)
|
|
79
79
|
test "interpolation: given a euc-jp translation and a utf-8 value it raises Encoding::CompatibilityError" do
|
|
80
|
-
|
|
80
|
+
assert_raises(Encoding::CompatibilityError) do
|
|
81
81
|
interpolate(:default => euc_jp('こんにちは、%{name}さん!'), :name => 'ゆきひろ')
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
test "interpolation: given a utf-8 translation and a euc-jp value it raises Encoding::CompatibilityError" do
|
|
86
|
-
|
|
86
|
+
assert_raises(Encoding::CompatibilityError) do
|
|
87
87
|
interpolate(:default => 'こんにちは、%{name}さん!', :name => euc_jp('ゆきひろ'))
|
|
88
88
|
end
|
|
89
89
|
end
|
|
@@ -108,10 +108,10 @@ module I18n
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
test "interpolation: given a translations containing a reserved key it raises I18n::ReservedInterpolationKey" do
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{exception_handler}') }
|
|
112
|
+
assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{default}') }
|
|
113
|
+
assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{separator}') }
|
|
114
|
+
assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{scope}') }
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
test "interpolation: deep interpolation for default string" do
|
|
@@ -78,15 +78,15 @@ module I18n
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
test "localize Date: given nil it raises I18n::ArgumentError" do
|
|
81
|
-
|
|
81
|
+
assert_raises(I18n::ArgumentError) { I18n.l(nil) }
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
test "localize Date: given a plain Object it raises I18n::ArgumentError" do
|
|
85
|
-
|
|
85
|
+
assert_raises(I18n::ArgumentError) { I18n.l(Object.new) }
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
test "localize Date: given a format is missing it raises I18n::MissingTranslationData" do
|
|
89
|
-
|
|
89
|
+
assert_raises(I18n::MissingTranslationData) { I18n.l(@date, :format => :missing) }
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
test "localize Date: it does not alter the format string" do
|
|
@@ -78,7 +78,7 @@ module I18n
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
test "localize DateTime: given a format is missing it raises I18n::MissingTranslationData" do
|
|
81
|
-
|
|
81
|
+
assert_raises(I18n::MissingTranslationData) { I18n.l(@datetime, :format => :missing) }
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
protected
|
|
@@ -79,7 +79,7 @@ module I18n
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
test "localize Time: given a format is missing it raises I18n::MissingTranslationData" do
|
|
82
|
-
|
|
82
|
+
assert_raises(I18n::MissingTranslationData) { I18n.l(@time, :format => :missing) }
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
protected
|
data/lib/i18n/tests/lookup.rb
CHANGED
|
@@ -34,7 +34,7 @@ module I18n
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
test "lookup: given a missing key, no default and the raise option it raises MissingTranslationData" do
|
|
37
|
-
|
|
37
|
+
assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) }
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
test "lookup: does not raise an exception if no translation data is present for the given locale" do
|
|
@@ -61,7 +61,7 @@ module I18n
|
|
|
61
61
|
# In fact it probably *should* fail but Rails currently relies on using the default locale instead.
|
|
62
62
|
# So we'll stick to this for now until we get it fixed in Rails.
|
|
63
63
|
test "lookup: given nil as a locale it does not raise but use the default locale" do
|
|
64
|
-
#
|
|
64
|
+
# assert_raises(I18n::InvalidLocale) { I18n.t(:bar, :locale => nil) }
|
|
65
65
|
assert_nothing_raised { I18n.t(:bar, :locale => nil) }
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -28,7 +28,7 @@ module I18n
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
test "pluralization: given incomplete pluralization data it raises I18n::InvalidPluralizationData" do
|
|
31
|
-
|
|
31
|
+
assert_raises(I18n::InvalidPluralizationData) { I18n.t(:default => { :one => 'bar' }, :count => 2) }
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
data/lib/i18n/tests/procs.rb
CHANGED
|
@@ -53,7 +53,13 @@ module I18n
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def self.filter_args(*args)
|
|
56
|
-
args.map
|
|
56
|
+
args.map do |arg|
|
|
57
|
+
if arg.is_a?(Hash)
|
|
58
|
+
arg.delete(:fallback_in_progress)
|
|
59
|
+
arg.delete(:fallback_original_locale)
|
|
60
|
+
end
|
|
61
|
+
arg
|
|
62
|
+
end.inspect
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
end
|
data/lib/i18n/utils.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18n
|
|
4
|
+
module Utils
|
|
5
|
+
class << self
|
|
6
|
+
if Hash.method_defined?(:except)
|
|
7
|
+
def except(hash, *keys)
|
|
8
|
+
hash.except(*keys)
|
|
9
|
+
end
|
|
10
|
+
else
|
|
11
|
+
def except(hash, *keys)
|
|
12
|
+
hash = hash.dup
|
|
13
|
+
keys.each { |k| hash.delete(k) }
|
|
14
|
+
hash
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def deep_merge(hash, other_hash, &block)
|
|
19
|
+
deep_merge!(hash.dup, other_hash, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def deep_merge!(hash, other_hash, &block)
|
|
23
|
+
hash.merge!(other_hash) do |key, this_val, other_val|
|
|
24
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
|
25
|
+
deep_merge(this_val, other_val, &block)
|
|
26
|
+
elsif block_given?
|
|
27
|
+
yield key, this_val, other_val
|
|
28
|
+
else
|
|
29
|
+
other_val
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def deep_symbolize_keys(hash)
|
|
35
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
36
|
+
result[key.respond_to?(:to_sym) ? key.to_sym : key] = deep_symbolize_keys_in_object(value)
|
|
37
|
+
result
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def deep_symbolize_keys_in_object(value)
|
|
44
|
+
case value
|
|
45
|
+
when Hash
|
|
46
|
+
deep_symbolize_keys(value)
|
|
47
|
+
when Array
|
|
48
|
+
value.map { |e| deep_symbolize_keys_in_object(e) }
|
|
49
|
+
else
|
|
50
|
+
value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/i18n/version.rb
CHANGED
data/lib/i18n.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'concurrent/map'
|
|
4
|
+
require 'concurrent/hash'
|
|
4
5
|
|
|
5
6
|
require 'i18n/version'
|
|
7
|
+
require 'i18n/utils'
|
|
6
8
|
require 'i18n/exceptions'
|
|
7
9
|
require 'i18n/interpolate/ruby'
|
|
8
10
|
|
|
@@ -21,6 +23,7 @@ module I18n
|
|
|
21
23
|
exception_handler
|
|
22
24
|
fallback
|
|
23
25
|
fallback_in_progress
|
|
26
|
+
fallback_original_locale
|
|
24
27
|
format
|
|
25
28
|
object
|
|
26
29
|
raise
|
|
@@ -28,12 +31,24 @@ module I18n
|
|
|
28
31
|
scope
|
|
29
32
|
separator
|
|
30
33
|
throw
|
|
31
|
-
]
|
|
32
|
-
RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
|
|
34
|
+
]
|
|
33
35
|
EMPTY_HASH = {}.freeze
|
|
34
36
|
|
|
35
37
|
def self.new_double_nested_cache # :nodoc:
|
|
36
|
-
Concurrent::Map.new { |h,k| h[k] = Concurrent::Map.new }
|
|
38
|
+
Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Marks a key as reserved. Reserved keys are used internally,
|
|
42
|
+
# and can't also be used for interpolation. If you are using any
|
|
43
|
+
# extra keys as I18n options, you should call I18n.reserve_key
|
|
44
|
+
# before any I18n.translate (etc) calls are made.
|
|
45
|
+
def self.reserve_key(key)
|
|
46
|
+
RESERVED_KEYS << key.to_sym
|
|
47
|
+
@reserved_keys_pattern = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.reserved_keys_pattern # :nodoc:
|
|
51
|
+
@reserved_keys_pattern ||= /%\{(#{RESERVED_KEYS.join("|")})\}/
|
|
37
52
|
end
|
|
38
53
|
|
|
39
54
|
module Base
|
|
@@ -199,18 +214,12 @@ module I18n
|
|
|
199
214
|
|
|
200
215
|
backend = config.backend
|
|
201
216
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
else
|
|
206
|
-
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)
|
|
207
220
|
end
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
if result.is_a?(MissingTranslation)
|
|
211
|
-
handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
|
|
212
221
|
else
|
|
213
|
-
|
|
222
|
+
translate_key(key, throw, raise, locale, backend, options)
|
|
214
223
|
end
|
|
215
224
|
end
|
|
216
225
|
alias :t :translate
|
|
@@ -259,14 +268,14 @@ module I18n
|
|
|
259
268
|
#
|
|
260
269
|
# Setting a Hash using Ruby:
|
|
261
270
|
#
|
|
262
|
-
# store_translations(:de, :
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
#
|
|
271
|
+
# store_translations(:de, i18n: {
|
|
272
|
+
# transliterate: {
|
|
273
|
+
# rule: {
|
|
274
|
+
# 'ü' => 'ue',
|
|
275
|
+
# 'ö' => 'oe'
|
|
276
|
+
# }
|
|
277
|
+
# }
|
|
278
|
+
# })
|
|
270
279
|
#
|
|
271
280
|
# Setting a Proc:
|
|
272
281
|
#
|
|
@@ -323,11 +332,11 @@ module I18n
|
|
|
323
332
|
def normalize_keys(locale, key, scope, separator = nil)
|
|
324
333
|
separator ||= I18n.default_separator
|
|
325
334
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
[
|
|
336
|
+
*normalize_key(locale, separator),
|
|
337
|
+
*normalize_key(scope, separator),
|
|
338
|
+
*normalize_key(key, separator)
|
|
339
|
+
]
|
|
331
340
|
end
|
|
332
341
|
|
|
333
342
|
# Returns true when the passed locale, which can be either a String or a
|
|
@@ -349,6 +358,18 @@ module I18n
|
|
|
349
358
|
|
|
350
359
|
private
|
|
351
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
|
+
|
|
352
373
|
# Any exceptions thrown in translate will be sent to the @@exception_handler
|
|
353
374
|
# which can be a Symbol, a Proc or any other Object unless they're forced to
|
|
354
375
|
# be raised or thrown (MissingTranslation).
|
|
@@ -395,7 +416,7 @@ module I18n
|
|
|
395
416
|
keys.delete('')
|
|
396
417
|
keys.map! do |k|
|
|
397
418
|
case k
|
|
398
|
-
when /\A[-+]?[1-9]\d
|
|
419
|
+
when /\A[-+]?([1-9]\d*|0)\z/ # integer
|
|
399
420
|
k.to_i
|
|
400
421
|
when 'true'
|
|
401
422
|
true
|
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.
|
|
4
|
+
version: 1.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sven Fuchs
|
|
@@ -13,7 +13,7 @@ authors:
|
|
|
13
13
|
autorequire:
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
|
-
date:
|
|
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,13 +49,13 @@ 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
|
|
55
56
|
- lib/i18n/backend/simple.rb
|
|
56
57
|
- lib/i18n/backend/transliterator.rb
|
|
57
58
|
- lib/i18n/config.rb
|
|
58
|
-
- lib/i18n/core_ext/hash.rb
|
|
59
59
|
- lib/i18n/exceptions.rb
|
|
60
60
|
- lib/i18n/gettext.rb
|
|
61
61
|
- lib/i18n/gettext/helpers.rb
|
|
@@ -81,6 +81,7 @@ files:
|
|
|
81
81
|
- lib/i18n/tests/lookup.rb
|
|
82
82
|
- lib/i18n/tests/pluralization.rb
|
|
83
83
|
- lib/i18n/tests/procs.rb
|
|
84
|
+
- lib/i18n/utils.rb
|
|
84
85
|
- lib/i18n/version.rb
|
|
85
86
|
homepage: https://github.com/ruby-i18n/i18n
|
|
86
87
|
licenses:
|
|
@@ -90,23 +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
|
-
|
|
95
|
-
HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.
|
|
96
|
-
But that may break your application.
|
|
97
|
-
|
|
98
|
-
If you are upgrading your Rails application from an older version of Rails:
|
|
99
|
-
|
|
100
|
-
Please check your Rails app for 'config.i18n.fallbacks = true'.
|
|
101
|
-
If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be
|
|
102
|
-
'config.i18n.fallbacks = [I18n.default_locale]'.
|
|
103
|
-
If not, fallbacks will be broken in your app by I18n 1.1.x.
|
|
104
|
-
|
|
105
|
-
If you are starting a NEW Rails application, you can ignore this notice.
|
|
106
|
-
|
|
107
|
-
For more info see:
|
|
108
|
-
https://github.com/svenfuchs/i18n/releases/tag/v1.1.0
|
|
109
|
-
|
|
94
|
+
post_install_message:
|
|
110
95
|
rdoc_options: []
|
|
111
96
|
require_paths:
|
|
112
97
|
- lib
|
|
@@ -121,9 +106,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
121
106
|
- !ruby/object:Gem::Version
|
|
122
107
|
version: 1.3.5
|
|
123
108
|
requirements: []
|
|
124
|
-
rubygems_version: 3.
|
|
109
|
+
rubygems_version: 3.3.16
|
|
125
110
|
signing_key:
|
|
126
111
|
specification_version: 4
|
|
127
112
|
summary: New wave Internationalization support for Ruby
|
|
128
113
|
test_files: []
|
|
129
|
-
...
|
data/lib/i18n/core_ext/hash.rb
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
module I18n
|
|
2
|
-
module HashRefinements
|
|
3
|
-
refine Hash do
|
|
4
|
-
using I18n::HashRefinements
|
|
5
|
-
def except(*keys)
|
|
6
|
-
dup.except!(*keys)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def except!(*keys)
|
|
10
|
-
keys.each { |key| delete(key) }
|
|
11
|
-
self
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def deep_symbolize_keys
|
|
15
|
-
each_with_object({}) do |(key, value), result|
|
|
16
|
-
result[symbolize_key(key)] = deep_symbolize_keys_in_object(value)
|
|
17
|
-
result
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# deep_merge from activesupport 5
|
|
22
|
-
# Copyright (c) 2005-2019 David Heinemeier Hansson
|
|
23
|
-
def deep_merge(other_hash, &block)
|
|
24
|
-
dup.deep_merge!(other_hash, &block)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# deep_merge! from activesupport 5
|
|
28
|
-
# Copyright (c) 2005-2019 David Heinemeier Hansson
|
|
29
|
-
def deep_merge!(other_hash, &block)
|
|
30
|
-
merge!(other_hash) do |key, this_val, other_val|
|
|
31
|
-
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
|
32
|
-
this_val.deep_merge(other_val, &block)
|
|
33
|
-
elsif block_given?
|
|
34
|
-
block.call(key, this_val, other_val)
|
|
35
|
-
else
|
|
36
|
-
other_val
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def symbolize_key(key)
|
|
42
|
-
key.respond_to?(:to_sym) ? key.to_sym : key
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def deep_symbolize_keys_in_object(value)
|
|
48
|
-
case value
|
|
49
|
-
when Hash
|
|
50
|
-
value.deep_symbolize_keys
|
|
51
|
-
when Array
|
|
52
|
-
value.map { |e| deep_symbolize_keys_in_object(e) }
|
|
53
|
-
else
|
|
54
|
-
value
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|