activesupport 2.3.8 → 2.3.9.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (66) hide show
  1. data/CHANGELOG +11 -0
  2. data/lib/active_support/core_ext/array/grouping.rb +1 -1
  3. data/lib/active_support/core_ext/array/random_access.rb +24 -4
  4. data/lib/active_support/core_ext/class.rb +1 -0
  5. data/lib/active_support/core_ext/class/attribute.rb +67 -0
  6. data/lib/active_support/core_ext/enumerable.rb +1 -1
  7. data/lib/active_support/core_ext/kernel/singleton_class.rb +13 -0
  8. data/lib/active_support/core_ext/module/remove_method.rb +6 -0
  9. data/lib/active_support/core_ext/object/misc.rb +3 -0
  10. data/lib/active_support/core_ext/range/blockless_step.rb +1 -1
  11. data/lib/active_support/core_ext/string/output_safety.rb +0 -11
  12. data/lib/active_support/dependencies.rb +36 -12
  13. data/lib/active_support/deprecation.rb +7 -0
  14. data/lib/active_support/json/backends/yajl.rb +1 -1
  15. data/lib/active_support/ordered_hash.rb +6 -0
  16. data/lib/active_support/testing/performance.rb +1 -1
  17. data/lib/active_support/values/time_zone.rb +5 -1
  18. data/lib/active_support/vendor.rb +2 -2
  19. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n.rb +92 -105
  20. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend.rb +5 -4
  21. data/lib/active_support/vendor/i18n-0.4.1/i18n/backend/active_record.rb +61 -0
  22. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/active_record/missing.rb +4 -6
  23. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/active_record/store_procs.rb +0 -0
  24. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/active_record/translation.rb +8 -3
  25. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/base.rb +55 -84
  26. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/cache.rb +1 -0
  27. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/cascade.rb +0 -1
  28. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/chain.rb +3 -1
  29. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/cldr.rb +0 -0
  30. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/fallbacks.rb +0 -0
  31. data/lib/active_support/vendor/i18n-0.4.1/i18n/backend/flatten.rb +113 -0
  32. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/gettext.rb +0 -0
  33. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/interpolation_compiler.rb +8 -4
  34. data/lib/active_support/vendor/i18n-0.4.1/i18n/backend/key_value.rb +102 -0
  35. data/lib/active_support/vendor/i18n-0.4.1/i18n/backend/memoize.rb +48 -0
  36. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/metadata.rb +5 -13
  37. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/backend/pluralization.rb +0 -0
  38. data/lib/active_support/vendor/i18n-0.4.1/i18n/backend/simple.rb +87 -0
  39. data/lib/active_support/vendor/i18n-0.4.1/i18n/backend/transliterator.rb +98 -0
  40. data/lib/active_support/vendor/i18n-0.4.1/i18n/config.rb +84 -0
  41. data/lib/active_support/vendor/i18n-0.4.1/i18n/core_ext/hash.rb +29 -0
  42. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/core_ext/string/interpolate.rb +3 -4
  43. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/exceptions.rb +0 -0
  44. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/gettext.rb +2 -0
  45. data/lib/active_support/vendor/{i18n-0.3.7/i18n/helpers/gettext.rb → i18n-0.4.1/i18n/gettext/helpers.rb} +2 -2
  46. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/gettext/po_parser.rb +0 -0
  47. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/locale.rb +0 -0
  48. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/locale/fallbacks.rb +0 -0
  49. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/locale/tag.rb +0 -0
  50. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/locale/tag/parents.rb +0 -0
  51. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/locale/tag/rfc4646.rb +0 -0
  52. data/lib/active_support/vendor/{i18n-0.3.7 → i18n-0.4.1}/i18n/locale/tag/simple.rb +0 -0
  53. data/lib/active_support/vendor/i18n-0.4.1/i18n/version.rb +3 -0
  54. data/lib/active_support/version.rb +1 -1
  55. data/lib/active_support/whiny_nil.rb +1 -1
  56. metadata +48 -43
  57. data/lib/active_support/vendor/i18n-0.3.7/i18n/backend/active_record.rb +0 -66
  58. data/lib/active_support/vendor/i18n-0.3.7/i18n/backend/fast.rb +0 -69
  59. data/lib/active_support/vendor/i18n-0.3.7/i18n/backend/helpers.rb +0 -68
  60. data/lib/active_support/vendor/i18n-0.3.7/i18n/backend/links.rb +0 -34
  61. data/lib/active_support/vendor/i18n-0.3.7/i18n/backend/simple.rb +0 -22
  62. data/lib/active_support/vendor/i18n-0.3.7/i18n/core_ext/hash/except.rb +0 -8
  63. data/lib/active_support/vendor/i18n-0.3.7/i18n/core_ext/hash/slice.rb +0 -8
  64. data/lib/active_support/vendor/i18n-0.3.7/i18n/core_ext/object/meta_class.rb +0 -5
  65. data/lib/active_support/vendor/i18n-0.3.7/i18n/helpers.rb +0 -5
  66. data/lib/active_support/vendor/i18n-0.3.7/i18n/version.rb +0 -3
