i18n 0.4.1 → 0.4.2

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.

@@ -1,6 +1,14 @@
1
1
  h1. Changelog
2
2
 
3
- h2. master
3
+ h2. 0.4.2 (2010-10-26)
4
+
5
+ * "Improve UTF8 handling":http://github.com/svenfuchs/i18n/commit/e8d5820a3b08eeca28de1a2b9c8a6ad2b9e6476c
6
+ * "Expose I18n::VERSION":http://github.com/svenfuchs/i18n/commit/b832037bac94c7144f45f3ff5e3b4e4089781726
7
+ * "Better deprecation output":http://github.com/svenfuchs/i18n/commit/2bee924464b8a9c33d3d7852eb1c8423aa38cc25
8
+
9
+ h2. 0.4.1 (2010-06-05)
10
+
11
+ * "Fix interpolation failure on Ruby 1.9":http://github.com/svenfuchs/i18n/commit/8d45bedb11c4136c00e853d104b00a8e67ec4894
4
12
 
5
13
  h2. 0.4.0 (2010-05-27)
6
14
 
@@ -29,12 +29,16 @@ Alternative backends:
29
29
  * ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
30
30
  * KeyValue (uses active_support/json and cannot store procs)
31
31
 
32
- For more information and lots of resources see: "http://rails-i18n.org/wiki":http://rails-i18n.org/wiki
32
+ For more information and lots of resources see: "http://ruby-i18n.org/wiki":http://ruby-i18n.org/wiki
33
33
 
34
34
  h2. Installation
35
35
 
36
36
  gem install i18n
37
37
 
