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.

@@ -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
@@ -12,94 +12,11 @@ require 'i18n/core_ext/string/interpolate'
12
12
 
13
13
  module I18n
14
14
  autoload :Backend, 'i18n/backend'
15
- autoload :Helpers, 'i18n/helpers'
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 {{bar}}"</tt> the option
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 => ['{{count}} foo', '{{count}} foos']</tt>, count will
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. {{options[:name]}}" : "Mrs. {{options[:name]}}" }
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
  #
@@ -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
- include Base, Flatten
11
+ module Implementation
12
+ include Base, Flatten
12
13
 
13
- def reload!
14
- end
14
+ def reload!
15
+ end
16
+
17
+ def available_locales
18
+ init_translations unless initialized?
15
19
 
16
- def available_locales
17
- begin
18
- Translation.available_locales
19
- rescue ::ActiveRecord::StatementInvalid
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 = normalize_keys(locale, key, scope, options[:separator])
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(result)
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
@@ -1,14 +1,15 @@
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
+ 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
- merge_translations(locale, data, options)
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.reject { |name, value| RESERVED_KEYS.include?(name) }
41
-
42
- entry = entry.nil? && default ? default(locale, key, default, options) : resolve(locale, key, entry, options)
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
- end
49
+ raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
50
+ entry = entry.dup if entry.is_a?(String)
51
51
 
52
- # Given a locale and a UTF-8 string, return the locale's ASCII
53
- # approximation for the string.
54
- def transliterate(locale, string, replacement = nil)
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
- String === result ? result.dup : result
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 {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
191
- # # => "file test.txt opened by {{user}}"
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
- s = string.gsub(INTERPOLATION_SYNTAX_PATTERN) do
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, s, key)
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
- s % values
214
+ string % values
218
215
  end
219
-
220
216
  rescue KeyError => e
221
- raise MissingInterpolationArgument.new(values, string)
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| merge_translations(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
- # Deep merges the given translations hash with the existing translations
265
- # for the given locale
266
- def merge_translations(locale, data, options = {})
267
- locale = locale.to_sym
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
@@ -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
@@ -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)
@@ -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
- key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR)
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 {{bar}} baz \\{{buz}}") # => ["foo ", "{{bar}}", " baz ", "\\{{buz}}"]
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 merge_translations(locale, data, options = {})
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
- attr_accessor :store
51
+ module Implementation
52
+ attr_accessor :store
46
53
 
47
- include Base, Flatten
48
-
49
- def initialize(store, subtrees=true)
50
- @store, @subtrees = store, subtrees
51
- end
54
+ include Base, Flatten
52
55
 
53
- # Mute reload! since we really don't want to clean the database.
54
- def reload!
55
- end
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
- def lookup(locale, key, scope = [], options = {})
68
- key = normalize_keys(locale, key, scope, options[:separator])
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 merge_translations(locale, data, options = {})
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 = deep_symbolize_keys(old_value).merge(value) if old_value.is_a?(Hash)
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.nil?
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, string, values = {})
47
- with_metadata(:original => string) do
48
- preserve_translation_metadata(string) { super }
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
@@ -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
- if RUBY_VERSION < '1.9'
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'}
@@ -1,3 +1,3 @@
1
1
  module I18n
2
- VERSION = "0.4.0.beta"
2
+ VERSION = "0.4.0.beta1"
3
3
  end
metadata CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 0
7
7
  - 4
8
8
  - 0
9
- - beta
10
- version: 0.4.0.beta
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-04-30 00:00:00 +02:00
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/core_ext/hash/except.rb
58
- - lib/i18n/core_ext/hash/slice.rb
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
@@ -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
@@ -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
@@ -1,8 +0,0 @@
1
- # from facets (http://facets.rubyforge.org)
2
- require 'i18n/core_ext/hash/slice'
3
-
4
- class Hash
5
- def except(*less_keys)
6
- slice(*keys - less_keys)
7
- end
8
- end unless Hash.method_defined?(:except)
@@ -1,8 +0,0 @@
1
- # from facets (http://facets.rubyforge.org)
2
- class Hash
3
- def slice(*keep_keys)
4
- h = {}
5
- keep_keys.each { |key| h[key] = fetch(key) }
6
- h
7
- end
8
- end unless Hash.new.respond_to?(:slice)