i18n 0.4.0.beta → 0.4.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of i18n might be problematic. Click here for more details.
- data/CHANGELOG.textile +8 -0
- data/lib/i18n.rb +5 -88
- data/lib/i18n/backend.rb +2 -3
- data/lib/i18n/backend/active_record.rb +24 -18
- data/lib/i18n/backend/base.rb +35 -39
- data/lib/i18n/backend/cache.rb +1 -0
- data/lib/i18n/backend/chain.rb +3 -1
- data/lib/i18n/backend/flatten.rb +33 -20
- data/lib/i18n/backend/interpolation_compiler.rb +8 -4
- data/lib/i18n/backend/key_value.rb +40 -30
- data/lib/i18n/backend/memoize.rb +48 -0
- data/lib/i18n/backend/metadata.rb +4 -10
- data/lib/i18n/backend/transliterator.rb +9 -5
- data/lib/i18n/config.rb +84 -0
- data/lib/i18n/core_ext/hash.rb +29 -0
- data/lib/i18n/core_ext/string/interpolate.rb +3 -4
- data/lib/i18n/version.rb +1 -1
- metadata +6 -7
- data/lib/i18n/backend/fast.rb +0 -62
- data/lib/i18n/backend/helpers.rb +0 -25
- data/lib/i18n/core_ext/hash/except.rb +0 -8
- data/lib/i18n/core_ext/hash/slice.rb +0 -8
data/CHANGELOG.textile
CHANGED
@@ -4,6 +4,14 @@ h2. master
|
|
4
4
|
|
5
5
|
*
|
6
6
|
|
7
|
+
h2. 0.4.0.beta1 (2010-05-03)
|
8
|
+
|
9
|
+
* "Renamed Fast backend to Memoize backend":http://github.com/svenfuchs/i18n/commit/f7f7dc12c00a19d3876223771e14f8671ff313cd
|
10
|
+
|
11
|
+
* "Deprecate {{}} as interpolation syntax":http://github.com/svenfuchs/i18n/commit/8894ee521ef5788c415b625a6daf522af4c416e0
|
12
|
+
|
13
|
+
* "Allow nil translation to be stored again":http://github.com/svenfuchs/i18n/commit/f2074f1e82d10c2e9a801c8cc2f2a0c7c30703ba
|
14
|
+
|
7
15
|
h2. 0.4.0.beta (2010-04-30)
|
8
16
|
|
9
17
|
* "Added a KeyValue backend":http://github.com/svenfuchs/i18n/commit/28ca5f53ade7f545f8c0804e93564d4686b416a4
|
data/lib/i18n.rb
CHANGED
@@ -12,94 +12,11 @@ require 'i18n/core_ext/string/interpolate'
|
|
12
12
|
|
13
13
|
module I18n
|
14
14
|
autoload :Backend, 'i18n/backend'
|
15
|
-
autoload :
|
15
|
+
autoload :Config, 'i18n/config'
|
16
|
+
autoload :Gettext, 'i18n/gettext'
|
16
17
|
autoload :Locale, 'i18n/locale'
|
17
18
|
|
18
|
-
class Config
|
19
|
-
# The only configuration value that is not global and scoped to thread is :locale.
|
20
|
-
# It defaults to the default_locale.
|
21
|
-
def locale
|
22
|
-
@locale ||= default_locale
|
23
|
-
end
|
24
|
-
|
25
|
-
# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
|
26
|
-
def locale=(locale)
|
27
|
-
@locale = locale.to_sym rescue nil
|
28
|
-
end
|
29
|
-
|
30
|
-
# Returns the current backend. Defaults to +Backend::Simple+.
|
31
|
-
def backend
|
32
|
-
@@backend ||= Backend::Simple.new
|
33
|
-
end
|
34
|
-
|
35
|
-
# Sets the current backend. Used to set a custom backend.
|
36
|
-
def backend=(backend)
|
37
|
-
@@backend = backend
|
38
|
-
end
|
39
|
-
|
40
|
-
# Returns the current default locale. Defaults to :'en'
|
41
|
-
def default_locale
|
42
|
-
@@default_locale ||= :en
|
43
|
-
end
|
44
|
-
|
45
|
-
# Sets the current default locale. Used to set a custom default locale.
|
46
|
-
def default_locale=(locale)
|
47
|
-
@@default_locale = locale.to_sym rescue nil
|
48
|
-
end
|
49
|
-
|
50
|
-
# Returns an array of locales for which translations are available.
|
51
|
-
# Unless you explicitely set the these through I18n.available_locales=
|
52
|
-
# the call will be delegated to the backend and memoized on the I18n module.
|
53
|
-
def available_locales
|
54
|
-
@@available_locales ||= backend.available_locales
|
55
|
-
end
|
56
|
-
|
57
|
-
# Sets the available locales.
|
58
|
-
def available_locales=(locales)
|
59
|
-
@@available_locales = locales
|
60
|
-
end
|
61
|
-
|
62
|
-
# Returns the current default scope separator. Defaults to '.'
|
63
|
-
def default_separator
|
64
|
-
@@default_separator ||= '.'
|
65
|
-
end
|
66
|
-
|
67
|
-
# Sets the current default scope separator.
|
68
|
-
def default_separator=(separator)
|
69
|
-
@@default_separator = separator
|
70
|
-
end
|
71
|
-
|
72
|
-
# Return the current exception handler. Defaults to :default_exception_handler.
|
73
|
-
def exception_handler
|
74
|
-
@@exception_handler ||= :default_exception_handler
|
75
|
-
end
|
76
|
-
|
77
|
-
# Sets the exception handler.
|
78
|
-
def exception_handler=(exception_handler)
|
79
|
-
@@exception_handler = exception_handler
|
80
|
-
end
|
81
|
-
|
82
|
-
# Allow clients to register paths providing translation data sources. The
|
83
|
-
# backend defines acceptable sources.
|
84
|
-
#
|
85
|
-
# E.g. the provided SimpleBackend accepts a list of paths to translation
|
86
|
-
# files which are either named *.rb and contain plain Ruby Hashes or are
|
87
|
-
# named *.yml and contain YAML data. So for the SimpleBackend clients may
|
88
|
-
# register translation files like this:
|
89
|
-
# I18n.load_path << 'path/to/locale/en.yml'
|
90
|
-
def load_path
|
91
|
-
@@load_path ||= []
|
92
|
-
end
|
93
|
-
|
94
|
-
# Sets the load path instance. Custom implementations are expected to
|
95
|
-
# behave like a Ruby Array.
|
96
|
-
def load_path=(load_path)
|
97
|
-
@@load_path = load_path
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
19
|
class << self
|
102
|
-
|
103
20
|
# Gets I18n configuration object.
|
104
21
|
def config
|
105
22
|
Thread.current[:i18n_config] ||= I18n::Config.new
|
@@ -163,7 +80,7 @@ module I18n
|
|
163
80
|
# values passed to #translate as part of the options hash, with the keys matching
|
164
81
|
# the interpolation variable names.
|
165
82
|
#
|
166
|
-
# <em>E.g.</em>, with a translation <tt>:foo => "foo {
|
83
|
+
# <em>E.g.</em>, with a translation <tt>:foo => "foo %{bar}"</tt> the option
|
167
84
|
# value for the key +bar+ will be interpolated into the translation:
|
168
85
|
# I18n.t :foo, :bar => 'baz' # => 'foo baz'
|
169
86
|
#
|
@@ -184,7 +101,7 @@ module I18n
|
|
184
101
|
#
|
185
102
|
# The <tt>:count</tt> option can be used both for pluralization and interpolation.
|
186
103
|
# <em>E.g.</em>, with the translation
|
187
|
-
# <tt>:foo => ['{
|
104
|
+
# <tt>:foo => ['%{count} foo', '%{count} foos']</tt>, count will
|
188
105
|
# be interpolated to the pluralized translation:
|
189
106
|
# I18n.t :foo, :count => 1 # => '1 foo'
|
190
107
|
#
|
@@ -218,7 +135,7 @@ module I18n
|
|
218
135
|
# called and passed the key and options.
|
219
136
|
#
|
220
137
|
# E.g. assuming the key <tt>:salutation</tt> resolves to:
|
221
|
-
# lambda { |key, options| options[:gender] == 'm' ? "Mr. {
|
138
|
+
# lambda { |key, options| options[:gender] == 'm' ? "Mr. %{options[:name]}" : "Mrs. %{options[:name]}" }
|
222
139
|
#
|
223
140
|
# Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith".
|
224
141
|
#
|
data/lib/i18n/backend.rb
CHANGED
@@ -2,17 +2,16 @@ module I18n
|
|
2
2
|
module Backend
|
3
3
|
autoload :ActiveRecord, 'i18n/backend/active_record'
|
4
4
|
autoload :Base, 'i18n/backend/base'
|
5
|
+
autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
|
5
6
|
autoload :Cache, 'i18n/backend/cache'
|
6
7
|
autoload :Cascade, 'i18n/backend/cascade'
|
7
8
|
autoload :Chain, 'i18n/backend/chain'
|
8
9
|
autoload :Cldr, 'i18n/backend/cldr'
|
9
10
|
autoload :Fallbacks, 'i18n/backend/fallbacks'
|
10
|
-
autoload :Fast, 'i18n/backend/fast'
|
11
11
|
autoload :Flatten, 'i18n/backend/flatten'
|
12
12
|
autoload :Gettext, 'i18n/backend/gettext'
|
13
|
-
autoload :Helpers, 'i18n/backend/helpers'
|
14
|
-
autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
|
15
13
|
autoload :KeyValue, 'i18n/backend/key_value'
|
14
|
+
autoload :Memoize, 'i18n/backend/memoize'
|
16
15
|
autoload :Metadata, 'i18n/backend/metadata'
|
17
16
|
autoload :Pluralization, 'i18n/backend/pluralization'
|
18
17
|
autoload :Simple, 'i18n/backend/simple'
|
@@ -8,23 +8,33 @@ module I18n
|
|
8
8
|
autoload :StoreProcs, 'i18n/backend/active_record/store_procs'
|
9
9
|
autoload :Translation, 'i18n/backend/active_record/translation'
|
10
10
|
|
11
|
-
|
11
|
+
module Implementation
|
12
|
+
include Base, Flatten
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
def reload!
|
15
|
+
end
|
16
|
+
|
17
|
+
def available_locales
|
18
|
+
init_translations unless initialized?
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
begin
|
21
|
+
Translation.available_locales
|
22
|
+
rescue ::ActiveRecord::StatementInvalid
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def store_translations(locale, data, options = {})
|
28
|
+
flatten_translations(locale, data).each do |key, value|
|
29
|
+
Translation.locale(locale).lookup(expand_keys(key)).delete_all
|
30
|
+
Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
|
31
|
+
end
|
21
32
|
end
|
22
|
-
end
|
23
33
|
|
24
34
|
protected
|
25
35
|
|
26
36
|
def lookup(locale, key, scope = [], options = {})
|
27
|
-
key =
|
37
|
+
key = normalize_flat_keys(locale, key, scope, options[:separator])
|
28
38
|
result = Translation.locale(locale).lookup(key).all
|
29
39
|
|
30
40
|
if result.empty?
|
@@ -37,14 +47,7 @@ module I18n
|
|
37
47
|
hash[r.key.slice(chop_range)] = r.value
|
38
48
|
hash
|
39
49
|
end
|
40
|
-
deep_symbolize_keys
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def merge_translations(locale, data, options = {})
|
45
|
-
flatten_translations(locale, data).each do |key, value|
|
46
|
-
Translation.locale(locale).lookup(expand_keys(key)).delete_all
|
47
|
-
Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
|
50
|
+
result.deep_symbolize_keys
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
@@ -54,6 +57,9 @@ module I18n
|
|
54
57
|
keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
|
55
58
|
end
|
56
59
|
end
|
60
|
+
end
|
61
|
+
|
62
|
+
include Implementation
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|
data/lib/i18n/backend/base.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
-
require 'i18n/core_ext/hash
|
4
|
+
require 'i18n/core_ext/hash'
|
5
5
|
|
6
6
|
module I18n
|
7
7
|
module Backend
|
8
8
|
module Base
|
9
|
-
include I18n::Backend::
|
9
|
+
include I18n::Backend::Transliterator
|
10
10
|
|
11
11
|
RESERVED_KEYS = [:scope, :default, :separator, :resolve]
|
12
|
+
RESERVED_KEYS_PATTERN = /%?%\{(#{RESERVED_KEYS.join("|")})\}/
|
12
13
|
INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
|
13
14
|
|
14
15
|
# Accepts a list of paths to translation files. Loads translations from
|
@@ -23,7 +24,11 @@ module I18n
|
|
23
24
|
# translations will be overwritten by new ones only at the deepest
|
24
25
|
# level of the hash.
|
25
26
|
def store_translations(locale, data, options = {})
|
26
|
-
|
27
|
+
locale = locale.to_sym
|
28
|
+
translations[locale] ||= {}
|
29
|
+
|
30
|
+
data = data.deep_symbolize_keys
|
31
|
+
translations[locale].deep_merge!(data)
|
27
32
|
end
|
28
33
|
|
29
34
|
def translate(locale, key, options = {})
|
@@ -34,28 +39,19 @@ module I18n
|
|
34
39
|
|
35
40
|
if options.empty?
|
36
41
|
entry = resolve(locale, key, entry, options)
|
37
|
-
raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
|
38
42
|
else
|
39
43
|
count, default = options.values_at(:count, :default)
|
40
|
-
values = options.
|
41
|
-
|
42
|
-
|
43
|
-
raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
|
44
|
-
|
45
|
-
entry = pluralize(locale, entry, count) if count
|
46
|
-
entry = interpolate(locale, entry, values)
|
44
|
+
values = options.except(*RESERVED_KEYS)
|
45
|
+
entry = entry.nil? && default ?
|
46
|
+
default(locale, key, default, options) : resolve(locale, key, entry, options)
|
47
47
|
end
|
48
48
|
|
49
|
-
entry
|
50
|
-
|
49
|
+
raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
|
50
|
+
entry = entry.dup if entry.is_a?(String)
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@transliterators ||= {}
|
56
|
-
@transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule',
|
57
|
-
:locale => locale, :resolve => false, :default => {})
|
58
|
-
@transliterators[locale].transliterate(string, replacement)
|
52
|
+
entry = pluralize(locale, entry, count) if count
|
53
|
+
entry = interpolate(locale, entry, values) if values
|
54
|
+
entry
|
59
55
|
end
|
60
56
|
|
61
57
|
# Acts the same as +strftime+, but uses a localized version of the
|
@@ -101,9 +97,11 @@ module I18n
|
|
101
97
|
def reload!
|
102
98
|
@initialized = false
|
103
99
|
@translations = nil
|
100
|
+
@skip_syntax_deprecation = false
|
104
101
|
end
|
105
102
|
|
106
103
|
protected
|
104
|
+
|
107
105
|
def init_translations
|
108
106
|
load_translations(*I18n.load_path.flatten)
|
109
107
|
@initialized = true
|
@@ -134,7 +132,7 @@ module I18n
|
|
134
132
|
return nil unless result.is_a?(Hash) && result.has_key?(key)
|
135
133
|
result = result[key]
|
136
134
|
result = resolve(locale, key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
|
137
|
-
|
135
|
+
result
|
138
136
|
end
|
139
137
|
end
|
140
138
|
|
@@ -187,8 +185,8 @@ module I18n
|
|
187
185
|
|
188
186
|
# Interpolates values into a given string.
|
189
187
|
#
|
190
|
-
# interpolate "file {
|
191
|
-
# # => "file test.txt opened by {
|
188
|
+
# interpolate "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
|
189
|
+
# # => "file test.txt opened by %{user}"
|
192
190
|
#
|
193
191
|
# Note that you have to double escape the <tt>\\</tt> when you want to escape
|
194
192
|
# the <tt>{{...}}</tt> key in a string (once for the string and once for the
|
@@ -197,28 +195,30 @@ module I18n
|
|
197
195
|
return string unless string.is_a?(::String) && !values.empty?
|
198
196
|
|
199
197
|
preserve_encoding(string) do
|
200
|
-
|
198
|
+
string = string.gsub(INTERPOLATION_SYNTAX_PATTERN) do
|
201
199
|
escaped, key = $1, $2.to_sym
|
202
200
|
if escaped
|
203
201
|
"{{#{key}}}"
|
204
|
-
elsif RESERVED_KEYS.include?(key)
|
205
|
-
raise ReservedInterpolationKey.new(key, string)
|
206
202
|
else
|
203
|
+
warn_syntax_deprecation!
|
207
204
|
"%{#{key}}"
|
208
205
|
end
|
209
206
|
end
|
210
207
|
|
211
208
|
values.each do |key, value|
|
212
|
-
value = value.call(values) if interpolate_lambda?(value,
|
209
|
+
value = value.call(values) if interpolate_lambda?(value, string, key)
|
213
210
|
value = value.to_s unless value.is_a?(::String)
|
214
211
|
values[key] = value
|
215
212
|
end
|
216
213
|
|
217
|
-
|
214
|
+
string % values
|
218
215
|
end
|
219
|
-
|
220
216
|
rescue KeyError => e
|
221
|
-
|
217
|
+
if string =~ RESERVED_KEYS_PATTERN
|
218
|
+
raise ReservedInterpolationKey.new($1.to_sym, string)
|
219
|
+
else
|
220
|
+
raise MissingInterpolationArgument.new(values, string)
|
221
|
+
end
|
222
222
|
end
|
223
223
|
|
224
224
|
def preserve_encoding(string)
|
@@ -246,7 +246,7 @@ module I18n
|
|
246
246
|
type = File.extname(filename).tr('.', '').downcase
|
247
247
|
raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
|
248
248
|
data = send(:"load_#{type}", filename) # TODO raise a meaningful exception if this does not yield a Hash
|
249
|
-
data.each { |locale, d|
|
249
|
+
data.each { |locale, d| store_translations(locale, d) }
|
250
250
|
end
|
251
251
|
|
252
252
|
# Loads a plain Ruby translations file. eval'ing the file must yield
|
@@ -261,14 +261,10 @@ module I18n
|
|
261
261
|
YAML::load(IO.read(filename))
|
262
262
|
end
|
263
263
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
translations[locale] ||= {}
|
269
|
-
|
270
|
-
data = deep_symbolize_keys(data)
|
271
|
-
deep_merge_hash!(translations[locale], data)
|
264
|
+
def warn_syntax_deprecation! #:nodoc:
|
265
|
+
return if @skip_syntax_deprecation
|
266
|
+
warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{caller.join("\n")}"
|
267
|
+
@skip_syntax_deprecation = true
|
272
268
|
end
|
273
269
|
end
|
274
270
|
end
|
data/lib/i18n/backend/cache.rb
CHANGED
data/lib/i18n/backend/chain.rb
CHANGED
@@ -16,7 +16,9 @@ module I18n
|
|
16
16
|
#
|
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
|
-
class Chain
|
19
|
+
class Chain
|
20
|
+
include Base
|
21
|
+
|
20
22
|
attr_accessor :backends
|
21
23
|
|
22
24
|
def initialize(*backends)
|
data/lib/i18n/backend/flatten.rb
CHANGED
@@ -12,6 +12,35 @@ module I18n
|
|
12
12
|
SEPARATOR_ESCAPE_CHAR = "\001"
|
13
13
|
FLATTEN_SEPARATOR = "."
|
14
14
|
|
15
|
+
# normalize_keys the flatten way. This method is significantly faster
|
16
|
+
# and creates way less objects than the one at I18n.normalize_keys.
|
17
|
+
# It also handles escaping the translation keys.
|
18
|
+
def self.normalize_flat_keys(locale, key, scope, separator)
|
19
|
+
keys = [scope, key].flatten.compact
|
20
|
+
separator ||= I18n.default_separator
|
21
|
+
|
22
|
+
if separator != FLATTEN_SEPARATOR
|
23
|
+
keys.map! do |k|
|
24
|
+
k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
|
25
|
+
"#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
keys.join(".")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Receives a string and escape the default separator.
|
33
|
+
def self.escape_default_separator(key) #:nodoc:
|
34
|
+
key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Shortcut to I18n::Backend::Flatten.normalize_flat_keys
|
38
|
+
# and then resolve_links.
|
39
|
+
def normalize_flat_keys(locale, key, scope, separator)
|
40
|
+
key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, separator)
|
41
|
+
resolve_link(locale, key)
|
42
|
+
end
|
43
|
+
|
15
44
|
# Store flattened links.
|
16
45
|
def links
|
17
46
|
@links ||= Hash.new { |h,k| h[k] = {} }
|
@@ -50,23 +79,6 @@ module I18n
|
|
50
79
|
hash
|
51
80
|
end
|
52
81
|
|
53
|
-
# normalize_keys the flatten way. This method is significantly faster
|
54
|
-
# and creates way less objects than the one at I18n.normalize_keys.
|
55
|
-
# It also handles escaping the translation keys.
|
56
|
-
def normalize_keys(locale, key, scope, separator)
|
57
|
-
keys = [scope, key].flatten.compact
|
58
|
-
separator ||= I18n.default_separator
|
59
|
-
|
60
|
-
if separator != FLATTEN_SEPARATOR
|
61
|
-
keys.map! do |k|
|
62
|
-
k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
|
63
|
-
"#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
resolve_link(locale, keys.join("."))
|
68
|
-
end
|
69
|
-
|
70
82
|
protected
|
71
83
|
|
72
84
|
def store_link(locale, key, link)
|
@@ -86,15 +98,16 @@ module I18n
|
|
86
98
|
end
|
87
99
|
end
|
88
100
|
|
89
|
-
def find_link(locale, key)
|
101
|
+
def find_link(locale, key) #:nodoc:
|
90
102
|
links[locale].each do |from, to|
|
91
103
|
return [from, to] if key[0, from.length] == from
|
92
104
|
end && nil
|
93
105
|
end
|
94
106
|
|
95
|
-
def escape_default_separator(key)
|
96
|
-
|
107
|
+
def escape_default_separator(key) #:nodoc:
|
108
|
+
I18n::Backend::Flatten.escape_default_separator(key)
|
97
109
|
end
|
110
|
+
|
98
111
|
end
|
99
112
|
end
|
100
113
|
end
|
@@ -11,14 +11,18 @@
|
|
11
11
|
# InterpolationCompiler module to the Simple backend:
|
12
12
|
#
|
13
13
|
# I18n::Backend::Simple.send(:include, I18n::Backend::InterpolationCompiler)
|
14
|
+
#
|
15
|
+
# Note that InterpolationCompiler does not yield meaningful results and consequently
|
16
|
+
# should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
|
17
|
+
# (jRuby, Rubinius and 1.8.7).
|
14
18
|
module I18n
|
15
19
|
module Backend
|
16
20
|
module InterpolationCompiler
|
17
21
|
module Compiler
|
18
22
|
extend self
|
19
23
|
|
20
|
-
TOKENIZER = /(
|
21
|
-
INTERPOLATION_SYNTAX_PATTERN = /(
|
24
|
+
TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
|
25
|
+
INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
|
22
26
|
|
23
27
|
def compile_if_an_interpolation(string)
|
24
28
|
if interpolated_str?(string)
|
@@ -37,7 +41,7 @@ module I18n
|
|
37
41
|
end
|
38
42
|
|
39
43
|
protected
|
40
|
-
# tokenize("foo {
|
44
|
+
# tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
|
41
45
|
def tokenize(str)
|
42
46
|
str.split(TOKENIZER)
|
43
47
|
end
|
@@ -102,7 +106,7 @@ module I18n
|
|
102
106
|
end
|
103
107
|
end
|
104
108
|
|
105
|
-
def
|
109
|
+
def store_translations(locale, data, options = {})
|
106
110
|
compile_all_strings_in(data)
|
107
111
|
super
|
108
112
|
end
|
@@ -28,6 +28,12 @@ module I18n
|
|
28
28
|
# require 'rufus/tokyo/cabinet' # gem install rufus-tokyo
|
29
29
|
# I18n.backend = I18n::Backend::KeyValue.new(Rufus::Tokyo::Cabinet.new('*'))
|
30
30
|
#
|
31
|
+
# == Performance
|
32
|
+
#
|
33
|
+
# You may make this backend even faster by including the Memoize module.
|
34
|
+
# However, notice that you should properly clear the cache if you change
|
35
|
+
# values directly in the key-store.
|
36
|
+
#
|
31
37
|
# == Subtrees
|
32
38
|
#
|
33
39
|
# In most backends, you are allowed to retrieve part of a translation tree:
|
@@ -42,54 +48,58 @@ module I18n
|
|
42
48
|
# I18n::Backend::KeyValue.new(@store, false)
|
43
49
|
#
|
44
50
|
class KeyValue
|
45
|
-
|
51
|
+
module Implementation
|
52
|
+
attr_accessor :store
|
46
53
|
|
47
|
-
|
48
|
-
|
49
|
-
def initialize(store, subtrees=true)
|
50
|
-
@store, @subtrees = store, subtrees
|
51
|
-
end
|
54
|
+
include Base, Flatten
|
52
55
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
def available_locales
|
58
|
-
locales = @store.keys.map { |k| k =~ /\./; $` }
|
59
|
-
locales.uniq!
|
60
|
-
locales.compact!
|
61
|
-
locales.map! { |k| k.to_sym }
|
62
|
-
locales
|
63
|
-
end
|
64
|
-
|
65
|
-
protected
|
56
|
+
def initialize(store, subtrees=true)
|
57
|
+
@store, @subtrees = store, subtrees
|
58
|
+
end
|
66
59
|
|
67
|
-
|
68
|
-
|
69
|
-
value = @store["#{locale}.#{key}"]
|
70
|
-
value = ActiveSupport::JSON.decode(value) if value
|
71
|
-
value.is_a?(Hash) ? deep_symbolize_keys(value) : value
|
60
|
+
# Mute reload! since we really don't want to clean the database.
|
61
|
+
def reload!
|
72
62
|
end
|
73
63
|
|
74
|
-
def
|
64
|
+
def store_translations(locale, data, options = {})
|
75
65
|
flatten_translations(locale, data, @subtrees).each do |key, value|
|
76
66
|
key = "#{locale}.#{key}"
|
77
67
|
|
78
68
|
case value
|
79
69
|
when Hash
|
80
70
|
if @subtrees && (old_value = @store[key])
|
81
|
-
old_value = ActiveSupport::JSON.decode(old_value)
|
82
|
-
value =
|
71
|
+
old_value = ActiveSupport::JSON.decode(old_value)
|
72
|
+
value = old_value.deep_symbolize_keys.deep_merge!(value) if old_value.is_a?(Hash)
|
83
73
|
end
|
84
74
|
when Proc
|
85
75
|
raise "Key-value stores cannot handle procs"
|
86
|
-
when Symbol
|
87
|
-
value = nil
|
88
76
|
end
|
89
77
|
|
90
|
-
@store[key] = ActiveSupport::JSON.encode(value) unless value.
|
78
|
+
@store[key] = ActiveSupport::JSON.encode(value) unless value.is_a?(Symbol)
|
91
79
|
end
|
92
80
|
end
|
81
|
+
|
82
|
+
def available_locales
|
83
|
+
init_translations unless initialized?
|
84
|
+
|
85
|
+
locales = @store.keys.map { |k| k =~ /\./; $` }
|
86
|
+
locales.uniq!
|
87
|
+
locales.compact!
|
88
|
+
locales.map! { |k| k.to_sym }
|
89
|
+
locales
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
def lookup(locale, key, scope = [], options = {})
|
95
|
+
key = normalize_flat_keys(locale, key, scope, options[:separator])
|
96
|
+
value = @store["#{locale}.#{key}"]
|
97
|
+
value = ActiveSupport::JSON.decode(value) if value
|
98
|
+
value.is_a?(Hash) ? value.deep_symbolize_keys : value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
include Implementation
|
93
103
|
end
|
94
104
|
end
|
95
105
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Memoize module simply memoizes the values returned by lookup using
|
4
|
+
# a flat hash and can tremendously speed up the lookup process in a backend.
|
5
|
+
#
|
6
|
+
# To enable it you can simply include the Memoize module to your backend:
|
7
|
+
#
|
8
|
+
# I18n::Backend::Simple.send(:include, I18n::Backend::Memoize)
|
9
|
+
#
|
10
|
+
# Notice that it's the responsibility of the backend to define whenever the
|
11
|
+
# cache should be cleaned.
|
12
|
+
module I18n
|
13
|
+
module Backend
|
14
|
+
module Memoize
|
15
|
+
def available_locales
|
16
|
+
@memoized_locales ||= super
|
17
|
+
end
|
18
|
+
|
19
|
+
def store_translations(locale, data, options = {})
|
20
|
+
reset_memoizations!(locale)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def reload!
|
25
|
+
reset_memoizations!
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def lookup(locale, key, scope = nil, options = {})
|
32
|
+
flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale,
|
33
|
+
key, scope, options[:separator]).to_sym
|
34
|
+
flat_hash = memoized_lookup[locale.to_sym]
|
35
|
+
flat_hash.key?(flat_key) ? flat_hash[flat_key] : (flat_hash[flat_key] = super)
|
36
|
+
end
|
37
|
+
|
38
|
+
def memoized_lookup
|
39
|
+
@memoized_lookup ||= Hash.new { |h, k| h[k] = {} }
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset_memoizations!(locale=nil)
|
43
|
+
@memoized_locales = nil
|
44
|
+
(locale ? memoized_lookup[locale.to_sym] : memoized_lookup).clear
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -27,7 +27,7 @@ module I18n
|
|
27
27
|
def translation_metadata=(translation_metadata)
|
28
28
|
@translation_metadata = translation_metadata
|
29
29
|
end
|
30
|
-
end
|
30
|
+
end unless Object.method_defined?(:translation_metadata)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -43,10 +43,9 @@ module I18n
|
|
43
43
|
with_metadata(metadata) { super }
|
44
44
|
end
|
45
45
|
|
46
|
-
def interpolate(locale,
|
47
|
-
|
48
|
-
|
49
|
-
end if string
|
46
|
+
def interpolate(locale, entry, values = {})
|
47
|
+
metadata = entry.translation_metadata.merge(:original => entry)
|
48
|
+
with_metadata(metadata) { super }
|
50
49
|
end
|
51
50
|
|
52
51
|
def pluralize(locale, entry, count)
|
@@ -61,11 +60,6 @@ module I18n
|
|
61
60
|
result
|
62
61
|
end
|
63
62
|
|
64
|
-
def preserve_translation_metadata(object, &block)
|
65
|
-
result = yield
|
66
|
-
result.translation_metadata = object.translation_metadata if result
|
67
|
-
result
|
68
|
-
end
|
69
63
|
end
|
70
64
|
end
|
71
65
|
end
|
@@ -2,9 +2,17 @@
|
|
2
2
|
module I18n
|
3
3
|
module Backend
|
4
4
|
module Transliterator
|
5
|
-
|
6
5
|
DEFAULT_REPLACEMENT_CHAR = "?"
|
7
6
|
|
7
|
+
# Given a locale and a UTF-8 string, return the locale's ASCII
|
8
|
+
# approximation for the string.
|
9
|
+
def transliterate(locale, string, replacement = nil)
|
10
|
+
@transliterators ||= {}
|
11
|
+
@transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule',
|
12
|
+
:locale => locale, :resolve => false, :default => {})
|
13
|
+
@transliterators[locale].transliterate(string, replacement)
|
14
|
+
end
|
15
|
+
|
8
16
|
# Get a transliterator instance.
|
9
17
|
def self.get(rule = nil)
|
10
18
|
if !rule || rule.kind_of?(Hash)
|
@@ -18,7 +26,6 @@ module I18n
|
|
18
26
|
|
19
27
|
# A transliterator which accepts a Proc as its transliteration rule.
|
20
28
|
class ProcTransliterator
|
21
|
-
|
22
29
|
def initialize(rule)
|
23
30
|
@rule = rule
|
24
31
|
end
|
@@ -26,13 +33,11 @@ module I18n
|
|
26
33
|
def transliterate(string, replacement = nil)
|
27
34
|
@rule.call(string)
|
28
35
|
end
|
29
|
-
|
30
36
|
end
|
31
37
|
|
32
38
|
# A transliterator which accepts a Hash of characters as its translation
|
33
39
|
# rule.
|
34
40
|
class HashTransliterator
|
35
|
-
|
36
41
|
DEFAULT_APPROXIMATIONS = {
|
37
42
|
"À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE",
|
38
43
|
"Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
|
@@ -87,7 +92,6 @@ module I18n
|
|
87
92
|
hash.keys.each {|key| hash[key.to_s] = hash.delete(key).to_s}
|
88
93
|
approximations.merge! hash
|
89
94
|
end
|
90
|
-
|
91
95
|
end
|
92
96
|
end
|
93
97
|
end
|
data/lib/i18n/config.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module I18n
|
2
|
+
class Config
|
3
|
+
# The only configuration value that is not global and scoped to thread is :locale.
|
4
|
+
# It defaults to the default_locale.
|
5
|
+
def locale
|
6
|
+
@locale ||= default_locale
|
7
|
+
end
|
8
|
+
|
9
|
+
# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
|
10
|
+
def locale=(locale)
|
11
|
+
@locale = locale.to_sym rescue nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the current backend. Defaults to +Backend::Simple+.
|
15
|
+
def backend
|
16
|
+
@@backend ||= Backend::Simple.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets the current backend. Used to set a custom backend.
|
20
|
+
def backend=(backend)
|
21
|
+
@@backend = backend
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the current default locale. Defaults to :'en'
|
25
|
+
def default_locale
|
26
|
+
@@default_locale ||= :en
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the current default locale. Used to set a custom default locale.
|
30
|
+
def default_locale=(locale)
|
31
|
+
@@default_locale = locale.to_sym rescue nil
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns an array of locales for which translations are available.
|
35
|
+
# Unless you explicitely set the these through I18n.available_locales=
|
36
|
+
# the call will be delegated to the backend and memoized on the I18n module.
|
37
|
+
def available_locales
|
38
|
+
@@available_locales ||= backend.available_locales
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets the available locales.
|
42
|
+
def available_locales=(locales)
|
43
|
+
@@available_locales = locales
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the current default scope separator. Defaults to '.'
|
47
|
+
def default_separator
|
48
|
+
@@default_separator ||= '.'
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sets the current default scope separator.
|
52
|
+
def default_separator=(separator)
|
53
|
+
@@default_separator = separator
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the current exception handler. Defaults to :default_exception_handler.
|
57
|
+
def exception_handler
|
58
|
+
@@exception_handler ||= :default_exception_handler
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sets the exception handler.
|
62
|
+
def exception_handler=(exception_handler)
|
63
|
+
@@exception_handler = exception_handler
|
64
|
+
end
|
65
|
+
|
66
|
+
# Allow clients to register paths providing translation data sources. The
|
67
|
+
# backend defines acceptable sources.
|
68
|
+
#
|
69
|
+
# E.g. the provided SimpleBackend accepts a list of paths to translation
|
70
|
+
# files which are either named *.rb and contain plain Ruby Hashes or are
|
71
|
+
# named *.yml and contain YAML data. So for the SimpleBackend clients may
|
72
|
+
# register translation files like this:
|
73
|
+
# I18n.load_path << 'path/to/locale/en.yml'
|
74
|
+
def load_path
|
75
|
+
@@load_path ||= []
|
76
|
+
end
|
77
|
+
|
78
|
+
# Sets the load path instance. Custom implementations are expected to
|
79
|
+
# behave like a Ruby Array.
|
80
|
+
def load_path=(load_path)
|
81
|
+
@@load_path = load_path
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Hash
|
2
|
+
def slice(*keep_keys)
|
3
|
+
h = {}
|
4
|
+
keep_keys.each { |key| h[key] = fetch(key) }
|
5
|
+
h
|
6
|
+
end unless Hash.method_defined?(:slice)
|
7
|
+
|
8
|
+
def except(*less_keys)
|
9
|
+
slice(*keys - less_keys)
|
10
|
+
end unless Hash.method_defined?(:except)
|
11
|
+
|
12
|
+
def deep_symbolize_keys
|
13
|
+
inject({}) { |result, (key, value)|
|
14
|
+
value = value.deep_symbolize_keys if value.is_a?(Hash)
|
15
|
+
result[(key.to_sym rescue key) || key] = value
|
16
|
+
result
|
17
|
+
}
|
18
|
+
end unless Hash.method_defined?(:deep_symbolize_keys)
|
19
|
+
|
20
|
+
# deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
21
|
+
MERGER = proc do |key, v1, v2|
|
22
|
+
Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2
|
23
|
+
end
|
24
|
+
|
25
|
+
def deep_merge!(data)
|
26
|
+
merge!(data, &MERGER)
|
27
|
+
end unless Hash.method_defined?(:deep_merge!)
|
28
|
+
end
|
29
|
+
|
@@ -7,13 +7,13 @@
|
|
7
7
|
You may redistribute it and/or modify it under the same license terms as Ruby.
|
8
8
|
=end
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
begin
|
11
|
+
raise ArgumentError if ("a %{x}" % {:x=>'b'}) != 'a b'
|
12
|
+
rescue ArgumentError
|
12
13
|
# KeyError is raised by String#% when the string contains a named placeholder
|
13
14
|
# that is not contained in the given arguments hash. Ruby 1.9 includes and
|
14
15
|
# raises this exception natively. We define it to mimic Ruby 1.9's behaviour
|
15
16
|
# in Ruby 1.8.x
|
16
|
-
|
17
17
|
class KeyError < IndexError
|
18
18
|
def initialize(message = nil)
|
19
19
|
super(message || "key not found")
|
@@ -24,7 +24,6 @@ if RUBY_VERSION < '1.9'
|
|
24
24
|
#
|
25
25
|
# String#% method which accept "named argument". The translator can know
|
26
26
|
# the meaning of the msgids using "named argument" instead of %s/%d style.
|
27
|
-
|
28
27
|
class String
|
29
28
|
# For older ruby versions, such as ruby-1.8.5
|
30
29
|
alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'}
|
data/lib/i18n/version.rb
CHANGED
metadata
CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
|
|
6
6
|
- 0
|
7
7
|
- 4
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.4.0.
|
9
|
+
- beta1
|
10
|
+
version: 0.4.0.beta1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Sven Fuchs
|
@@ -19,7 +19,7 @@ autorequire:
|
|
19
19
|
bindir: bin
|
20
20
|
cert_chain: []
|
21
21
|
|
22
|
-
date: 2010-
|
22
|
+
date: 2010-05-03 00:00:00 +02:00
|
23
23
|
default_executable:
|
24
24
|
dependencies: []
|
25
25
|
|
@@ -44,18 +44,17 @@ files:
|
|
44
44
|
- lib/i18n/backend/chain.rb
|
45
45
|
- lib/i18n/backend/cldr.rb
|
46
46
|
- lib/i18n/backend/fallbacks.rb
|
47
|
-
- lib/i18n/backend/fast.rb
|
48
47
|
- lib/i18n/backend/flatten.rb
|
49
48
|
- lib/i18n/backend/gettext.rb
|
50
|
-
- lib/i18n/backend/helpers.rb
|
51
49
|
- lib/i18n/backend/interpolation_compiler.rb
|
52
50
|
- lib/i18n/backend/key_value.rb
|
51
|
+
- lib/i18n/backend/memoize.rb
|
53
52
|
- lib/i18n/backend/metadata.rb
|
54
53
|
- lib/i18n/backend/pluralization.rb
|
55
54
|
- lib/i18n/backend/simple.rb
|
56
55
|
- lib/i18n/backend/transliterator.rb
|
57
|
-
- lib/i18n/
|
58
|
-
- lib/i18n/core_ext/hash
|
56
|
+
- lib/i18n/config.rb
|
57
|
+
- lib/i18n/core_ext/hash.rb
|
59
58
|
- lib/i18n/core_ext/string/interpolate.rb
|
60
59
|
- lib/i18n/exceptions.rb
|
61
60
|
- lib/i18n/gettext.rb
|
data/lib/i18n/backend/fast.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
# The Fast module contains optimizations that can tremendously speed up the
|
4
|
-
# lookup process on the Simple backend. It works by flattening the nested
|
5
|
-
# translation hash to a flat hash (e.g. { :a => { :b => 'c' } } becomes
|
6
|
-
# { :'a.b' => 'c' }).
|
7
|
-
#
|
8
|
-
# To enable these optimizations you can simply include the Fast module to
|
9
|
-
# the Simple backend:
|
10
|
-
#
|
11
|
-
# I18n::Backend::Simple.send(:include, I18n::Backend::Fast)
|
12
|
-
module I18n
|
13
|
-
module Backend
|
14
|
-
module Fast
|
15
|
-
include Flatten
|
16
|
-
|
17
|
-
# Overwrite reload! to also clean up flattened translations.
|
18
|
-
def reload!
|
19
|
-
super
|
20
|
-
reset_flattened_translations!
|
21
|
-
end
|
22
|
-
|
23
|
-
protected
|
24
|
-
|
25
|
-
# Generate flattened translations after translations are initialized.
|
26
|
-
def init_translations
|
27
|
-
super
|
28
|
-
flattened_translations
|
29
|
-
end
|
30
|
-
|
31
|
-
def lookup(locale, key, scope = nil, options = {})
|
32
|
-
locale = locale.to_sym
|
33
|
-
return nil unless flattened_translations[locale]
|
34
|
-
|
35
|
-
key = normalize_keys(locale, key, scope, options[:separator]).to_sym
|
36
|
-
flattened_translations[locale][key]
|
37
|
-
end
|
38
|
-
|
39
|
-
# Store flattened translations in a variable.
|
40
|
-
def flattened_translations
|
41
|
-
@flattened_translations ||=
|
42
|
-
translations.inject({}) do |result, (locale, data)|
|
43
|
-
result[locale] = flatten_translations(locale, data, true)
|
44
|
-
result
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# Clean up flattened translations variable. Should be called whenever
|
49
|
-
# the internal hash is changed.
|
50
|
-
def reset_flattened_translations!
|
51
|
-
@flattened_translations = nil
|
52
|
-
end
|
53
|
-
|
54
|
-
# Overwrite merge_translations to clean up the internal hash so added
|
55
|
-
# translations are also cached.
|
56
|
-
def merge_translations(locale, data, options = {})
|
57
|
-
super
|
58
|
-
reset_flattened_translations!
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
data/lib/i18n/backend/helpers.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
module I18n
|
2
|
-
module Backend
|
3
|
-
module Helpers
|
4
|
-
# Return a new hash with all keys and nested keys converted to symbols.
|
5
|
-
def deep_symbolize_keys(hash)
|
6
|
-
hash.inject({}) { |result, (key, value)|
|
7
|
-
value = deep_symbolize_keys(value) if value.is_a?(Hash)
|
8
|
-
result[(key.to_sym rescue key) || key] = value
|
9
|
-
result
|
10
|
-
}
|
11
|
-
end
|
12
|
-
|
13
|
-
# deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
|
14
|
-
MERGER = proc do |key, v1, v2|
|
15
|
-
# TODO should probably be:
|
16
|
-
# raise TypeError.new("can't merge #{v1.inspect} and #{v2.inspect}") unless Hash === v1 && Hash === v2
|
17
|
-
Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : (v2 || v1)
|
18
|
-
end
|
19
|
-
|
20
|
-
def deep_merge_hash!(hash, data)
|
21
|
-
hash.merge!(data, &MERGER)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|