38
+ h4. Rails version warning
39
+
40
+ On Rails < 2.3.6 the method I18n.localize will fail with MissingInterpolationArgument (issue "20":http://github.com/svenfuchs/i18n/issues/issue/20). Upgrade to Rails 2.3.6 or higher (2.3.8 preferably) is recommended.
41
+
38
42
  h3. Installation on Rails < 2.3.5 (deprecated)
39
43
 
40
44
  Up to version 2.3.4 Rails will not accept i18n gems > 0.1.3. There is an unpacked
@@ -43,6 +47,7 @@ This requirement is relaxed in "6da03653":http://github.com/rails/rails/commit/6
43
47
 
44
48
  The new i18n gem can be loaded from vendor/plugins like this:
45
49
 
50
+ <pre>
46
51
  def reload_i18n!
47
52
  raise "Move to i18n version 0.2.0 or greater" if Rails.version > "2.3.4"
48
53
 
@@ -50,6 +55,7 @@ The new i18n gem can be loaded from vendor/plugins like this:
50
55
  I18n::Backend.send :remove_const, "Simple"
51
56
  $: << Rails.root.join('vendor', 'plugins', 'i18n', 'lib').to_s
52
57
  end
58
+ </pre>
53
59
 
54
60
  Then you can `reload_i18n!` inside an i18n initializer.
55
61
 
@@ -61,6 +67,23 @@ You can run tests both with
61
67
  * run any test file directly, e.g. `ruby test/api/simple_test.rb`
62
68
  * run all tests with `ruby test/all.rb`
63
69
 
70
+ You can parametrize the test suite for using different sets of dependencies by
71
+ using:
72
+
73
+ .pre `ruby test/all.rb -w DEPENDENCIES`
74
+
75
+ ... where DEPENDENCIES is a comma-separated list of:
76
+
77
+ * r23 or rails-2.3.x
78
+ * r3 or rails-3.x
79
+ * no-rails
80
+ * sqlite
81
+ * mysql
82
+
83
+ So, e.g. this would run the test suite against Rails 2.3.x using mysql:
84
+
85
+ .pre `ruby test/all.rb -w r23,mysql`
86
+
64
87
  The structure of the test suite is a bit unusual as it uses modules to reuse
65
88
  particular tests in different test cases.
66
89
 
@@ -7,6 +7,7 @@
7
7
  # Matt Aimonetti (http://railsontherun.com/)
8
8
  # Copyright:: Copyright (c) 2008 The Ruby i18n Team
9
9
  # License:: MIT
10
+ require 'i18n/version'
10
11
  require 'i18n/exceptions'
11
12
  require 'i18n/core_ext/string/interpolate'
12
13
 
@@ -145,19 +146,27 @@ module I18n
145
146
  # always return the same translations/values per unique combination of argument
146
147
  # values.
147
148
  def translate(*args)
148
- options = args.pop if args.last.is_a?(Hash)
149
+ options = args.last.is_a?(Hash) ? args.pop : {}
149
150
  key = args.shift
150
- locale = options && options.delete(:locale) || config.locale
151
- raises = options && options.delete(:raise)
152
- config.backend.translate(locale, key, options || {})
151
+ backend = config.backend
152
+ locale = options.delete(:locale) || config.locale
153
+ raises = options.delete(:raise)
154
+
155
+ raise I18n::ArgumentError if key.is_a?(String) && key.empty?
156
+
157
+ if key.is_a?(Array)
158
+ key.map { |k| backend.translate(locale, k, options) }
159
+ else
160
+ backend.translate(locale, key, options)
161
+ end
153
162
  rescue I18n::ArgumentError => exception
154
163
  raise exception if raises
155
164
  handle_exception(exception, locale, key, options)
156
165
  end
157
166
  alias :t :translate
158
167
 
159
- def translate!(key, options = {})
160
- translate(key, options.merge( :raise => true ))
168
+ def translate!(key, options={})
169
+ translate(key, options.merge(:raise => true))
161
170
  end
162
171
  alias :t! :translate!
163
172
 
@@ -8,7 +8,7 @@
8
8
  # Example usage:
9
9
  #
10
10
  # I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
11
- # I18n.backend = I18nChainBackend.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
11
+ # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
12
12
  #
13
13
  # Stub records for pluralizations will also be created for each key defined
14
14
  # in i18n.plural.keys.
@@ -33,16 +33,16 @@ module I18n
33
33
  module Backend
34
34
  class ActiveRecord
35
35
  module Missing
36
+ include Flatten
37
+
36
38
  def store_default_translations(locale, key, options = {})
37
- count, scope, default, separator = options.values_at(:count, *Base::RESERVED_KEYS)
39
+ count, scope, default, separator = options.values_at(:count, :scope, :default, :separator)
38
40
  separator ||= I18n.default_separator
39
-
40
- keys = I18n.normalize_keys(locale, key, scope, separator)[1..-1]
41
- key = keys.join(separator || I18n.default_separator)
41
+ key = normalize_flat_keys(locale, key, scope, separator)
42
42
 
43
43
  unless ActiveRecord::Translation.locale(locale).lookup(key).exists?
44
- interpolations = options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }.keys
45
- keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(separator) } : [key]
44
+ interpolations = options.keys - Base::RESERVED_KEYS
45
+ keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key]
46
46
  keys.each { |key| store_default_translation(locale, key, interpolations) }
47
47
  end
48
48
  end
@@ -46,33 +46,36 @@ module I18n
46
46
  # # => 'FOO'
47
47
  class ActiveRecord
48
48
  class Translation < ::ActiveRecord::Base
49
+ TRUTHY_CHAR = "\001"
50
+ FALSY_CHAR = "\002"
51
+
49
52
  set_table_name 'translations'
50
53
  attr_protected :is_proc, :interpolations
51
54
 
52
55
  serialize :value
53
56
  serialize :interpolations, Array
54
57
 
55
- scope_method = ::ActiveRecord::VERSION::MAJOR == 2 ? :named_scope : :scope
58
+ class << self
59
+ def locale(locale)
60
+ scoped(:conditions => { :locale => locale.to_s })
61
+ end
56
62
 
57
- send scope_method, :locale, lambda { |locale|
58
- { :conditions => { :locale => locale.to_s } }
59
- }
63
+ def lookup(keys, *separator)
64
+ column_name = connection.quote_column_name('key')
65
+ keys = Array(keys).map! { |key| key.to_s }
60
66
 