@@ -2,18 +2,19 @@ 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
+ autoload :Flatten, 'i18n/backend/flatten'
11
12
  autoload :Gettext, 'i18n/backend/gettext'
12
- autoload :Helpers, 'i18n/backend/helpers'
13
- autoload :Links, 'i18n/backend/links'
14
- autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
13
+ autoload :KeyValue, 'i18n/backend/key_value'
14
+ autoload :Memoize, 'i18n/backend/memoize'
15
15
  autoload :Metadata, 'i18n/backend/metadata'
16
16
  autoload :Pluralization, 'i18n/backend/pluralization'
17
17
  autoload :Simple, 'i18n/backend/simple'
18
+ autoload :Transliterator, 'i18n/backend/transliterator'
18
19
  end
19
20
  end
@@ -0,0 +1,61 @@
1
+ require 'i18n/backend/base'
2
+ require 'i18n/backend/active_record/translation'
3
+
4
+ module I18n
5
+ module Backend
6
+ class ActiveRecord
7
+ autoload :Missing, 'i18n/backend/active_record/missing'
8
+ autoload :StoreProcs, 'i18n/backend/active_record/store_procs'
9
+ autoload :Translation, 'i18n/backend/active_record/translation'
10
+
11
+ module Implementation
12
+ include Base, Flatten
13
+
14
+ def available_locales
15
+ begin
16
+ Translation.available_locales
17
+ rescue ::ActiveRecord::StatementInvalid
18
+ []
19
+ end
20
+ end
21
+
22
+ def store_translations(locale, data, options = {})
23
+ escape = options.fetch(:escape, true)
24
+ flatten_translations(locale, data, escape, false).each do |key, value|
25
+ Translation.locale(locale).lookup(expand_keys(key)).delete_all
26
+ Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
27
+ end
28
+ end
29
+
30
+ protected
31
+
32
+ def lookup(locale, key, scope = [], options = {})
33
+ key = normalize_flat_keys(locale, key, scope, options[:separator])
34
+ result = Translation.locale(locale).lookup(key).all
35
+
36
+ if result.empty?
37
+ nil
38
+ elsif result.first.key == key
39
+ result.first.value
40
+ else
41
+ chop_range = (key.size + FLATTEN_SEPARATOR.size)..-1
42
+ result = result.inject({}) do |hash, r|
43
+ hash[r.key.slice(chop_range)] = r.value
44
+ hash
45
+ end
46
+ result.deep_symbolize_keys
47
+ end
48
+ end
49
+
50
+ # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
51
+ def expand_keys(key)
52
+ key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
53
+ keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
54
+ end
55
+ end
56
+ end
57
+
58
+ include Implementation
59
+ end
60
+ end
61
+ end
@@ -40,7 +40,7 @@ module I18n
40
40
  keys = I18n.normalize_keys(locale, key, scope, separator)[1..-1]
