i18n 0.9.5 → 1.10.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 +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
|
-
[](https://badge.fury.io/rb/i18n)
|
|
4
|
+
[](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
|