61
- send scope_method, :lookup, lambda { |keys, *separator|
62
- column_name = connection.quote_column_name('key')
63
- keys = Array(keys).map! { |key| key.to_s }
67
+ unless separator.empty?
68
+ warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
69
+ "You can change the internal separator by overwriting FLATTEN_SEPARATOR."
70
+ end
64
71
 
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."
72
+ namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
73
+ scoped(:conditions => ["#{column_name} IN (?) OR #{column_name} LIKE ?", keys, namespace])
68
74
  end
69
75
 
70
- namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
71
- { :conditions => ["#{column_name} IN (?) OR #{column_name} LIKE ?", keys, namespace] }
72
- }
73
-
74
- def self.available_locales
75
- Translation.find(:all, :select => 'DISTINCT locale').map { |t| t.locale.to_sym }
76
+ def available_locales
77
+ Translation.find(:all, :select => 'DISTINCT locale').map { |t| t.locale.to_sym }
78
+ end
76
79
  end
77
80
 
78
81
  def interpolates?(key)
@@ -80,13 +83,27 @@ module I18n
80
83
  end
81
84
 
82
85
  def value
86
+ value = read_attribute(:value)
83
87
  if is_proc
84
- Kernel.eval(read_attribute(:value))
88
+ Kernel.eval(value)
89
+ elsif value == FALSY_CHAR
90
+ false
91
+ elsif value == TRUTHY_CHAR
92
+ true
85
93
  else
86
- value = read_attribute(:value)
87
- value == 'f' ? false : value
94
+ value
88
95
  end
89
96
  end
97
+
98
+ def value=(value)
99
+ if value === false
100
+ value = FALSY_CHAR
101
+ elsif value === true
102
+ value = TRUTHY_CHAR
103
+ end
104
+
105
+ write_attribute(:value, value)
106
+ end
90
107
  end
91
108
  end
92
109
  end
@@ -8,10 +8,9 @@ module I18n
8
8
  module Base
9
9
  include I18n::Backend::Transliterator
10
10
 
11
- RESERVED_KEYS = [:scope, :default, :separator, :resolve]
11
+ RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback]
12
12
  RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
13
13
  DEPRECATED_INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
14
- INTERPOLATION_SYNTAX_PATTERN = /%\{([^\}]+)\}/
15
14
 
16
15
  # Accepts a list of paths to translation files. Loads translations from
17
16
  # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
@@ -29,8 +28,6 @@ module I18n
29
28
 
30
29
  def translate(locale, key, options = {})
31
30
  raise InvalidLocale.new(locale) unless locale
32
- return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
33
-
34
31
  entry = key && lookup(locale, key, options[:scope], options)
35
32
 
36
33
  if options.empty?
@@ -57,9 +54,10 @@ module I18n
57
54
  raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
58
55
 
59
56
  if Symbol === format
60
- key = format
57
+ key = format
61
58
  type = object.respond_to?(:sec) ? 'time' : 'date'
62
- format = I18n.t(:"#{type}.formats.#{key}", options.merge(:raise => true, :object => object, :locale => locale))
59
+ options = options.merge(:raise => true, :object => object, :locale => locale)
60
+ format = I18n.t(:"#{type}.formats.#{key}", options)
63
61
  end
64
62
 
65
63
  # format = resolve(locale, object, format, options)
@@ -113,14 +111,14 @@ module I18n
113
111
  # If the given subject is a Symbol, it will be translated with the
114
112
  # given options. If it is a Proc then it will be evaluated. All other
115
113
  # subjects will be returned directly.
116
- def resolve(locale, object, subject, options = nil)
114
+ def resolve(locale, object, subject, options = {})
117
115
  return subject if options[:resolve] == false
118
116
  case subject
119
117
  when Symbol
120
- I18n.translate(subject, (options || {}).merge(:locale => locale, :raise => true))
118
+ I18n.translate(subject, options.merge(:locale => locale, :raise => true))
121
119
  when Proc
122
120
  date_or_time = options.delete(:object) || object
123
- resolve(locale, object, subject.call(date_or_time, options), options = {})
121
+ resolve(locale, object, subject.call(date_or_time, options))
124
122
  else
125
123
  subject
126
124
  end
@@ -151,50 +149,29 @@ module I18n
151
149
  # interpolation).
152
150
  def interpolate(locale, string, values = {})
153
151
  return string unless string.is_a?(::String) && !values.empty?