41
41
  key = keys.join(separator || I18n.default_separator)
42
42
 
43
- unless ActiveRecord::Translation.locale(locale).lookup(key, separator).exists?
43
+ unless ActiveRecord::Translation.locale(locale).lookup(key).exists?
44
44
  interpolations = options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }.keys
45
45
  keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(separator) } : [key]
46
46
  keys.each { |key| store_default_translation(locale, key, interpolations) }
@@ -55,11 +55,9 @@ module I18n
55
55
 
56
56
  def translate(locale, key, options = {})
57
57
  super
58
-
59
- rescue I18n::MissingTranslationData => e
60
- self.store_default_translations(locale, key, options)
61
-
62
- raise e
58
+ rescue I18n::MissingTranslationData => e
59
+ self.store_default_translations(locale, key, options)
60
+ raise e
63
61
  end
64
62
  end
65
63
  end
@@ -60,9 +60,14 @@ module I18n
60
60
 
61
61
  send scope_method, :lookup, lambda { |keys, *separator|
62
62
  column_name = connection.quote_column_name('key')
63
- keys = Array(keys).map! { |key| key.to_s }
64
- separator = separator.first || I18n.default_separator
65
- namespace = "#{keys.last}#{separator}%"
63
+ keys = Array(keys).map! { |key| key.to_s }
64
+
65
+ unless separator.empty?
66
+ warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
67
+ "You can change the internal separator by overwriting FLATTEN_SEPARATOR."
68
+ end
69
+
70
+ namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
66
71
  { :conditions => ["#{column_name} IN (?) OR #{column_name} LIKE ?", keys, namespace] }
67
72
  }
68
73
 
@@ -1,50 +1,52 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'yaml'
4
- require 'i18n/core_ext/hash/except'
4
+ require 'i18n/core_ext/hash'
5
5
 
6
6
  module I18n
7
7
  module Backend
8
8
  module Base
9
- include I18n::Backend::Helpers
9
+ include I18n::Backend::Transliterator
10
10
 
11
11
  RESERVED_KEYS = [:scope, :default, :separator, :resolve]
12
- INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
12
+ RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
13
+ DEPRECATED_INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
14
+ INTERPOLATION_SYNTAX_PATTERN = /%\{([^\}]+)\}/
13
15
 
14
16
  # Accepts a list of paths to translation files. Loads translations from
15
17
  # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
16
18
  # for details.
17
19
  def load_translations(*filenames)
20
+ filenames = I18n.load_path.flatten if filenames.empty?
18
21
  filenames.each { |filename| load_file(filename) }
19
22
  end
20
23
 
21
- # Stores translations for the given locale in memory.
22
- # This uses a deep merge for the translations hash, so existing
23
- # translations will be overwritten by new ones only at the deepest
24
- # level of the hash.
24
+ # This method receives a locale, a data hash and options for storing translations.
25
+ # Should be implemented
25
26
  def store_translations(locale, data, options = {})
26
- merge_translations(locale, data, options)
27
+ raise NotImplementedError
27
28
  end
28
29
 
29
30
  def translate(locale, key, options = {})
30
31
  raise InvalidLocale.new(locale) unless locale
31
32
  return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
32
33
 
34
+ entry = key && lookup(locale, key, options[:scope], options)
35
+
33
36
  if options.empty?
34
- entry = resolve(locale, key, lookup(locale, key), options)
35
- raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
37
+ entry = resolve(locale, key, entry, options)
36
38
  else
37
- count, scope, default = options.values_at(:count, :scope, :default)
38
- values = options.reject { |name, value| RESERVED_KEYS.include?(name) }
39
-
40
- entry = lookup(locale, key, scope, options)
41
- entry = entry.nil? && default ? default(locale, key, default, options) : resolve(locale, key, entry, options)
42
- raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
43
-
44
- entry = pluralize(locale, entry, count) if count
45
- entry = interpolate(locale, entry, values) if values
39
+ count, default = options.values_at(:count, :default)
40
+ values = options.except(*RESERVED_KEYS)
41
+ entry = entry.nil? && default ?
42
+ default(locale, key, default, options) : resolve(locale, key, entry, options)
46
43
  end
