i18n 0.9.5 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +71 -32
- data/lib/i18n/backend/base.rb +75 -31
- data/lib/i18n/backend/cache.rb +10 -11
- data/lib/i18n/backend/cache_file.rb +36 -0
- data/lib/i18n/backend/cascade.rb +3 -1
- data/lib/i18n/backend/chain.rb +37 -6
- data/lib/i18n/backend/fallbacks.rb +43 -14
- data/lib/i18n/backend/flatten.rb +10 -5
- data/lib/i18n/backend/gettext.rb +4 -2
- data/lib/i18n/backend/interpolation_compiler.rb +3 -1
- data/lib/i18n/backend/key_value.rb +31 -4
- data/lib/i18n/backend/lazy_loadable.rb +184 -0
- data/lib/i18n/backend/memoize.rb +10 -2
- data/lib/i18n/backend/metadata.rb +5 -3
- data/lib/i18n/backend/pluralization.rb +3 -1
- data/lib/i18n/backend/simple.rb +29 -16
- data/lib/i18n/backend/transliterator.rb +2 -0
- data/lib/i18n/backend.rb +5 -1
- data/lib/i18n/config.rb +20 -2
- data/lib/i18n/exceptions.rb +60 -17
- data/lib/i18n/gettext/helpers.rb +4 -2
- data/lib/i18n/gettext/po_parser.rb +7 -7
- data/lib/i18n/gettext.rb +2 -0
- data/lib/i18n/interpolate/ruby.rb +8 -6
- data/lib/i18n/locale/fallbacks.rb +21 -20
- data/lib/i18n/locale/tag/parents.rb +8 -6
- data/lib/i18n/locale/tag/simple.rb +1 -1
- data/lib/i18n/locale.rb +2 -0
- data/lib/i18n/middleware.rb +2 -0
- data/lib/i18n/tests/basics.rb +3 -5
- data/lib/i18n/tests/defaults.rb +1 -1
- data/lib/i18n/tests/interpolation.rb +12 -7
- data/lib/i18n/tests/link.rb +11 -1
- data/lib/i18n/tests/localization/date.rb +32 -10
- data/lib/i18n/tests/localization/date_time.rb +28 -7
- data/lib/i18n/tests/localization/procs.rb +7 -5
- data/lib/i18n/tests/localization/time.rb +27 -5
- data/lib/i18n/tests/lookup.rb +4 -4
- data/lib/i18n/tests/pluralization.rb +1 -1
- data/lib/i18n/tests/procs.rb +12 -1
- data/lib/i18n/tests.rb +2 -0
- data/lib/i18n/utils.rb +55 -0
- data/lib/i18n/version.rb +3 -1
- data/lib/i18n.rb +123 -50
- metadata +16 -61
- data/gemfiles/Gemfile.rails-3.2.x +0 -10
- data/gemfiles/Gemfile.rails-4.0.x +0 -10
- data/gemfiles/Gemfile.rails-4.1.x +0 -10
- data/gemfiles/Gemfile.rails-4.2.x +0 -10
- data/gemfiles/Gemfile.rails-5.0.x +0 -10
- data/gemfiles/Gemfile.rails-5.1.x +0 -10
- data/gemfiles/Gemfile.rails-master +0 -10
- data/lib/i18n/core_ext/hash.rb +0 -29
- data/lib/i18n/core_ext/kernel/suppress_warnings.rb +0 -8
- data/lib/i18n/core_ext/string/interpolate.rb +0 -9
- data/test/api/all_features_test.rb +0 -58
- data/test/api/cascade_test.rb +0 -28
- data/test/api/chain_test.rb +0 -24
- data/test/api/fallbacks_test.rb +0 -30
- data/test/api/key_value_test.rb +0 -24
- data/test/api/memoize_test.rb +0 -56
- data/test/api/override_test.rb +0 -42
- data/test/api/pluralization_test.rb +0 -30
- data/test/api/simple_test.rb +0 -28
- data/test/backend/cache_test.rb +0 -109
- data/test/backend/cascade_test.rb +0 -86
- data/test/backend/chain_test.rb +0 -122
- data/test/backend/exceptions_test.rb +0 -36
- data/test/backend/fallbacks_test.rb +0 -219
- data/test/backend/interpolation_compiler_test.rb +0 -118
- data/test/backend/key_value_test.rb +0 -61
- data/test/backend/memoize_test.rb +0 -79
- data/test/backend/metadata_test.rb +0 -48
- data/test/backend/pluralization_test.rb +0 -45
- data/test/backend/simple_test.rb +0 -103
- data/test/backend/transliterator_test.rb +0 -84
- data/test/core_ext/hash_test.rb +0 -36
- data/test/gettext/api_test.rb +0 -214
- data/test/gettext/backend_test.rb +0 -92
- data/test/i18n/exceptions_test.rb +0 -117
- data/test/i18n/gettext_plural_keys_test.rb +0 -20
- data/test/i18n/interpolate_test.rb +0 -91
- data/test/i18n/load_path_test.rb +0 -34
- data/test/i18n/middleware_test.rb +0 -24
- data/test/i18n_test.rb +0 -462
- data/test/locale/fallbacks_test.rb +0 -133
- data/test/locale/tag/rfc4646_test.rb +0 -143
- data/test/locale/tag/simple_test.rb +0 -32
- data/test/run_all.rb +0 -20
- data/test/test_data/locales/de.po +0 -82
- data/test/test_data/locales/en.rb +0 -3
- data/test/test_data/locales/en.yml +0 -3
- data/test/test_data/locales/invalid/empty.yml +0 -0
- data/test/test_data/locales/invalid/syntax.yml +0 -4
- data/test/test_data/locales/plurals.rb +0 -113
- data/test/test_helper.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcce939890b82f2f78ef93aa98b2be5995f172f6c37b95c53800c155f2036eec
|
4
|
+
data.tar.gz: ae4d72446e698fc7c0666f0e9dad6f11fa669d768fe9272d0c89e14aba8d2274
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf36e9389526d5a48bbfc4043bc2c4587ddbfbfab41a2ccaafa801570c3a838530a001c6c3df1669d5e0beb14b63b394253cef6d6fdd12aa936ae8c07ecdbf72
|
7
|
+
data.tar.gz: 82b972e13f2342ca864e4ead724008aa75ad24bbf6d4f14603330e2999501cfb5c05ee6a9e5cd2cffbf48f07f6da868fccc33b4771f7b4b085989c4def36a7b9
|
data/README.md
CHANGED
@@ -1,45 +1,83 @@
|
|
1
1
|
# Ruby I18n
|
2
2
|
|
3
|
-
[![
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/i18n.svg)](https://badge.fury.io/rb/i18n)
|
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
|
-
Ruby
|
6
|
+
Ruby internationalization and localization (i18n) solution.
|
6
7
|
|
7
|
-
|
8
|
+
Currently maintained by @radar.
|
8
9
|
|
9
|
-
|
10
|
+
## Usage
|
10
11
|
|
11
|
-
|
12
|
-
* interpolation of values to translations (Ruby 1.9 compatible syntax)
|
13
|
-
* pluralization (CLDR compatible)
|
14
|
-
* customizable transliteration to ASCII
|
15
|
-
* flexible defaults
|
16
|
-
* bulk lookup
|
17
|
-
* lambdas as translation data
|
18
|
-
* custom key/scope separator
|
19
|
-
* custom exception handlers
|
20
|
-
* extensible architecture with a swappable backend
|
12
|
+
### Rails
|
21
13
|
|
22
|
-
|
14
|
+
You will most commonly use this library within a Rails app.
|
15
|
+
|
16
|
+
[See the Rails Guide](https://guides.rubyonrails.org/i18n.html) for an example of its usage.
|
17
|
+
|
18
|
+
### Ruby (without Rails)
|
19
|
+
|
20
|
+
If you want to use this library without Rails, you can simply add `i18n` to your `Gemfile`:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'i18n'
|
24
|
+
```
|
25
|
+
|
26
|
+
Then configure I18n with some translations, and a default locale:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
I18n.load_path << Dir[File.expand_path("config/locales") + "/*.yml"]
|
30
|
+
I18n.default_locale = :en # (note that `en` is already the default!)
|
31
|
+
```
|
32
|
+
|
33
|
+
A simple translation file in your project might live at `config/locales/en.yml` and look like:
|
34
|
+
|
35
|
+
```yml
|
36
|
+
en:
|
37
|
+
test: "This is a test"
|
38
|
+
```
|
39
|
+
|
40
|
+
You can then access this translation by doing:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
I18n.t(:test)
|
44
|
+
```
|
45
|
+
|
46
|
+
You can switch locales in your project by setting `I18n.locale` to a different value:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
I18n.locale = :de
|
50
|
+
I18n.t(:test) # => "Dies ist ein Test"
|
51
|
+
```
|
52
|
+
|
53
|
+
## Features
|
54
|
+
|
55
|
+
* Translation and localization
|
56
|
+
* Interpolation of values to translations
|
57
|
+
* Pluralization (CLDR compatible)
|
58
|
+
* Customizable transliteration to ASCII
|
59
|
+
* Flexible defaults
|
60
|
+
* Bulk lookup
|
61
|
+
* Lambdas as translation data
|
62
|
+
* Custom key/scope separator
|
63
|
+
* Custom exception handlers
|
64
|
+
* Extensible architecture with a swappable backend
|
65
|
+
|
66
|
+
## Pluggable Features
|
23
67
|
|
24
68
|
* Cache
|
25
69
|
* Pluralization: lambda pluralizers stored as translation data
|
26
70
|
* Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
|
27
|
-
* [Gettext support](https://github.com/
|
71
|
+
* [Gettext support](https://github.com/ruby-i18n/i18n/wiki/Gettext)
|
28
72
|
* Translation metadata
|
29
73
|
|
30
|
-
Alternative
|
74
|
+
## Alternative Backend
|
31
75
|
|
32
76
|
* Chain
|
33
77
|
* ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
|
34
78
|
* KeyValue (uses active_support/json and cannot store procs)
|
35
79
|
|
36
|
-
For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/
|
37
|
-
|
38
|
-
## Installation
|
39
|
-
|
40
|
-
```
|
41
|
-
gem install i18n
|
42
|
-
```
|
80
|
+
For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/ruby-i18n/i18n/wiki/Resources).
|
43
81
|
|
44
82
|
## Tests
|
45
83
|
|
@@ -58,7 +96,7 @@ particular tests in different test cases.
|
|
58
96
|
The reason for this is that we need to enforce the I18n API across various
|
59
97
|
combinations of extensions. E.g. the Simple backend alone needs to support
|
60
98
|
the same API as any combination of feature and/or optimization modules included
|
61
|
-
to the Simple backend. We test this by reusing the same API
|
99
|
+
to the Simple backend. We test this by reusing the same API definition (implemented
|
62
100
|
as test methods) in test cases with different setups.
|
63
101
|
|
64
102
|
You can find the test cases that enforce the API in test/api. And you can find
|
@@ -67,17 +105,18 @@ the API definition test methods in test/api/tests.
|
|
67
105
|
All other test cases (e.g. as defined in test/backend, test/core_ext) etc.
|
68
106
|
follow the usual test setup and should be easy to grok.
|
69
107
|
|
70
|
-
##
|
108
|
+
## More Documentation
|
71
109
|
|
72
|
-
|
73
|
-
* [Joshua Harvey](http://www.workingwithrails.com/person/759-joshua-harvey)
|
74
|
-
* [Stephan Soller](http://www.arkanis-development.de)
|
75
|
-
* [Saimon Moore](http://saimonmoore.net)
|
76
|
-
* [Matt Aimonetti](https://matt.aimonetti.net/)
|
110
|
+
Additional documentation can be found here: https://github.com/ruby-i18n/i18n/wiki
|
77
111
|
|
78
112
|
## Contributors
|
79
113
|
|
80
|
-
|
114
|
+
* @radar
|
115
|
+
* @carlosantoniodasilva
|
116
|
+
* @josevalim
|
117
|
+
* @knapo
|
118
|
+
* @tigrish
|
119
|
+
* [and many more](https://github.com/ruby-i18n/i18n/graphs/contributors)
|
81
120
|
|
82
121
|
## License
|
83
122
|
|
data/lib/i18n/backend/base.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
2
|
-
require '
|
3
|
-
require 'i18n/core_ext/kernel/suppress_warnings'
|
4
|
+
require 'json'
|
4
5
|
|
5
6
|
module I18n
|
6
7
|
module Backend
|
@@ -8,20 +9,23 @@ module I18n
|
|
8
9
|
include I18n::Backend::Transliterator
|
9
10
|
|
10
11
|
# Accepts a list of paths to translation files. Loads translations from
|
11
|
-
# plain Ruby (*.rb)
|
12
|
+
# plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See #load_rb, #load_yml, and #load_json
|
12
13
|
# for details.
|
13
14
|
def load_translations(*filenames)
|
14
15
|
filenames = I18n.load_path if filenames.empty?
|
15
|
-
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
|
16
20
|
end
|
17
21
|
|
18
22
|
# This method receives a locale, a data hash and options for storing translations.
|
19
23
|
# Should be implemented
|
20
|
-
def store_translations(locale, data, options =
|
24
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
21
25
|
raise NotImplementedError
|
22
26
|
end
|
23
27
|
|
24
|
-
def translate(locale, key, options =
|
28
|
+
def translate(locale, key, options = EMPTY_HASH)
|
25
29
|
raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty?
|
26
30
|
raise InvalidLocale.new(locale) unless locale
|
27
31
|
return nil if key.nil? && !options.key?(:default)
|
@@ -31,7 +35,7 @@ module I18n
|
|
31
35
|
if entry.nil? && options.key?(:default)
|
32
36
|
entry = default(locale, key, options[:default], options)
|
33
37
|
else
|
34
|
-
entry =
|
38
|
+
entry = resolve_entry(locale, key, entry, options)
|
35
39
|
end
|
36
40
|
|
37
41
|
count = options[:count]
|
@@ -50,7 +54,7 @@ module I18n
|
|
50
54
|
end
|
51
55
|
|
52
56
|
deep_interpolation = options[:deep_interpolation]
|
53
|
-
values =
|
57
|
+
values = Utils.except(options, *RESERVED_KEYS)
|
54
58
|
if values
|
55
59
|
entry = if deep_interpolation
|
56
60
|
deep_interpolate(locale, entry, values)
|
@@ -61,14 +65,14 @@ module I18n
|
|
61
65
|
entry
|
62
66
|
end
|
63
67
|
|
64
|
-
def exists?(locale, key)
|
68
|
+
def exists?(locale, key, options = EMPTY_HASH)
|
65
69
|
lookup(locale, key) != nil
|
66
70
|
end
|
67
71
|
|
68
72
|
# Acts the same as +strftime+, but uses a localized version of the
|
69
73
|
# format string. Takes a key from the date/time formats translations as
|
70
74
|
# a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
|
71
|
-
def localize(locale, object, format = :default, options =
|
75
|
+
def localize(locale, object, format = :default, options = EMPTY_HASH)
|
72
76
|
if object.nil? && options.include?(:default)
|
73
77
|
return options[:default]
|
74
78
|
end
|
@@ -78,7 +82,7 @@ module I18n
|
|
78
82
|
key = format
|
79
83
|
type = object.respond_to?(:sec) ? 'time' : 'date'
|
80
84
|
options = options.merge(:raise => true, :object => object, :locale => locale)
|
81
|
-
format = I18n.t(:"#{type}.formats.#{key}", options)
|
85
|
+
format = I18n.t(:"#{type}.formats.#{key}", **options)
|
82
86
|
end
|
83
87
|
|
84
88
|
format = translate_localization_format(locale, object, format, options)
|
@@ -92,12 +96,21 @@ module I18n
|
|
92
96
|
end
|
93
97
|
|
94
98
|
def reload!
|
99
|
+
eager_load! if eager_loaded?
|
100
|
+
end
|
101
|
+
|
102
|
+
def eager_load!
|
103
|
+
@eager_loaded = true
|
95
104
|
end
|
96
105
|
|
97
106
|
protected
|
98
107
|
|
108
|
+
def eager_loaded?
|
109
|
+
@eager_loaded ||= false
|
110
|
+
end
|
111
|
+
|
99
112
|
# The method which actually looks up for the translation in the store.
|
100
|
-
def lookup(locale, key, scope = [], options =
|
113
|
+
def lookup(locale, key, scope = [], options = EMPTY_HASH)
|
101
114
|
raise NotImplementedError
|
102
115
|
end
|
103
116
|
|
@@ -109,8 +122,8 @@ module I18n
|
|
109
122
|
# If given subject is an Array, it walks the array and returns the
|
110
123
|
# first translation that can be resolved. Otherwise it tries to resolve
|
111
124
|
# the translation directly.
|
112
|
-
def default(locale, object, subject, options =
|
113
|
-
options = options.
|
125
|
+
def default(locale, object, subject, options = EMPTY_HASH)
|
126
|
+
options = options.reject { |key, value| key == :default }
|
114
127
|
case subject
|
115
128
|
when Array
|
116
129
|
subject.each do |item|
|
@@ -126,21 +139,22 @@ module I18n
|
|
126
139
|
# If the given subject is a Symbol, it will be translated with the
|
127
140
|
# given options. If it is a Proc then it will be evaluated. All other
|
128
141
|
# subjects will be returned directly.
|
129
|
-
def resolve(locale, object, subject, options =
|
142
|
+
def resolve(locale, object, subject, options = EMPTY_HASH)
|
130
143
|
return subject if options[:resolve] == false
|
131
144
|
result = catch(:exception) do
|
132
145
|
case subject
|
133
146
|
when Symbol
|
134
|
-
I18n.translate(subject, options.merge(:locale => locale, :throw => true))
|
147
|
+
I18n.translate(subject, **options.merge(:locale => locale, :throw => true))
|
135
148
|
when Proc
|
136
149
|
date_or_time = options.delete(:object) || object
|
137
|
-
resolve(locale, object, subject.call(date_or_time, options))
|
150
|
+
resolve(locale, object, subject.call(date_or_time, **options))
|
138
151
|
else
|
139
152
|
subject
|
140
153
|
end
|
141
154
|
end
|
142
155
|
result unless result.is_a?(MissingTranslation)
|
143
156
|
end
|
157
|
+
alias_method :resolve_entry, :resolve
|
144
158
|
|
145
159
|
# Picks a translation from a pluralized mnemonic subkey according to English
|
146
160
|
# pluralization rules :
|
@@ -151,7 +165,8 @@ module I18n
|
|
151
165
|
# not standard with regards to the CLDR pluralization rules.
|
152
166
|
# Other backends can implement more flexible or complex pluralization rules.
|
153
167
|
def pluralize(locale, entry, count)
|
154
|
-
|
168
|
+
entry = entry.reject { |k, _v| k == :attributes } if entry.is_a?(Hash)
|
169
|
+
return entry unless entry.is_a?(Hash) && count && entry.values.none? { |v| v.is_a?(Hash) }
|
155
170
|
|
156
171
|
key = pluralization_key(entry, count)
|
157
172
|
raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
|
@@ -168,7 +183,7 @@ module I18n
|
|
168
183
|
# each element of the array is recursively interpolated (until it finds a string)
|
169
184
|
# method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz"
|
170
185
|
# # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]"
|
171
|
-
def interpolate(locale, subject, values =
|
186
|
+
def interpolate(locale, subject, values = EMPTY_HASH)
|
172
187
|
return subject if values.empty?
|
173
188
|
|
174
189
|
case subject
|
@@ -184,7 +199,7 @@ module I18n
|
|
184
199
|
# deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
|
185
200
|
# ann: 'good', john: 'big'
|
186
201
|
# #=> { people: { ann: "Ann is good", john: "John is big" } }
|
187
|
-
def deep_interpolate(locale, data, values =
|
202
|
+
def deep_interpolate(locale, data, values = EMPTY_HASH)
|
188
203
|
return data if values.empty?
|
189
204
|
|
190
205
|
case data
|
@@ -210,40 +225,69 @@ module I18n
|
|
210
225
|
def load_file(filename)
|
211
226
|
type = File.extname(filename).tr('.', '').downcase
|
212
227
|
raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
|
213
|
-
data = send(:"load_#{type}", filename)
|
228
|
+
data, keys_symbolized = send(:"load_#{type}", filename)
|
214
229
|
unless data.is_a?(Hash)
|
215
230
|
raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
|
216
231
|
end
|
217
|
-
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
|
218
235
|
end
|
219
236
|
|
220
237
|
# Loads a plain Ruby translations file. eval'ing the file must yield
|
221
238
|
# a Hash containing translation data with locales as toplevel keys.
|
222
239
|
def load_rb(filename)
|
223
|
-
eval(IO.read(filename), binding, filename)
|
240
|
+
translations = eval(IO.read(filename), binding, filename)
|
241
|
+
[translations, false]
|
224
242
|
end
|
225
243
|
|
226
244
|
# Loads a YAML translations file. The data must have locales as
|
227
245
|
# toplevel keys.
|
228
246
|
def load_yml(filename)
|
229
247
|
begin
|
230
|
-
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
|
231
253
|
rescue TypeError, ScriptError, StandardError => e
|
232
254
|
raise InvalidLocaleData.new(filename, e.inspect)
|
233
255
|
end
|
234
256
|
end
|
257
|
+
alias_method :load_yaml, :load_yml
|
258
|
+
|
259
|
+
# Loads a JSON translations file. The data must have locales as
|
260
|
+
# toplevel keys.
|
261
|
+
def load_json(filename)
|
262
|
+
begin
|
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
|
269
|
+
rescue TypeError, StandardError => e
|
270
|
+
raise InvalidLocaleData.new(filename, e.inspect)
|
271
|
+
end
|
272
|
+
end
|
235
273
|
|
236
274
|
def translate_localization_format(locale, object, format, options)
|
237
|
-
format.to_s.gsub(/%[aAbBpP]/) do |match|
|
275
|
+
format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match|
|
238
276
|
case match
|
239
|
-
when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
|
240
|
-
when '
|
241
|
-
when '%
|
242
|
-
when '
|
243
|
-
when '%
|
244
|
-
when '
|
277
|
+
when '%a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
|
278
|
+
when '%^a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday].upcase
|
279
|
+
when '%A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday]
|
280
|
+
when '%^A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday].upcase
|
281
|
+
when '%b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
|
282
|
+
when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase
|
283
|
+
when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon]
|
284
|
+
when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase
|
285
|
+
when '%p' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
|
286
|
+
when '%P' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
|
245
287
|
end
|
246
288
|
end
|
289
|
+
rescue MissingTranslationData => e
|
290
|
+
e.message
|
247
291
|
end
|
248
292
|
|
249
293
|
def pluralization_key(entry, count)
|
data/lib/i18n/backend/cache.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This module allows you to easily cache all responses from the backend - thus
|
2
4
|
# speeding up the I18n aspects of your application quite a bit.
|
3
5
|
#
|
@@ -14,12 +16,12 @@
|
|
14
16
|
# ActiveSupport::Cache (only the methods #fetch and #write are being used).
|
15
17
|
#
|
16
18
|
# The cache_key implementation by default assumes you pass values that return
|
17
|
-
# a valid key from #hash (see
|
18
|
-
#
|
19
|
-
# configure your own digest method via which responds to #hexdigest (see
|
20
|
-
#
|
19
|
+
# a valid key from #hash (see
|
20
|
+
# https://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
|
21
|
+
# configure your own digest method via which responds to #hexdigest (see
|
22
|
+
# https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/Digest.html):
|
21
23
|
#
|
22
|
-
# I18n.cache_key_digest = Digest::
|
24
|
+
# I18n.cache_key_digest = OpenSSL::Digest::SHA256.new
|
23
25
|
#
|
24
26
|
# If you use a lambda as a default value in your translation like this:
|
25
27
|
#
|
@@ -75,7 +77,7 @@ module I18n
|
|
75
77
|
module Backend
|
76
78
|
# TODO Should the cache be cleared if new translations are stored?
|
77
79
|
module Cache
|
78
|
-
def translate(locale, key, options =
|
80
|
+
def translate(locale, key, options = EMPTY_HASH)
|
79
81
|
I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super
|
80
82
|
end
|
81
83
|
|
@@ -98,16 +100,13 @@ module I18n
|
|
98
100
|
|
99
101
|
def cache_key(locale, key, options)
|
100
102
|
# This assumes that only simple, native Ruby values are passed to I18n.translate.
|
101
|
-
"i18n/#{I18n.cache_namespace}/#{locale}/#{digest_item(key)}/#{
|
103
|
+
"i18n/#{I18n.cache_namespace}/#{locale}/#{digest_item(key)}/#{digest_item(options)}"
|
102
104
|
end
|
103
105
|
|
104
106
|
private
|
105
|
-
# In Ruby < 1.9 the following is true: { :foo => 1, :bar => 2 }.hash == { :foo => 2, :bar => 1 }.hash
|
106
|
-
# Therefore we must use the hash of the inspect string instead to avoid cache key colisions.
|
107
|
-
USE_INSPECT_HASH = RUBY_VERSION <= "1.9"
|
108
107
|
|
109
108
|
def digest_item(key)
|
110
|
-
I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.hash
|
109
|
+
I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.to_s.hash
|
111
110
|
end
|
112
111
|
end
|
113
112
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module I18n
|
6
|
+
module Backend
|
7
|
+
# Overwrites the Base load_file method to cache loaded file contents.
|
8
|
+
module CacheFile
|
9
|
+
# Optionally provide path_roots array to normalize filename paths,
|
10
|
+
# to make the cached i18n data portable across environments.
|
11
|
+
attr_accessor :path_roots
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Track loaded translation files in the `i18n.load_file` scope,
|
16
|
+
# and skip loading the file if its contents are still up-to-date.
|
17
|
+
def load_file(filename)
|
18
|
+
initialized = !respond_to?(:initialized?) || initialized?
|
19
|
+
key = I18n::Backend::Flatten.escape_default_separator(normalized_path(filename))
|
20
|
+
old_mtime, old_digest = initialized && lookup(:i18n, key, :load_file)
|
21
|
+
return if (mtime = File.mtime(filename).to_i) == old_mtime ||
|
22
|
+
(digest = OpenSSL::Digest::SHA256.file(filename).hexdigest) == old_digest
|
23
|
+
super
|
24
|
+
store_translations(:i18n, load_file: { key => [mtime, digest] })
|
25
|
+
end
|
26
|
+
|
27
|
+
# Translate absolute filename to relative path for i18n key.
|
28
|
+
def normalized_path(file)
|
29
|
+
return file unless path_roots
|
30
|
+
path = path_roots.find(&file.method(:start_with?)) ||
|
31
|
+
raise(InvalidLocaleData.new(file, 'outside expected path roots'))
|
32
|
+
file.sub(path, path_roots.index(path).to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/i18n/backend/cascade.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# The Cascade module adds the ability to do cascading lookups to backends that
|
2
4
|
# are compatible to the Simple backend.
|
3
5
|
#
|
@@ -31,7 +33,7 @@
|
|
31
33
|
module I18n
|
32
34
|
module Backend
|
33
35
|
module Cascade
|
34
|
-
def lookup(locale, key, scope = [], options =
|
36
|
+
def lookup(locale, key, scope = [], options = EMPTY_HASH)
|
35
37
|
return super unless cascade = options[:cascade]
|
36
38
|
|
37
39
|
cascade = { :step => 1 } unless cascade.is_a?(Hash)
|
data/lib/i18n/backend/chain.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module I18n
|
2
4
|
module Backend
|
3
5
|
# Backend that chains multiple other backends and checks each of them when
|
@@ -24,11 +26,24 @@ module I18n
|
|
24
26
|
self.backends = backends
|
25
27
|
end
|
26
28
|
|
29
|
+
def initialized?
|
30
|
+
backends.all? do |backend|
|
31
|
+
backend.instance_eval do
|
32
|
+
return false unless initialized?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
27
38
|
def reload!
|
28
39
|
backends.each { |backend| backend.reload! }
|
29
40
|
end
|
30
41
|
|
31
|
-
def
|
42
|
+
def eager_load!
|
43
|
+
backends.each { |backend| backend.eager_load! }
|
44
|
+
end
|
45
|
+
|
46
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
32
47
|
backends.first.store_translations(locale, data, options)
|
33
48
|
end
|
34
49
|
|
@@ -36,9 +51,9 @@ module I18n
|
|
36
51
|
backends.map { |backend| backend.available_locales }.flatten.uniq
|
37
52
|
end
|
38
53
|
|
39
|
-
def translate(locale, key, default_options =
|
54
|
+
def translate(locale, key, default_options = EMPTY_HASH)
|
40
55
|
namespace = nil
|
41
|
-
options =
|
56
|
+
options = Utils.except(default_options, :default)
|
42
57
|
|
43
58
|
backends.each do |backend|
|
44
59
|
catch(:exception) do
|
@@ -56,13 +71,13 @@ module I18n
|
|
56
71
|
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
57
72
|
end
|
58
73
|
|
59
|
-
def exists?(locale, key)
|
74
|
+
def exists?(locale, key, options = EMPTY_HASH)
|
60
75
|
backends.any? do |backend|
|
61
|
-
backend.exists?(locale, key)
|
76
|
+
backend.exists?(locale, key, options)
|
62
77
|
end
|
63
78
|
end
|
64
79
|
|
65
|
-
def localize(locale, object, format = :default, options =
|
80
|
+
def localize(locale, object, format = :default, options = EMPTY_HASH)
|
66
81
|
backends.each do |backend|
|
67
82
|
catch(:exception) do
|
68
83
|
result = backend.localize(locale, object, format, options) and return result
|
@@ -72,6 +87,22 @@ module I18n
|
|
72
87
|
end
|
73
88
|
|
74
89
|
protected
|
90
|
+
def init_translations
|
91
|
+
backends.each do |backend|
|
92
|
+
backend.send(:init_translations)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def translations
|
97
|
+
backends.reverse.each_with_object({}) do |backend, memo|
|
98
|
+
partial_translations = backend.instance_eval do
|
99
|
+
init_translations unless initialized?
|
100
|
+
translations
|
101
|
+
end
|
102
|
+
Utils.deep_merge!(memo, partial_translations) { |_, a, b| b || a }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
75
106
|
def namespace_lookup?(result, options)
|
76
107
|
result.is_a?(Hash) && !options.has_key?(:count)
|
77
108
|
end
|