154
- original_values = values.dup
155
-
156
- preserve_encoding(string) do
157
- string = string.gsub(DEPRECATED_INTERPOLATION_SYNTAX_PATTERN) do
158
- escaped, key = $1, $2.to_sym
159
- if escaped
160
- "{{#{key}}}"
161
- else
162
- warn_syntax_deprecation!
163
- "%{#{key}}"
164
- end
165
- end
166
152
 
167
- keys = string.scan(INTERPOLATION_SYNTAX_PATTERN).flatten
168
- return string if keys.empty?
169
-
170
- values.each do |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
153
+ string = string.gsub(DEPRECATED_INTERPOLATION_SYNTAX_PATTERN) do
154
+ escaped, key = $1, $2.to_sym
155
+ if escaped
156
+ "{{#{key}}}"
157
+ else
158
+ warn_syntax_deprecation!(locale, string)
159
+ "%{#{key}}"
178
160
  end
161
+ end
179
162
 
180
- string % values
163
+ values.each do |key, value|
164
+ value = value.call(values) if interpolate_lambda?(value, string, key)
165
+ value = value.to_s unless value.is_a?(::String)
166
+ values[key] = value
181
167
  end
168
+
169
+ string % values
182
170
  rescue KeyError => e
183
171
  if string =~ RESERVED_KEYS_PATTERN
184
172
  raise ReservedInterpolationKey.new($1.to_sym, string)
185
173
  else
186
- raise MissingInterpolationArgument.new(original_values, string)
187
- end
188
- end
189
-
190
- def preserve_encoding(string)
191
- if string.respond_to?(:encoding)
192
- encoding = string.encoding
193
- result = yield
194
- result.force_encoding(encoding) if result.respond_to?(:force_encoding)
195
- result
196
- else
197
- yield
174
+ raise MissingInterpolationArgument.new(values, string)
198
175
  end
199
176
  end
200
177
 
@@ -210,9 +187,10 @@ module I18n
210
187
  # for all other file extensions.
211
188
  def load_file(filename)
212
189
  type = File.extname(filename).tr('.', '').downcase
213
- raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
214
- data = send(:"load_#{type}", filename) # TODO raise a meaningful exception if this does not yield a Hash
215
- data.each { |locale, d| store_translations(locale, d) }
190
+ raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
191
+ data = send(:"load_#{type}", filename)
192
+ raise InvalidLocaleData.new(filename) unless data.is_a?(Hash)
193
+ data.each { |locale, d| store_translations(locale, d || {}) }
216
194
  end
217
195
 
218
196
  # Loads a plain Ruby translations file. eval'ing the file must yield
@@ -224,12 +202,12 @@ module I18n
224
202
  # Loads a YAML translations file. The data must have locales as
225
203
  # toplevel keys.
226
204
  def load_yml(filename)
227
- YAML::load(IO.read(filename))
205
+ YAML.load_file(filename)
228
206
  end
229
207
 
230
- def warn_syntax_deprecation! #:nodoc:
208
+ def warn_syntax_deprecation!(locale, string) #:nodoc:
231
209
  return if @skip_syntax_deprecation
232
- warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{caller.join("\n")}"
210
+ warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{locale} - #{string}\n"
233
211
  @skip_syntax_deprecation = true
234
212
  end
235
213
  end
@@ -6,11 +6,11 @@
6
6
  # To enable caching you can simply include the Cache module to the Simple
7
7
  # backend - or whatever other backend you are using:
8
8
  #
9
- # I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
9
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
10
10
  #
11
11
  # You will also need to set a cache store implementation that you want to use:
12
12
  #
13
- # I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
13
+ # I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
14
14
  #
15
15
  # You can use any cache implementation you want that provides the same API as
16
16
  # ActiveSupport::Cache (only the methods #fetch and #write are being used).
@@ -18,6 +18,23 @@
18
18
  # The cache_key implementation assumes that you only pass values to
19
19
  # I18n.translate that return a valid key from #hash (see
20
20
  # http://www.ruby-doc.org/core/classes/Object.html#M000337).