47
44
 
45
+ raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
46
+ entry = entry.dup if entry.is_a?(String)
47
+
48
+ entry = pluralize(locale, entry, count) if count
49
+ entry = interpolate(locale, entry, values) if values
48
50
  entry
49
51
  end
50
52
 
@@ -57,7 +59,7 @@ module I18n
57
59
  if Symbol === format
58
60
  key = format
59
61
  type = object.respond_to?(:sec) ? 'time' : 'date'
60
- format = I18n.t(:"#{type}.formats.#{key}", :locale => locale, :raise => true)
62
+ format = I18n.t(:"#{type}.formats.#{key}", options.merge(:raise => true, :object => object, :locale => locale))
61
63
  end
62
64
 
63
65
  # format = resolve(locale, object, format, options)
@@ -74,51 +76,21 @@ module I18n
74
76
  object.strftime(format)
75
77
  end
76
78
 
77
- def initialized?
78
- @initialized ||= false
79
- end
80
-
81
79
  # Returns an array of locales for which translations are available
82
80
  # ignoring the reserved translation meta data key :i18n.
83
81
  def available_locales
84
- init_translations unless initialized?
85
- translations.inject([]) do |locales, (locale, data)|
86
- locales << locale unless (data.keys - [:i18n]).empty?
87
- locales
88
- end
82
+ raise NotImplementedError
89
83
  end
90
84
 
91
85
  def reload!
92
- @initialized = false
93
- @translations = nil
86
+ @skip_syntax_deprecation = false
94
87
  end
95
88
 
96
89
  protected
97
- def init_translations
98
- load_translations(*I18n.load_path.flatten)
99
- @initialized = true
100
- end
101
-
102
- def translations
103
- @translations ||= {}
104
- end
105
90
 
106
- # Looks up a translation from the translations hash. Returns nil if
107
- # eiher key is nil, or locale, scope or key do not exist as a key in the
108
- # nested translations hash. Splits keys or scopes containing dots
109
- # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
110
- # <tt>%w(currency format)</tt>.
91
+ # The method which actually looks up for the translation in the store.
111
92
  def lookup(locale, key, scope = [], options = {})
112
- return unless key
113
- init_translations unless initialized?
114
- keys = I18n.normalize_keys(locale, key, scope, options[:separator])
115
- keys.inject(translations) do |result, key|
116
- key = key.to_sym
117
- return nil unless result.is_a?(Hash) && result.has_key?(key)
118
- result = result[key]
119
- result = resolve(locale, key, result, options) if result.is_a?(Symbol)
120
- String === result ? result.dup : result
121
- end
93
+ raise NotImplementedError
122
94
  end
123
95
 
124
96
  # Evaluates defaults.
@@ -147,7 +119,8 @@ module I18n
147
119
  when Symbol
148
120
  I18n.translate(subject, (options || {}).merge(:locale => locale, :raise => true))
149
121
  when Proc
150
- resolve(locale, object, subject.call(object, options), options = {})
122
+ date_or_time = options.delete(:object) || object
123
+ resolve(locale, object, subject.call(date_or_time, options), options = {})
151
124
  else
152
125
  subject
153
126
  end
@@ -160,7 +133,7 @@ module I18n
160
133
  # and the second translation if it is equal to 1. Other backends can
161
134
  # implement more flexible or complex pluralization rules.
162
135
  def pluralize(locale, entry, count)
163
- return entry unless entry.is_a?(Hash) and count
136
+ return entry unless entry.is_a?(Hash) && count
164
137
 
165
138
  key = :zero if count == 0 && entry.has_key?(:zero)
