i18n 1.6.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +125 -0
- data/lib/i18n.rb +398 -0
- data/lib/i18n/backend.rb +21 -0
- data/lib/i18n/backend/base.rb +284 -0
- data/lib/i18n/backend/cache.rb +113 -0
- data/lib/i18n/backend/cache_file.rb +36 -0
- data/lib/i18n/backend/cascade.rb +56 -0
- data/lib/i18n/backend/chain.rb +127 -0
- data/lib/i18n/backend/fallbacks.rb +84 -0
- data/lib/i18n/backend/flatten.rb +115 -0
- data/lib/i18n/backend/gettext.rb +85 -0
- data/lib/i18n/backend/interpolation_compiler.rb +123 -0
- data/lib/i18n/backend/key_value.rb +206 -0
- data/lib/i18n/backend/memoize.rb +54 -0
- data/lib/i18n/backend/metadata.rb +71 -0
- data/lib/i18n/backend/pluralization.rb +55 -0
- data/lib/i18n/backend/simple.rb +111 -0
- data/lib/i18n/backend/transliterator.rb +108 -0
- data/lib/i18n/config.rb +165 -0
- data/lib/i18n/core_ext/hash.rb +47 -0
- data/lib/i18n/exceptions.rb +111 -0
- data/lib/i18n/gettext.rb +28 -0
- data/lib/i18n/gettext/helpers.rb +75 -0
- data/lib/i18n/gettext/po_parser.rb +329 -0
- data/lib/i18n/interpolate/ruby.rb +39 -0
- data/lib/i18n/locale.rb +8 -0
- data/lib/i18n/locale/fallbacks.rb +96 -0
- data/lib/i18n/locale/tag.rb +28 -0
- data/lib/i18n/locale/tag/parents.rb +22 -0
- data/lib/i18n/locale/tag/rfc4646.rb +74 -0
- data/lib/i18n/locale/tag/simple.rb +39 -0
- data/lib/i18n/middleware.rb +17 -0
- data/lib/i18n/tests.rb +14 -0
- data/lib/i18n/tests/basics.rb +60 -0
- data/lib/i18n/tests/defaults.rb +52 -0
- data/lib/i18n/tests/interpolation.rb +159 -0
- data/lib/i18n/tests/link.rb +56 -0
- data/lib/i18n/tests/localization.rb +19 -0
- data/lib/i18n/tests/localization/date.rb +117 -0
- data/lib/i18n/tests/localization/date_time.rb +103 -0
- data/lib/i18n/tests/localization/procs.rb +116 -0
- data/lib/i18n/tests/localization/time.rb +103 -0
- data/lib/i18n/tests/lookup.rb +81 -0
- data/lib/i18n/tests/pluralization.rb +35 -0
- data/lib/i18n/tests/procs.rb +55 -0
- data/lib/i18n/version.rb +5 -0
- metadata +124 -0
data/lib/i18n/backend.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
module Backend
|
5
|
+
autoload :Base, 'i18n/backend/base'
|
6
|
+
autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
|
7
|
+
autoload :Cache, 'i18n/backend/cache'
|
8
|
+
autoload :CacheFile, 'i18n/backend/cache_file'
|
9
|
+
autoload :Cascade, 'i18n/backend/cascade'
|
10
|
+
autoload :Chain, 'i18n/backend/chain'
|
11
|
+
autoload :Fallbacks, 'i18n/backend/fallbacks'
|
12
|
+
autoload :Flatten, 'i18n/backend/flatten'
|
13
|
+
autoload :Gettext, 'i18n/backend/gettext'
|
14
|
+
autoload :KeyValue, 'i18n/backend/key_value'
|
15
|
+
autoload :Memoize, 'i18n/backend/memoize'
|
16
|
+
autoload :Metadata, 'i18n/backend/metadata'
|
17
|
+
autoload :Pluralization, 'i18n/backend/pluralization'
|
18
|
+
autoload :Simple, 'i18n/backend/simple'
|
19
|
+
autoload :Transliterator, 'i18n/backend/transliterator'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
require 'i18n/core_ext/hash'
|
6
|
+
|
7
|
+
module I18n
|
8
|
+
module Backend
|
9
|
+
module Base
|
10
|
+
using I18n::HashRefinements
|
11
|
+
include I18n::Backend::Transliterator
|
12
|
+
|
13
|
+
# Accepts a list of paths to translation files. Loads translations from
|
14
|
+
# plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See #load_rb, #load_yml, and #load_json
|
15
|
+
# for details.
|
16
|
+
def load_translations(*filenames)
|
17
|
+
filenames = I18n.load_path if filenames.empty?
|
18
|
+
filenames.flatten.each { |filename| load_file(filename) }
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method receives a locale, a data hash and options for storing translations.
|
22
|
+
# Should be implemented
|
23
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def translate(locale, key, options = EMPTY_HASH)
|
28
|
+
raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty?
|
29
|
+
raise InvalidLocale.new(locale) unless locale
|
30
|
+
return nil if key.nil? && !options.key?(:default)
|
31
|
+
|
32
|
+
entry = lookup(locale, key, options[:scope], options) unless key.nil?
|
33
|
+
|
34
|
+
if entry.nil? && options.key?(:default)
|
35
|
+
entry = default(locale, key, options[:default], options)
|
36
|
+
else
|
37
|
+
entry = resolve(locale, key, entry, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
count = options[:count]
|
41
|
+
|
42
|
+
if entry.nil? && (subtrees? || !count)
|
43
|
+
if (options.key?(:default) && !options[:default].nil?) || !options.key?(:default)
|
44
|
+
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
entry = entry.dup if entry.is_a?(String)
|
49
|
+
entry = pluralize(locale, entry, count) if count
|
50
|
+
|
51
|
+
if entry.nil? && !subtrees?
|
52
|
+
throw(:exception, I18n::MissingTranslation.new(locale, key, options))
|
53
|
+
end
|
54
|
+
|
55
|
+
deep_interpolation = options[:deep_interpolation]
|
56
|
+
values = options.except(*RESERVED_KEYS)
|
57
|
+
if values
|
58
|
+
entry = if deep_interpolation
|
59
|
+
deep_interpolate(locale, entry, values)
|
60
|
+
else
|
61
|
+
interpolate(locale, entry, values)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
entry
|
65
|
+
end
|
66
|
+
|
67
|
+
def exists?(locale, key)
|
68
|
+
lookup(locale, key) != nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Acts the same as +strftime+, but uses a localized version of the
|
72
|
+
# format string. Takes a key from the date/time formats translations as
|
73
|
+
# a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
|
74
|
+
def localize(locale, object, format = :default, options = EMPTY_HASH)
|
75
|
+
if object.nil? && options.include?(:default)
|
76
|
+
return options[:default]
|
77
|
+
end
|
78
|
+
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
|
79
|
+
|
80
|
+
if Symbol === format
|
81
|
+
key = format
|
82
|
+
type = object.respond_to?(:sec) ? 'time' : 'date'
|
83
|
+
options = options.merge(:raise => true, :object => object, :locale => locale)
|
84
|
+
format = I18n.t(:"#{type}.formats.#{key}", options)
|
85
|
+
end
|
86
|
+
|
87
|
+
format = translate_localization_format(locale, object, format, options)
|
88
|
+
object.strftime(format)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns an array of locales for which translations are available
|
92
|
+
# ignoring the reserved translation meta data key :i18n.
|
93
|
+
def available_locales
|
94
|
+
raise NotImplementedError
|
95
|
+
end
|
96
|
+
|
97
|
+
def reload!
|
98
|
+
eager_load! if eager_loaded?
|
99
|
+
end
|
100
|
+
|
101
|
+
def eager_load!
|
102
|
+
@eager_loaded = true
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def eager_loaded?
|
108
|
+
@eager_loaded ||= false
|
109
|
+
end
|
110
|
+
|
111
|
+
# The method which actually looks up for the translation in the store.
|
112
|
+
def lookup(locale, key, scope = [], options = EMPTY_HASH)
|
113
|
+
raise NotImplementedError
|
114
|
+
end
|
115
|
+
|
116
|
+
def subtrees?
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
# Evaluates defaults.
|
121
|
+
# If given subject is an Array, it walks the array and returns the
|
122
|
+
# first translation that can be resolved. Otherwise it tries to resolve
|
123
|
+
# the translation directly.
|
124
|
+
def default(locale, object, subject, options = EMPTY_HASH)
|
125
|
+
options = options.dup.reject { |key, value| key == :default }
|
126
|
+
case subject
|
127
|
+
when Array
|
128
|
+
subject.each do |item|
|
129
|
+
result = resolve(locale, object, item, options)
|
130
|
+
return result unless result.nil?
|
131
|
+
end and nil
|
132
|
+
else
|
133
|
+
resolve(locale, object, subject, options)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Resolves a translation.
|
138
|
+
# If the given subject is a Symbol, it will be translated with the
|
139
|
+
# given options. If it is a Proc then it will be evaluated. All other
|
140
|
+
# subjects will be returned directly.
|
141
|
+
def resolve(locale, object, subject, options = EMPTY_HASH)
|
142
|
+
return subject if options[:resolve] == false
|
143
|
+
result = catch(:exception) do
|
144
|
+
case subject
|
145
|
+
when Symbol
|
146
|
+
I18n.translate(subject, options.merge(:locale => locale, :throw => true))
|
147
|
+
when Proc
|
148
|
+
date_or_time = options.delete(:object) || object
|
149
|
+
resolve(locale, object, subject.call(date_or_time, options))
|
150
|
+
else
|
151
|
+
subject
|
152
|
+
end
|
153
|
+
end
|
154
|
+
result unless result.is_a?(MissingTranslation)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Picks a translation from a pluralized mnemonic subkey according to English
|
158
|
+
# pluralization rules :
|
159
|
+
# - It will pick the :one subkey if count is equal to 1.
|
160
|
+
# - It will pick the :other subkey otherwise.
|
161
|
+
# - It will pick the :zero subkey in the special case where count is
|
162
|
+
# equal to 0 and there is a :zero subkey present. This behaviour is
|
163
|
+
# not standard with regards to the CLDR pluralization rules.
|
164
|
+
# Other backends can implement more flexible or complex pluralization rules.
|
165
|
+
def pluralize(locale, entry, count)
|
166
|
+
return entry unless entry.is_a?(Hash) && count
|
167
|
+
|
168
|
+
key = pluralization_key(entry, count)
|
169
|
+
raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
|
170
|
+
entry[key]
|
171
|
+
end
|
172
|
+
|
173
|
+
# Interpolates values into a given subject.
|
174
|
+
#
|
175
|
+
# if the given subject is a string then:
|
176
|
+
# method interpolates "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
|
177
|
+
# # => "file test.txt opened by %{user}"
|
178
|
+
#
|
179
|
+
# if the given subject is an array then:
|
180
|
+
# each element of the array is recursively interpolated (until it finds a string)
|
181
|
+
# method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz"
|
182
|
+
# # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]"
|
183
|
+
def interpolate(locale, subject, values = EMPTY_HASH)
|
184
|
+
return subject if values.empty?
|
185
|
+
|
186
|
+
case subject
|
187
|
+
when ::String then I18n.interpolate(subject, values)
|
188
|
+
when ::Array then subject.map { |element| interpolate(locale, element, values) }
|
189
|
+
else
|
190
|
+
subject
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Deep interpolation
|
195
|
+
#
|
196
|
+
# deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
|
197
|
+
# ann: 'good', john: 'big'
|
198
|
+
# #=> { people: { ann: "Ann is good", john: "John is big" } }
|
199
|
+
def deep_interpolate(locale, data, values = EMPTY_HASH)
|
200
|
+
return data if values.empty?
|
201
|
+
|
202
|
+
case data
|
203
|
+
when ::String
|
204
|
+
I18n.interpolate(data, values)
|
205
|
+
when ::Hash
|
206
|
+
data.each_with_object({}) do |(k, v), result|
|
207
|
+
result[k] = deep_interpolate(locale, v, values)
|
208
|
+
end
|
209
|
+
when ::Array
|
210
|
+
data.map do |v|
|
211
|
+
deep_interpolate(locale, v, values)
|
212
|
+
end
|
213
|
+
else
|
214
|
+
data
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Loads a single translations file by delegating to #load_rb or
|
219
|
+
# #load_yml depending on the file extension and directly merges the
|
220
|
+
# data to the existing translations. Raises I18n::UnknownFileType
|
221
|
+
# for all other file extensions.
|
222
|
+
def load_file(filename)
|
223
|
+
type = File.extname(filename).tr('.', '').downcase
|
224
|
+
raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
|
225
|
+
data = send(:"load_#{type}", filename)
|
226
|
+
unless data.is_a?(Hash)
|
227
|
+
raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
|
228
|
+
end
|
229
|
+
data.each { |locale, d| store_translations(locale, d || {}) }
|
230
|
+
end
|
231
|
+
|
232
|
+
# Loads a plain Ruby translations file. eval'ing the file must yield
|
233
|
+
# a Hash containing translation data with locales as toplevel keys.
|
234
|
+
def load_rb(filename)
|
235
|
+
eval(IO.read(filename), binding, filename)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Loads a YAML translations file. The data must have locales as
|
239
|
+
# toplevel keys.
|
240
|
+
def load_yml(filename)
|
241
|
+
begin
|
242
|
+
YAML.load_file(filename)
|
243
|
+
rescue TypeError, ScriptError, StandardError => e
|
244
|
+
raise InvalidLocaleData.new(filename, e.inspect)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
alias_method :load_yaml, :load_yml
|
248
|
+
|
249
|
+
# Loads a JSON translations file. The data must have locales as
|
250
|
+
# toplevel keys.
|
251
|
+
def load_json(filename)
|
252
|
+
begin
|
253
|
+
::JSON.parse(File.read(filename))
|
254
|
+
rescue TypeError, StandardError => e
|
255
|
+
raise InvalidLocaleData.new(filename, e.inspect)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def translate_localization_format(locale, object, format, options)
|
260
|
+
format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match|
|
261
|
+
case match
|
262
|
+
when '%a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
|
263
|
+
when '%^a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday].upcase
|
264
|
+
when '%A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday]
|
265
|
+
when '%^A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday].upcase
|
266
|
+
when '%b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
|
267
|
+
when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase
|
268
|
+
when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon]
|
269
|
+
when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase
|
270
|
+
when '%p' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
|
271
|
+
when '%P' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
|
272
|
+
end
|
273
|
+
end
|
274
|
+
rescue MissingTranslationData => e
|
275
|
+
e.message
|
276
|
+
end
|
277
|
+
|
278
|
+
def pluralization_key(entry, count)
|
279
|
+
key = :zero if count == 0 && entry.has_key?(:zero)
|
280
|
+
key ||= count == 1 ? :one : :other
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module allows you to easily cache all responses from the backend - thus
|
4
|
+
# speeding up the I18n aspects of your application quite a bit.
|
5
|
+
#
|
6
|
+
# To enable caching you can simply include the Cache module to the Simple
|
7
|
+
# backend - or whatever other backend you are using:
|
8
|
+
#
|
9
|
+
# I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
|
10
|
+
#
|
11
|
+
# You will also need to set a cache store implementation that you want to use:
|
12
|
+
#
|
13
|
+
# I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
|
14
|
+
#
|
15
|
+
# You can use any cache implementation you want that provides the same API as
|
16
|
+
# ActiveSupport::Cache (only the methods #fetch and #write are being used).
|
17
|
+
#
|
18
|
+
# The cache_key implementation by default assumes you pass values that return
|
19
|
+
# a valid key from #hash (see
|
20
|
+
# http://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
|
+
# http://ruby-doc.org/stdlib/libdoc/digest/rdoc/index.html):
|
23
|
+
#
|
24
|
+
# I18n.cache_key_digest = Digest::MD5.new
|
25
|
+
#
|
26
|
+
# If you use a lambda as a default value in your translation like this:
|
27
|
+
#
|
28
|
+
# I18n.t(:"date.order", :default => lambda {[:month, :day, :year]})
|
29
|
+
#
|
30
|
+
# Then you will always have a cache miss, because each time this method
|
31
|
+
# is called the lambda will have a different hash value. If you know
|
32
|
+
# the result of the lambda is a constant as in the example above, then
|
33
|
+
# to cache this you can make the lambda a constant, like this:
|
34
|
+
#
|
35
|
+
# DEFAULT_DATE_ORDER = lambda {[:month, :day, :year]}
|
36
|
+
# ...
|
37
|
+
# I18n.t(:"date.order", :default => DEFAULT_DATE_ORDER)
|
38
|
+
#
|
39
|
+
# If the lambda may result in different values for each call then consider
|
40
|
+
# also using the Memoize backend.
|
41
|
+
#
|
42
|
+
module I18n
|
43
|
+
class << self
|
44
|
+
@@cache_store = nil
|
45
|
+
@@cache_namespace = nil
|
46
|
+
@@cache_key_digest = nil
|
47
|
+
|
48
|
+
def cache_store
|
49
|
+
@@cache_store
|
50
|
+
end
|
51
|
+
|
52
|
+
def cache_store=(store)
|
53
|
+
@@cache_store = store
|
54
|
+
end
|
55
|
+
|
56
|
+
def cache_namespace
|
57
|
+
@@cache_namespace
|
58
|
+
end
|
59
|
+
|
60
|
+
def cache_namespace=(namespace)
|
61
|
+
@@cache_namespace = namespace
|
62
|
+
end
|
63
|
+
|
64
|
+
def cache_key_digest
|
65
|
+
@@cache_key_digest
|
66
|
+
end
|
67
|
+
|
68
|
+
def cache_key_digest=(key_digest)
|
69
|
+
@@cache_key_digest = key_digest
|
70
|
+
end
|
71
|
+
|
72
|
+
def perform_caching?
|
73
|
+
!cache_store.nil?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module Backend
|
78
|
+
# TODO Should the cache be cleared if new translations are stored?
|
79
|
+
module Cache
|
80
|
+
def translate(locale, key, options = EMPTY_HASH)
|
81
|
+
I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def fetch(cache_key, &block)
|
87
|
+
result = _fetch(cache_key, &block)
|
88
|
+
throw(:exception, result) if result.is_a?(MissingTranslation)
|
89
|
+
result = result.dup if result.frozen? rescue result
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
def _fetch(cache_key, &block)
|
94
|
+
result = I18n.cache_store.read(cache_key)
|
95
|
+
return result unless result.nil?
|
96
|
+
result = catch(:exception, &block)
|
97
|
+
I18n.cache_store.write(cache_key, result) unless result.is_a?(Proc)
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
def cache_key(locale, key, options)
|
102
|
+
# This assumes that only simple, native Ruby values are passed to I18n.translate.
|
103
|
+
"i18n/#{I18n.cache_namespace}/#{locale}/#{digest_item(key)}/#{digest_item(options)}"
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def digest_item(key)
|
109
|
+
I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.to_s.hash
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest/sha2'
|
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 = Digest::SHA2.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
|