21
+ #
22
+ # If you use a lambda as a default value in your translation like this:
23
+ #
24
+ # I18n.t(:"date.order", :default => lambda {[:month, :day, :year]})
25
+ #
26
+ # Then you will always have a cache miss, because each time this method
27
+ # is called the lambda will have a different hash value. If you know
28
+ # the result of the lambda is a constant as in the example above, then
29
+ # to cache this you can make the lambda a constant, like this:
30
+ #
31
+ # DEFAULT_DATE_ORDER = lambda {[:month, :day, :year]}
32
+ # ...
33
+ # I18n.t(:"date.order", :default => DEFAULT_DATE_ORDER)
34
+ #
35
+ # If the lambda may result in different values for each call then consider
36
+ # also using the Memoize backend.
37
+ #
21
38
  module I18n
22
39
  class << self
23
40
  @@cache_store = nil
@@ -47,31 +64,41 @@ module I18n
47
64
  module Backend
48
65
  # TODO Should the cache be cleared if new translations are stored?
49
66
  module Cache
50
- def translate(*args)
51
- I18n.perform_caching? ? fetch(*args) { super } : super
67
+ def translate(locale, key, options = {})
68
+ I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super
52
69
  end
53
70
 
54
71
  protected
55
72
 
56
- def fetch(*args, &block)
57
- result = I18n.cache_store.fetch(cache_key(*args), &block)
73
+ def fetch(cache_key, &block)
74
+ result = fetch_storing_missing_translation_exception(cache_key, &block)
58
75
  raise result if result.is_a?(Exception)
59
76
  result = result.dup if result.frozen? rescue result
60
77
  result
78
+ end
79
+
80
+ def fetch_storing_missing_translation_exception(cache_key, &block)
81
+ fetch_ignoring_procs(cache_key, &block)
61
82
  rescue MissingTranslationData => exception
62
- I18n.cache_store.write(cache_key(*args), exception)
63
- raise exception
83
+ I18n.cache_store.write(cache_key, exception)
84
+ exception
64
85
  end
65
86
 
66
- def cache_key(*args)
87
+ def fetch_ignoring_procs(cache_key, &block)
88
+ I18n.cache_store.read(cache_key) || yield.tap do |result|
89
+ I18n.cache_store.write(cache_key, result) unless result.is_a?(Proc)
90
+ end
91
+ end
92
+
93
+ def cache_key(locale, key, options)
67
94
  # This assumes that only simple, native Ruby values are passed to I18n.translate.
68
- # Also, in Ruby < 1.8.7 {}.hash != {}.hash
69
- # (see http://paulbarry.com/articles/2009/09/14/why-rails-3-will-require-ruby-1-8-7)
70
- # If args.inspect does not work for you for some reason, patches are very welcome :)
71
- hash = RUBY_VERSION >= "1.8.7" ? args.hash : args.inspect
72
- keys = ['i18n', I18n.cache_namespace, hash]
73
- keys.compact.join('-')
95
+ "i18n/#{I18n.cache_namespace}/#{locale}/#{key.hash}/#{USE_INSPECT_HASH ? options.inspect.hash : options.hash}"
74
96
  end
97
+
98
+ private
99
+ # In Ruby < 1.9 the following is true: { :foo => 1, :bar => 2 }.hash == { :foo => 2, :bar => 1 }.hash
100
+ # Therefore we must use the hash of the inspect string instead to avoid cache key colisions.
101
+ USE_INSPECT_HASH = RUBY_VERSION <= "1.9"
75
102
  end
76
103
  end
77
- end
104
+ end
@@ -17,61 +17,66 @@ module I18n
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
19
  class Chain
20
- include Base
20
+ module Implementation
21
+ include Base
21
22
 
22
- attr_accessor :backends
23
+ attr_accessor :backends
23
24
 
24
- def initialize(*backends)
25
- self.backends = backends
26
- end
25
+ def initialize(*backends)
26
+ self.backends = backends
27
+ end
27
28
 
28
- def reload!
29
- backends.each { |backend| backend.reload! }
30
- end
29
+ def reload!
30
+ backends.each { |backend| backend.reload! }
31
+ end
31
32
 
32
- def store_translations(locale, data, options = {})
33
- backends.first.store_translations(locale, data, options = {})
34
- end
33
+ def store_translations(locale, data, options = {})
34
+ backends.first.store_translations(locale, data, options = {})
35
+ end
35
36
 