166
139
  key ||= count == 1 ? :one : :other
@@ -170,38 +143,48 @@ module I18n
170
143
 
171
144
  # Interpolates values into a given string.
172
145
  #
173
- # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
174
- # # => "file test.txt opened by {{user}}"
146
+ # interpolate "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
147
+ # # => "file test.txt opened by %{user}"
175
148
  #
176
149
  # Note that you have to double escape the <tt>\\</tt> when you want to escape
177
150
  # the <tt>{{...}}</tt> key in a string (once for the string and once for the
178
151
  # interpolation).
179
152
  def interpolate(locale, string, values = {})
180
153
  return string unless string.is_a?(::String) && !values.empty?
154
+ original_values = values.dup
181
155
 
182
156
  preserve_encoding(string) do
183
- s = string.gsub(INTERPOLATION_SYNTAX_PATTERN) do
157
+ string = string.gsub(DEPRECATED_INTERPOLATION_SYNTAX_PATTERN) do
184
158
  escaped, key = $1, $2.to_sym
185
159
  if escaped
186
160
  "{{#{key}}}"
187
- elsif RESERVED_KEYS.include?(key)
188
- raise ReservedInterpolationKey.new(key, string)
189
161
  else
162
+ warn_syntax_deprecation!
190
163
  "%{#{key}}"
191
164
  end
192
165
  end
193
166
 
167
+ keys = string.scan(INTERPOLATION_SYNTAX_PATTERN).flatten
168
+ return string if keys.empty?
169
+
194
170
  values.each do |key, value|
195
- value = value.call(values) if interpolate_lambda?(value, s, key)
196
- value = value.to_s unless value.is_a?(::String)
197
- values[key] = value
171
+ if keys.include?(key.to_s)
172
+ value = value.call(values) if interpolate_lambda?(value, string, key)
173
+ value = value.to_s unless value.is_a?(::String)
174
+ values[key] = value
175
+ else
176
+ values.delete(key)
177
+ end
198
178
  end
199
179
 
200
- s % values
180
+ string % values
201
181
  end
202
-
203
182
  rescue KeyError => e
204
- raise MissingInterpolationArgument.new(values, string)
183
+ if string =~ RESERVED_KEYS_PATTERN
184
+ raise ReservedInterpolationKey.new($1.to_sym, string)
185
+ else
186
+ raise MissingInterpolationArgument.new(original_values, string)
187
+ end
205
188
  end
206
189
 
207
190
  def preserve_encoding(string)
@@ -229,7 +212,7 @@ module I18n
229
212
  type = File.extname(filename).tr('.', '').downcase
230
213
  raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
231
214
  data = send(:"load_#{type}", filename) # TODO raise a meaningful exception if this does not yield a Hash
232
- data.each { |locale, d| merge_translations(locale, d) }
215
+ data.each { |locale, d| store_translations(locale, d) }
233
216
  end
234
217
 
235
218
  # Loads a plain Ruby translations file. eval'ing the file must yield
@@ -244,22 +227,10 @@ module I18n
244
227
  YAML::load(IO.read(filename))
245
228
  end
246
229
 
247
- # Deep merges the given translations hash with the existing translations
248
- # for the given locale
249
- def merge_translations(locale, data, options = {})
250
- locale = locale.to_sym
251
- translations[locale] ||= {}
252
- separator = options[:separator] || I18n.default_separator
253
- data = unwind_keys(data, separator)
254
- data = deep_symbolize_keys(data)
255
-
256
- # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
257
- merger = proc do |key, v1, v2|
258
- # TODO should probably be:
259
- # raise TypeError.new("can't merge #{v1.inspect} and #{v2.inspect}") unless Hash === v1 && Hash === v2
260
- Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : (v2 || v1)
261
- end
262
- translations[locale].merge!(data, &merger)
230
+ def warn_syntax_deprecation! #:nodoc:
231
+ return if @skip_syntax_deprecation
232
+ warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{caller.join("\n")}"
233
+ @skip_syntax_deprecation = true
263
234
  end