36
- def available_locales
37
- backends.map { |backend| backend.available_locales }.flatten.uniq
38
- end
37
+ def available_locales
38
+ backends.map { |backend| backend.available_locales }.flatten.uniq
39
+ end
39
40
 
40
- def translate(locale, key, options = {})
41
- return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
41
+ def translate(locale, key, default_options = {})
42
+ namespace = nil
43
+ options = default_options.except(:default)
42
44
 
43
- default = options.delete(:default)
44
- namespace = {}
45
- backends.each do |backend|
46
- begin
47
- options.update(:default => default) if default and backend == backends.last
48
- translation = backend.translate(locale, key, options)
49
- if namespace_lookup?(translation, options)
50
- namespace.update(translation)
51
- elsif !translation.nil?
52
- return translation
45
+ backends.each do |backend|
46
+ begin
47
+ options = default_options if backend == backends.last
48
+ translation = backend.translate(locale, key, options)
49
+ if namespace_lookup?(translation, options)
50
+ namespace ||= {}
51
+ namespace.merge!(translation)
52
+ elsif !translation.nil?
53
+ return translation
54
+ end
55
+ rescue MissingTranslationData
53
56
  end
54
- rescue MissingTranslationData
55
57
  end
58
+
59
+ return namespace if namespace
60
+ raise(I18n::MissingTranslationData.new(locale, key, options))
56
61
  end
57
- return namespace unless namespace.empty?
58
- raise(I18n::MissingTranslationData.new(locale, key, options))
59
- end
60
62
 
61
- def localize(locale, object, format = :default, options = {})
62
- backends.each do |backend|
63
- begin
64
- result = backend.localize(locale, object, format, options) and return result
65
- rescue MissingTranslationData
63
+ def localize(locale, object, format = :default, options = {})
64
+ backends.each do |backend|
65
+ begin
66
+ result = backend.localize(locale, object, format, options) and return result
67
+ rescue MissingTranslationData
68
+ end
66
69
  end
70
+ raise(I18n::MissingTranslationData.new(locale, format, options))
67
71
  end
68
- raise(I18n::MissingTranslationData.new(locale, format, options))
72
+
73
+ protected
74
+ def namespace_lookup?(result, options)
75
+ result.is_a?(Hash) && !options.has_key?(:count)
76
+ end
69
77
  end
70
78
 
71
- protected
72
- def namespace_lookup?(result, options)
73
- result.is_a?(Hash) and not options.has_key?(:count)
74
- end
79
+ include Implementation
75
80
  end
76
81
  end
77
82
  end
@@ -35,11 +35,13 @@ module I18n
35
35
  # usual.
36
36
  #
37
37
  # The default option takes precedence over fallback locales
38
- # only when it's not a String. When default contains String it
39
- # is evaluated after fallback locales.
38
+ # only when it's a Symbol. When the default contains a String or a Proc
39
+ # it is evaluated last after all the fallback locales have been tried.
40
40
  def translate(locale, key, options = {})
41
- default = extract_string_default!(options) if options[:default]
41
+ return super if options[:fallback]
42
+ default = extract_string_or_lambda_default!(options) if options[:default]
42
43
 
44
+ options[:fallback] = true
43
45
  I18n.fallbacks[locale].each do |fallback|
44
46
  begin
45
47
  result = super(fallback, key, options)
@@ -47,21 +49,22 @@ module I18n
47
49
  rescue I18n::MissingTranslationData
48
50
  end
49
51
  end
52
+ options.delete(:fallback)
50
53
 
51
54
  return super(locale, nil, options.merge(:default => default)) if default
52
55
  raise(I18n::MissingTranslationData.new(locale, key, options))
53
56
  end
54
57
 
55
- def extract_string_default!(options)
58
+ def extract_string_or_lambda_default!(options)
56
59
  defaults = Array(options[:default])
57
- if index = find_first_string_default(defaults)
60
+ if index = find_first_string_or_lambda_default(defaults)
58
61
  options[:default] = defaults[0, index]
59
62
  defaults[index]
60
63
  end
61
64
  end
62
65
 
63
- def find_first_string_default(defaults)
64
- defaults.each_index { |ix| return ix if String === defaults[ix] }
66
+ def find_first_string_or_lambda_default(defaults)
67
+ defaults.each_with_index { |default, ix| return ix if String === default || Proc === default }
65
68
  nil
66
69
  end
67
70
  end
@@ -49,9 +49,7 @@ module I18n
49
49
  normalized = { part => normalized.empty? ? value : normalized }
50
50
  end
51
51
 
52
- # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
53
- merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
54
- result.merge!(normalized, &merger)
52
+ result.deep_merge!(normalized)
55
53
  end
56
54
  result
57
55
  end
@@ -71,11 +71,11 @@ module I18n
71
71
  init_translations unless initialized?
72
72
  keys = I18n.normalize_keys(locale, key, scope, options[:separator])
73
73
 
74
- keys.inject(translations) do |result, key|
75
- key = key.to_sym
76
- return nil unless result.is_a?(Hash) && result.has_key?(key)
77
- result = result[key]
78
- result = resolve(locale, key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
74
+ keys.inject(translations) do |result, _key|
75
+ _key = _key.to_sym
76
+ return nil unless result.is_a?(Hash) && result.has_key?(_key)
77
+ result = result[_key]
78
+ result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
79
79
  result
80
80
  end
81
81
  end
@@ -32,15 +32,17 @@ module I18n
32
32
  end
33
33
 
34
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.
35
+ # Unless you explicitely set these through I18n.available_locales=
36
+ # the call will be delegated to the backend.
37
37
  def available_locales
38
- @@available_locales ||= backend.available_locales
38
+ @@available_locales ||= nil
39
+ @@available_locales || backend.available_locales
39
40
  end
40
41
 
41
42
  # Sets the available locales.
42
43
  def available_locales=(locales)
43
- @@available_locales = locales
44
+ @@available_locales = Array(locales).map {|locale| locale.to_sym}
45
+ @@available_locales = nil if @@available_locales.empty?
44
46
  end
45
47
 
46
48
  # Returns the current default scope separator. Defaults to '.'
@@ -17,10 +17,20 @@ module I18n
17
17
  end
18
18
  end
19
19
 
20
+ class InvalidLocaleData < ArgumentError
21
+ attr_reader :filename
22
+ def initialize(filename)
23
+ @filename = filename
24
+ super "can not load translations from #{filename}, expected it to return a hash, but does not"
25
+ end
26
+ end
27
+
20
28
  class MissingTranslationData < ArgumentError
21
29
  attr_reader :locale, :key, :options
22
30
  def initialize(locale, key, opts = nil)
23
- @key, @locale, @options = key, locale, opts || {}
31
+ @key, @locale, @options = key, locale, opts.dup || {}
32
+ options.each { |k, v| options[k] = v.inspect if v.is_a?(Proc) }
33
+
24
34
  keys = I18n.normalize_keys(locale, key, options[:scope])
25
35
  keys << 'no key' if keys.size < 2
26
36
  super "translation missing: #{keys.join(', ')}"
@@ -6,7 +6,7 @@ module I18n
6
6
  # Implements classical Gettext style accessors. To use this include the
7
7
  # module to the global namespace or wherever you want to use it.
8
8
  #
9
- # include I18n::Helpers::Gettext
9
+ # include I18n::Gettext::Helpers
10
10
  module Helpers
11
11
  def gettext(msgid, options = {})
12
12
  I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options))
@@ -1,3 +1,3 @@
1
1
  module I18n
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 1
10
- version: 0.4.1
9
+ - 2
10
+ version: 0.4.2
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-06-05 00:00:00 +02:00
22
+ date: 2010-10-26 00:00:00 +02:00
23
23
  default_executable:
24
24
  dependencies: []
25
25
 
@@ -93,12 +93,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
- hash: 23
96
+ hash: 17
97
97
  segments:
98
98
  - 1
99
99
  - 3
100
- - 6
101
- version: 1.3.6
100
+ - 5
101
+ version: 1.3.5
102
102
  requirements: []
103
103
 
104
104
  rubyforge_project: "[none]"