264
235
  end
265
236
  end
@@ -45,6 +45,7 @@ module I18n
45
45
  end
46
46
 
47
47
  module Backend
48
+ # TODO Should the cache be cleared if new translations are stored?
48
49
  module Cache
49
50
  def translate(*args)
50
51
  I18n.perform_caching? ? fetch(*args) { super } : super
@@ -36,7 +36,6 @@ module I18n
36
36
  module Backend
37
37
  module Cascade
38
38
  def lookup(locale, key, scope = [], options = {})
39
- return unless key
40
39
  return super unless cascade = options[:cascade]
41
40
 
42
41
  separator = options[:separator] || I18n.default_separator
@@ -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 < Simple
19
+ class Chain
20
+ include Base
21
+
20
22
  attr_accessor :backends
21
23
 
22
24
  def initialize(*backends)
@@ -0,0 +1,113 @@
1
+ module I18n
2
+ module Backend
3
+ # This module contains several helpers to assist flattening translations.
4
+ # You may want to flatten translations for:
5
+ #
6
+ # 1) speed up lookups, as in the Memoize backend;
7
+ # 2) In case you want to store translations in a data store, as in ActiveRecord backend;
8
+ #
9
+ # You can check both backends above for some examples.
10
+ # This module also keeps all links in a hash so they can be properly resolved when flattened.
11
+ module Flatten
12
+ SEPARATOR_ESCAPE_CHAR = "\001"
13
+ FLATTEN_SEPARATOR = "."
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
+
44
+ # Store flattened links.
45
+ def links
46
+ @links ||= Hash.new { |h,k| h[k] = {} }
47
+ end
48
+
49
+ # Flatten keys for nested Hashes by chaining up keys:
50
+ #
51
+ # >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
52
+ # => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
53
+ #
54
+ def flatten_keys(hash, escape, prev_key=nil, &block)
55
+ hash.each_pair do |key, value|
56
+ key = escape_default_separator(key) if escape
57
+ curr_key = [prev_key, key].compact.join(FLATTEN_SEPARATOR).to_sym
58
+ yield curr_key, value
59
+ flatten_keys(value, escape, curr_key, &block) if value.is_a?(Hash)
60
+ end
61
+ end
62
+
63
+ # Receives a hash of translations (where the key is a locale and
64
+ # the value is another hash) and return a hash with all
65
+ # translations flattened.
66
+ #
67
+ # Nested hashes are included in the flattened hash just if subtree
68
+ # is true and Symbols are automatically stored as links.
69
+ def flatten_translations(locale, data, escape, subtree)
70
+ hash = {}
71
+ flatten_keys(data, escape) do |key, value|
72
+ if value.is_a?(Hash)
73
+ hash[key] = value if subtree
74
+ else
75
+ store_link(locale, key, value) if value.is_a?(Symbol)
76
+ hash[key] = value
77
+ end
78
+ end
79
+ hash
80
+ end
81
+
82
+ protected
83
+
84
+ def store_link(locale, key, link)
85
+ links[locale.to_sym][key.to_s] = link.to_s
86
+ end
87
+
88
+ def resolve_link(locale, key)
89
+ key, locale = key.to_s, locale.to_sym
90
+ links = self.links[locale]
91
+
92
+ if links.key?(key)
93
+ links[key]
94
+ elsif link = find_link(locale, key)
95
+ store_link(locale, key, key.gsub(*link))
96
+ else
97
+ key
98
+ end
99
+ end
100
+
101
+ def find_link(locale, key) #:nodoc:
102
+ links[locale].each do |from, to|
103
+ return [from, to] if key[0, from.length] == from
104
+ end && nil
105
+ end
106
+
107
+ def escape_default_separator(key) #:nodoc:
108
+ I18n::Backend::Flatten.escape_default_separator(key)
109
+ end
110
+
111
+ end
112
+ end
113
+ end