i18n 0.3.2 → 0.3.3

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/Rakefile CHANGED
@@ -4,10 +4,13 @@ task :test do
4
4
  ruby "test/all.rb"
5
5
  end
6
6
 
7
+ require File.expand_path("lib/i18n/version", File.dirname(__FILE__))
8
+
7
9
  begin
8
10
  require 'jeweler'
9
11
  Jeweler::Tasks.new do |s|
10
12
  s.name = "i18n"
13
+ s.version = I18n::VERSION
11
14
  s.rubyforge_project = "i18n"
12
15
  s.summary = "New wave Internationalization support for Ruby"
13
16
  s.email = "rails-i18n@googlegroups.com"
@@ -201,11 +201,11 @@ module I18n
201
201
  # always return the same translations/values per unique combination of argument
202
202
  # values.
203
203
  def translate(*args)
204
- options = args.last.is_a?(Hash) ? args.pop : {}
204
+ options = args.pop if args.last.is_a?(Hash)
205
205
  key = args.shift
206
- locale = options.delete(:locale) || I18n.locale
207
- raises = options.delete(:raise)
208
- backend.translate(locale, key, options)
206
+ locale = options && options.delete(:locale) || I18n.locale
207
+ raises = options && options.delete(:raise)
208
+ backend.translate(locale, key, options || {})
209
209
  rescue I18n::ArgumentError => exception
210
210
  raise exception if raises
211
211
  handle_exception(exception, locale, key, options)
@@ -270,7 +270,8 @@ module I18n
270
270
  def normalize_translation_keys(locale, key, scope, separator = nil)
271
271
  keys = [locale] + Array(scope) + Array(key)
272
272
  keys = keys.map { |k| k.to_s.split(separator || I18n.default_separator) }
273
- keys.flatten.map { |k| k.to_sym }
273
+ keys = keys.flatten - ['']
274
+ keys.map { |k| k.to_sym }
274
275
  end
275
276
  end
276
277
  end
@@ -1,15 +1,17 @@
1
1
  module I18n
2
2
  module Backend
3
- autoload :ActiveRecord, 'i18n/backend/active_record'
4
- autoload :Base, 'i18n/backend/base'
5
- autoload :Cache, 'i18n/backend/cache'
6
- autoload :Cascade, 'i18n/backend/cascade'
7
- autoload :Chain, 'i18n/backend/chain'
8
- autoload :Fallbacks, 'i18n/backend/fallbacks'
9
- autoload :Gettext, 'i18n/backend/gettext'
10
- autoload :Helpers, 'i18n/backend/helpers'
11
- autoload :Metadata, 'i18n/backend/metadata'
12
- autoload :Pluralization, 'i18n/backend/pluralization'
13
- autoload :Simple, 'i18n/backend/simple'
3
+ autoload :ActiveRecord, 'i18n/backend/active_record'
4
+ autoload :Base, 'i18n/backend/base'
5
+ autoload :Cache, 'i18n/backend/cache'
6
+ autoload :Cascade, 'i18n/backend/cascade'
7
+ autoload :Chain, 'i18n/backend/chain'
8
+ autoload :Fallbacks, 'i18n/backend/fallbacks'
9
+ autoload :Fast, 'i18n/backend/fast'
10
+ autoload :Gettext, 'i18n/backend/gettext'
11
+ autoload :Helpers, 'i18n/backend/helpers'
12
+ autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
13
+ autoload :Metadata, 'i18n/backend/metadata'
14
+ autoload :Pluralization, 'i18n/backend/pluralization'
15
+ autoload :Simple, 'i18n/backend/simple'
14
16
  end
15
17
  end
@@ -24,7 +24,7 @@ module I18n
24
24
  separator = options[:separator] || I18n.default_separator
25
25
  wind_keys(data, separator).each do |key, v|
26
26
  Translation.locale(locale).lookup(expand_keys(key, separator), separator).delete_all
27
- Translation.create(:locale => locale.to_s, :key => key, :value => v)
27
+ Translation.create(:locale => locale.to_s, :key => key.to_s, :value => v)
28
28
  end
29
29
  end
30
30
 
@@ -11,19 +11,21 @@
11
11
  # I18n.backend = I18nChainBackend.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
- # in i18n.plural_keys.
14
+ # in i18n.plural.keys.
15
15
  #
16
16
  # For example:
17
17
  #
18
18
  # # en.yml
19
19
  # en:
20
20
  # i18n:
21
- # plural_keys: [:zero, :one, :other]
21
+ # plural:
22
+ # keys: [:zero, :one, :other]
22
23
  #
23
24
  # # pl.yml
24
25
  # pl:
25
26
  # i18n:
26
- # plural_keys: [:zero, :one, :few, :other]
27
+ # plural:
28
+ # keys: [:zero, :one, :few, :other]
27
29
  #
28
30
  # It will also persist interpolation keys in Translation#interpolations so
29
31
  # translators will be able to review and use them.
@@ -34,13 +36,14 @@ module I18n
34
36
  def store_default_translations(locale, key, options = {})
35
37
  count, scope, default, separator = options.values_at(:count, *Base::RESERVED_KEYS)
36
38
  separator ||= I18n.default_separator
39
+
37
40
  keys = I18n.send(:normalize_translation_keys, locale, key, scope, separator)[1..-1]
38
41
  key = keys.join(separator || I18n.default_separator)
39
42
 
40
43
  unless ActiveRecord::Translation.locale(locale).lookup(key, separator).exists?
41
44
  interpolations = options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }.keys
42
- keys = count ? I18n.t('i18n.plural_keys', :locale => locale).map { |k| [key, k].join(separator) } : [key]
43
- keys.each { |key| store_default_translation(locale, key, interpolations) }
45
+ keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(separator) } : [key]
46
+ keys.each { |key| store_default_translation(locale, key, interpolations) }
44
47
  end
45
48
  end
46
49
 
@@ -49,7 +52,7 @@ module I18n
49
52
  translation.interpolations = interpolations
50
53
  translation.save
51
54
  end
52
-
55
+
53
56
  def translate(locale, key, options = {})
54
57
  super
55
58
 
@@ -73,7 +73,8 @@ module I18n
73
73
  if is_proc
74
74
  Kernel.eval read_attribute(:value)
75
75
  else
76
- read_attribute(:value)
76
+ value = read_attribute(:value)
77
+ value == 'f' ? false : value
77
78
  end
78
79
  end
79
80
  end
@@ -26,18 +26,24 @@ module I18n
26
26
  end
27
27
 
28
28
  def translate(locale, key, options = {})
29
- raise InvalidLocale.new(locale) if locale.nil?
29
+ raise InvalidLocale.new(locale) unless locale
30
30
  return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
31
31
 
32
- count, scope, default, separator = options.values_at(:count, *RESERVED_KEYS)
33
- values = options.reject { |name, value| RESERVED_KEYS.include?(name) }
32
+ if options.empty?
33
+ entry = resolve(locale, key, lookup(locale, key), options)
34
+ raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
35
+ else
36
+ count, scope, default, separator = options.values_at(:count, :scope, :default, :separator)
37
+ values = options.reject { |name, value| RESERVED_KEYS.include?(name) }
34
38
 
35
- entry = lookup(locale, key, scope, separator)
36
- entry = entry.nil? ? default(locale, key, default, options) : resolve(locale, key, entry, options)
39
+ entry = lookup(locale, key, scope, separator)
40
+ entry = entry.nil? && default ? default(locale, key, default, options) : resolve(locale, key, entry, options)
41
+ raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
42
+
43
+ entry = pluralize(locale, entry, count) if count
44
+ entry = interpolate(locale, entry, values) if values
45
+ end
37
46
 
38
- raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
39
- entry = pluralize(locale, entry, count)
40
- entry = interpolate(locale, entry, values)
41
47
  entry
42
48
  end
43
49
 
@@ -108,11 +114,10 @@ module I18n
108
114
  keys = I18n.send(:normalize_translation_keys, locale, key, scope, separator)
109
115
  keys.inject(translations) do |result, key|
110
116
  key = key.to_sym
111
- if result.respond_to?(:has_key?) and result.has_key?(key)
112
- result[key]
113
- else
114
- return nil
115
- end
117
+ return nil unless result.is_a?(Hash) && result.has_key?(key)
118
+ result = result[key]
119
+ result = resolve(locale, key, result, :separator => separator) if result.is_a?(Symbol)
120
+ result
116
121
  end
117
122
  end
118
123
 
@@ -124,8 +129,8 @@ module I18n
124
129
  options = options.dup.reject { |key, value| key == :default }
125
130
  case subject
126
131
  when Array
127
- subject.each do |subject|
128
- result = resolve(locale, object, subject, options) and return result
132
+ subject.each do |item|
133
+ result = resolve(locale, object, item, options) and return result
129
134
  end and nil
130
135
  else
131
136
  resolve(locale, object, subject, options)
@@ -136,10 +141,10 @@ module I18n
136
141
  # If the given subject is a Symbol, it will be translated with the
137
142
  # given options. If it is a Proc then it will be evaluated. All other
138
143
  # subjects will be returned directly.
139
- def resolve(locale, object, subject, options = {})
144
+ def resolve(locale, object, subject, options = nil)
140
145
  case subject
141
146
  when Symbol
142
- I18n.translate(subject, options.merge(:locale => locale, :raise => true))
147
+ I18n.translate(subject, (options || {}).merge(:locale => locale, :raise => true))
143
148
  when Proc
144
149
  resolve(locale, object, subject.call(object, options), options = {})
145
150
  else
@@ -46,7 +46,7 @@ module I18n
46
46
  translation = backend.translate(locale, key, options)
47
47
  if namespace_lookup?(translation, options)
48
48
  namespace.update(translation)
49
- elsif translation
49
+ elsif !translation.nil?
50
50
  return translation
51
51
  end
52
52
  rescue MissingTranslationData
@@ -40,7 +40,8 @@ module I18n
40
40
  def translate(locale, key, options = {})
41
41
  I18n.fallbacks[locale].each do |fallback|
42
42
  begin
43
- result = super(fallback, key, options) and return result
43
+ result = super(fallback, key, options)
44
+ return result unless result.nil?
44
45
  rescue I18n::MissingTranslationData
45
46
  end
46
47
  end
@@ -0,0 +1,68 @@
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
+ SEPARATOR_ESCAPE_CHAR = "\001"
16
+
17
+ def reset_flattened_translations!
18
+ @flattened_translations = nil
19
+ end
20
+
21
+ def flattened_translations
22
+ @flattened_translations ||= flatten_translations(translations)
23
+ end
24
+
25
+ def merge_translations(locale, data)
26
+ super
27
+ reset_flattened_translations!
28
+ end
29
+
30
+ def init_translations
31
+ super
32
+ reset_flattened_translations!
33
+ end
34
+
35
+ protected
36
+ # flatten_hash({:a=>'a', :b=>{:c=>'c', :d=>'d', :f=>{:x=>'x'}}})
37
+ # # => {:a=>'a', :b=>{:c=>'c', :d=>'d', :f=>{:x=>'x'}}, :"b.f" => {:x=>"x"}, :"b.c"=>"c", :"b.f.x"=>"x", :"b.d"=>"d"}
38
+ def flatten_hash(h, nested_stack = [], flattened_h = {}, orig_h=h)
39
+ wind_keys(h, nil, true)
40
+ end
41
+
42
+ def flatten_translations(translations)
43
+ # don't flatten locale roots
44
+ translations.inject({}) do |flattened_h, (locale_name, locale_translations)|
45
+ flattened_h[locale_name] = flatten_hash(locale_translations)
46
+ flattened_h
47
+ end
48
+ end
49
+
50
+ def lookup(locale, key, scope = nil, separator = nil)
51
+ return unless key
52
+ init_translations unless initialized?
53
+
54
+ if separator && I18n.default_separator != separator
55
+ key = cleanup_non_standard_separator(key, separator)
56
+ scope = Array(scope).map{|k| cleanup_non_standard_separator(k, separator)} if scope
57
+ end
58
+
59
+ key = (Array(scope) + [key]).join(I18n.default_separator) if scope
60
+ flattened_translations[locale.to_sym][key.to_sym]
61
+ end
62
+
63
+ def cleanup_non_standard_separator(key, user_separator)
64
+ escape_default_separator(key).tr(user_separator, I18n.default_separator)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,6 +1,8 @@
1
1
  module I18n
2
2
  module Backend
3
3
  module Helpers
4
+ SEPARATOR_ESCAPE_CHAR = "\001"
5
+
4
6
  # Return a new hash with all keys and nested keys converted to symbols.
5
7
  def deep_symbolize_keys(hash)
6
8
  hash.inject({}) { |result, (key, value)|
@@ -13,16 +15,41 @@ module I18n
13
15
  # Flatten keys for nested Hashes by chaining up keys using the separator
14
16
  # >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
15
17
  # => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
16
- def wind_keys(hash, separator = ".", prev_key = nil, result = {})
17
- hash.inject(result) do |result, pair|
18
- key, value = *pair
19
- curr_key = [prev_key, key].compact.join(separator)
18
+ def wind_keys(hash, separator = nil, subtree = false, prev_key = nil, result = {}, orig_hash=hash)
19
+ separator ||= I18n.default_separator
20
+
21
+ hash.each_pair do |key, value|
22
+ key = escape_default_separator(key, separator)
23
+ curr_key = [prev_key, key].compact.join(separator).to_sym
24
+
25
+ if value.is_a?(Symbol)
26
+ value = hash_lookup(orig_hash, value, separator) ||
27
+ hash_lookup(hash, value, separator) || value
28
+ end
29
+
20
30
  if value.is_a?(Hash)
21
- wind_keys(value, separator, curr_key, result)
31
+ result[curr_key] = value if subtree
32
+ wind_keys(value, separator, subtree, curr_key, result, orig_hash)
22
33
  else
23
34
  result[curr_key] = value
24
35
  end
25
- result
36
+ end
37
+
38
+ result
39
+ end
40
+
41
+ def escape_default_separator(key, separator=nil)
42
+ key.to_s.tr(separator || I18n.default_separator, SEPARATOR_ESCAPE_CHAR)
43
+ end
44
+
45
+ def hash_lookup(hash, keys, separator = ".")
46
+ keys.to_s.split(separator).inject(hash) do |result, key|
47
+ key = key.to_sym
48
+ if result.respond_to?(:has_key?) and result.has_key?(key)
49
+ result[key]
50
+ else
51
+ return nil
52
+ end
26
53
  end
27
54
  end
28
55
 
@@ -32,7 +59,7 @@ module I18n
32
59
  def unwind_keys(hash, separator = ".")
33
60
  result = {}
34
61
  hash.each do |key, value|
35
- keys = key.split(separator)
62
+ keys = key.to_s.split(separator)
36
63
  curr = result
37
64
  curr = curr[keys.shift] ||= {} while keys.size > 1
38
65
  curr[keys.shift] = value
@@ -0,0 +1,119 @@
1
+ # encoding: utf-8
2
+
3
+ # The InterpolationCompiler module contains optimizations that can tremendously
4
+ # speed up the interpolation process on the Simple backend.
5
+ #
6
+ # It works by defining a pre-compiled method on stored translation Strings that
7
+ # already bring all the knowledge about contained interpolation variables etc.
8
+ # so that the actual recurring interpolation will be very fast.
9
+ #
10
+ # To enable pre-compiled interpolations you can simply include the
11
+ # InterpolationCompiler module to the Simple backend:
12
+ #
13
+ # I18n::Backend::Simple.send(:include, I18n::Backend::InterpolationCompiler)
14
+ module I18n
15
+ module Backend
16
+ module InterpolationCompiler
17
+ module Compiler
18
+ extend self
19
+
20
+ TOKENIZER = /(\\\{\{[^\}]+\}\}|\{\{[^\}]+\}\})/
21
+ INTERPOLATION_SYNTAX_PATTERN = /(\\)?(\{\{([^\}]+)\}\})/
22
+
23
+ def compile_if_an_interpolation(string)
24
+ if interpolated_str?(string)
25
+ string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
26
+ def i18n_interpolate(v = {})
27
+ "#{compiled_interpolation_body(string)}"
28
+ end
29
+ RUBY_EVAL
30
+ end
31
+
32
+ string
33
+ end
34
+
35
+ def interpolated_str?(str)
36
+ str.kind_of?(String) && str =~ INTERPOLATION_SYNTAX_PATTERN
37
+ end
38
+
39
+ protected
40
+ # tokenize("foo {{bar}} baz \\{{buz}}") # => ["foo ", "{{bar}}", " baz ", "\\{{buz}}"]
41
+ def tokenize(str)
42
+ str.split(TOKENIZER)
43
+ end
44
+
45
+ def compiled_interpolation_body(str)
46
+ tokenize(str).map do |token|
47
+ (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
48
+ end.join
49
+ end
50
+
51
+ def handle_interpolation_token(interpolation, matchdata)
52
+ escaped, pattern, key = matchdata.values_at(1, 2, 3)
53
+ escaped ? pattern : compile_interpolation_token(key.to_sym)
54
+ end
55
+
56
+ def compile_interpolation_token(key)
57
+ "\#{#{interpolate_or_raise_missing(key)}}"
58
+ end
59
+
60
+ def interpolate_or_raise_missing(key)
61
+ escaped_key = escape_key_sym(key)
62
+ Base::RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
63
+ end
64
+
65
+ def interpolate_key(key)
66
+ [direct_key(key), nil_key(key), missing_key(key)].join('||')
67
+ end
68
+
69
+ def direct_key(key)
70
+ "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
71
+ end
72
+
73
+ def nil_key(key)
74
+ "(v.has_key?(#{key}) && '')"
75
+ end
76
+
77
+ def missing_key(key)
78
+ "raise(MissingInterpolationArgument.new(#{key}, self))"
79
+ end
80
+
81
+ def reserved_key(key)
82
+ "raise(ReservedInterpolationKey.new(#{key}, self))"
83
+ end
84
+
85
+ def escape_plain_str(str)
86
+ str.gsub(/"|\\|#/) {|x| "\\#{x}"}
87
+ end
88
+
89
+ def escape_key_sym(key)
90
+ # rely on Ruby to do all the hard work :)
91
+ key.to_sym.inspect
92
+ end
93
+ end
94
+
95
+ def interpolate(locale, string, values)
96
+ if string.respond_to?(:i18n_interpolate)
97
+ string.i18n_interpolate(values)
98
+ elsif values
99
+ super
100
+ else
101
+ string
102
+ end
103
+ end
104
+
105
+ def merge_translations(locale, data)
106
+ compile_all_strings_in(data)
107
+ super
108
+ end
109
+
110
+ protected
111
+ def compile_all_strings_in(data)
112
+ data.each_value do |value|
113
+ Compiler.compile_if_an_interpolation(value)
114
+ compile_all_strings_in(value) if value.kind_of?(Hash)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -19,8 +19,8 @@ module I18n
19
19
 
20
20
  class MissingTranslationData < ArgumentError
21
21
  attr_reader :locale, :key, :options
22
- def initialize(locale, key, options)
23
- @key, @locale, @options = key, locale, options
22
+ def initialize(locale, key, opts = nil)
23
+ @key, @locale, @options = key, locale, opts || {}
24
24
  keys = I18n.send(:normalize_translation_keys, locale, key, options[:scope])
25
25
  keys << 'no key' if keys.size < 2
26
26
  super "translation missing: #{keys.join(', ')}"
@@ -0,0 +1,3 @@
1
+ module I18n
2
+ VERSION = "0.3.3"
3
+ end
@@ -3,38 +3,53 @@
3
3
  module Tests
4
4
  module Api
5
5
  module Link
6
- define_method "test linked lookup: given the key resolves to a symbol it looks up the symbol" do
7
- setup_linked_translations
8
- assert_equal 'foo', I18n.backend.translate('en', :link_to_foo)
6
+ define_method "test linked lookup: if a key resolves to a symbol it looks up the symbol" do
7
+ I18n.backend.store_translations 'en', {
8
+ :link => :linked,
9
+ :linked => 'linked'
10
+ }
11
+ assert_equal 'linked', I18n.backend.translate('en', :link)
9
12
  end
10
13
 
11
- define_method "test linked lookup: given the key resolves to a dot-separated symbol it looks up the dot-separated symbol (1)" do
12
- setup_linked_translations
13
- assert_equal('baz', I18n.backend.translate('en', :link_to_baz))
14
+ define_method "test linked lookup: if a key resolves to a dot-separated symbol it looks up the symbol" do
15
+ I18n.backend.store_translations 'en', {
16
+ :link => :"foo.linked",
17
+ :foo => { :linked => 'linked' }
18
+ }
19
+ assert_equal('linked', I18n.backend.translate('en', :link))
14
20
  end
15
-
16
- define_method "test linked lookup: given the key resolves to a dot-separated symbol it looks up the dot-separated symbol (2)" do
17
- setup_linked_translations
18
- assert_equal('buz', I18n.backend.translate('en', :'bar.link_to_buz'))
21
+
22
+ define_method "test linked lookup: if a dot-separated key resolves to a symbol it looks up the symbol" do
23
+ I18n.backend.store_translations 'en', {
24
+ :foo => { :link => :linked },
25
+ :linked => 'linked'
26
+ }
27
+ assert_equal('linked', I18n.backend.translate('en', :'foo.link'))
19
28
  end
20
29
 
21
- define_method "test linked lookup: given a scope and the key resolves to a symbol it looks up the symbol within the scope" do
22
- setup_linked_translations
23
- assert_equal('baz', I18n.backend.translate('en', :link_to_baz, :scope => :bar))
30
+ define_method "test linked lookup: if a dot-separated key resolves to a dot-separated symbol it looks up the symbol" do
31
+ I18n.backend.store_translations 'en', {
32
+ :foo => { :link => :"bar.linked" },
33
+ :bar => { :linked => 'linked' }
34
+ }
35
+ assert_equal('linked', I18n.backend.translate('en', :'foo.link'))
24
36
  end
25
37
 
26
- protected
38
+ define_method "test linked lookup: links refer to absolute keys even if a scope was given" do
39
+ I18n.backend.store_translations 'en', {
40
+ :foo => { :link => :linked, :linked => 'linked in foo' },
41
+ :linked => 'linked absolutely'
42
+ }
43
+ assert_equal 'linked absolutely', I18n.backend.translate('en', :link, :scope => :foo)
44
+ end
27
45
 
28
- def setup_linked_translations
29
- I18n.backend.store_translations 'en', {
30
- :foo => 'foo',
31
- :bar => { :baz => 'baz', :link_to_baz => :baz, :link_to_buz => :'boz.buz' },
32
- :boz => { :buz => 'buz' },
33
- :link_to_foo => :foo,
34
- :link_to_bar => :bar,
35
- :link_to_baz => :'bar.baz'
36
- }
37
- end
46
+ define_method "test linked lookup: a link can resolve to a namespace in the middle of a dot-separated key" do
47
+ I18n.backend.store_translations 'en', {
48
+ :activemodel => { :errors => { :messages => { :blank => "can't be blank" } } },
49
+ :activerecord => { :errors => { :messages => :"activemodel.errors.messages" } }
50
+ }
51
+ assert_equal "can't be blank", I18n.t(:"activerecord.errors.messages.blank")
52
+ end
38
53
  end
39
54
  end
40
55
  end
@@ -5,17 +5,21 @@ module Tests
5
5
  module Lookup
6
6
  def setup
7
7
  super
8
- store_translations(:foo => { :bar => 'bar', :baz => 'baz' })
8
+ store_translations(:foo => { :bar => 'bar', :baz => 'baz' }, :bla => false)
9
9
  end
10
-
10
+
11
11
  define_method "test lookup: given a nested key it looks up the nested hash value" do
12
12
  assert_equal 'bar', I18n.t(:'foo.bar')
13
13
  end
14
14
 
15
+ define_method "test make sure we can store a native false value as well" do
16
+ assert_equal false, I18n.t(:bla)
17
+ end
18
+
15
19
  define_method "test lookup: given a missing key, no default and no raise option it returns an error message" do
16
20
  assert_equal "translation missing: en, missing", I18n.t(:missing)
17
21
  end
18
-
22
+
19
23
  define_method "test lookup: given a missing key, no default and the raise option it raises MissingTranslationData" do
20
24
  assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) }
21
25
  end
@@ -7,8 +7,11 @@ class I18nAllFeaturesApiTest < Test::Unit::TestCase
7
7
  include I18n::Backend::Base
8
8
  include I18n::Backend::Cache
9
9
  include I18n::Backend::Metadata
10
+ include I18n::Backend::Cascade
10
11
  include I18n::Backend::Fallbacks
11
12
  include I18n::Backend::Pluralization
13
+ include I18n::Backend::Fast
14
+ include I18n::Backend::InterpolationCompiler
12
15
  end
13
16
 
14
17
  def setup
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
4
+
5
+ class I18nCascadeApiTest < Test::Unit::TestCase
6
+ class Backend
7
+ include I18n::Backend::Base
8
+ include I18n::Backend::Cascade
9
+ end
10
+
11
+ def setup
12
+ I18n.backend = Backend.new
13
+ super
14
+ end
15
+
16
+ include Tests::Api::Basics
17
+ include Tests::Api::Defaults
18
+ include Tests::Api::Interpolation
19
+ include Tests::Api::Link
20
+ include Tests::Api::Lookup
21
+ include Tests::Api::Pluralization
22
+ include Tests::Api::Procs
23
+ include Tests::Api::Localization::Date
24
+ include Tests::Api::Localization::DateTime
25
+ include Tests::Api::Localization::Time
26
+ include Tests::Api::Localization::Procs
27
+
28
+ define_method "test: make sure we use a backend with Cascade included" do
29
+ assert_equal Backend, I18n.backend.class
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
4
+
5
+ class I18nFastBackendApiTest < Test::Unit::TestCase
6
+ include Tests::Api::Basics
7
+ include Tests::Api::Defaults
8
+ include Tests::Api::Interpolation
9
+ include Tests::Api::Link
10
+ include Tests::Api::Lookup
11
+ include Tests::Api::Pluralization
12
+ include Tests::Api::Procs
13
+ include Tests::Api::Localization::Date
14
+ include Tests::Api::Localization::DateTime
15
+ include Tests::Api::Localization::Time
16
+ include Tests::Api::Localization::Procs
17
+
18
+ class FastBackend
19
+ include I18n::Backend::Base
20
+ include I18n::Backend::Fast
21
+ end
22
+
23
+ def setup
24
+ I18n.backend = FastBackend.new
25
+ super
26
+ end
27
+
28
+ define_method "test: make sure we use the FastBackend backend" do
29
+ assert_equal FastBackend, I18n.backend.class
30
+ end
31
+ end
@@ -5,7 +5,7 @@ setup_active_record
5
5
 
6
6
  class I18nActiveRecordMissingTest < Test::Unit::TestCase
7
7
  def setup
8
- store_translations(:en, :i18n => { :plural_keys => [:zero, :one, :other] })
8
+ store_translations(:en, :i18n => { :plural => { :keys => [:zero, :one, :other] } })
9
9
 
10
10
  I18n.backend = I18n::Backend::Chain.new(I18n.backend)
11
11
  I18n.backend.meta_class.send(:include, I18n::Backend::ActiveRecord::Missing)
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
4
+ require File.expand_path(File.dirname(__FILE__) + '/simple_test')
5
+
6
+ class I18nBackendFastTest < I18nBackendSimpleTest
7
+ class FastBackend
8
+ include I18n::Backend::Base
9
+ include I18n::Backend::Fast
10
+ end
11
+
12
+ def setup
13
+ super
14
+ I18n.backend = FastBackend.new
15
+ end
16
+ end
17
+
18
+ class I18nBackendFastSpecificTest < Test::Unit::TestCase
19
+ class FastBackend
20
+ include I18n::Backend::Base
21
+ include I18n::Backend::Fast
22
+ end
23
+
24
+ def setup
25
+ @backend = FastBackend.new
26
+ end
27
+
28
+ def assert_flattens(expected, nested)
29
+ assert_equal expected, @backend.send(:flatten_hash, nested)
30
+ end
31
+
32
+ def test_hash_flattening_works
33
+ assert_flattens(
34
+ {:a=>'a', :b=>{:c=>'c', :d=>'d', :f=>{:x=>'x'}}, :"b.f" => {:x=>"x"}, :"b.c"=>"c", :"b.f.x"=>"x", :"b.d"=>"d"},
35
+ {:a=>'a', :b=>{:c=>'c', :d=>'d', :f=>{:x=>'x'}}}
36
+ )
37
+ assert_flattens({:a=>{:b =>['a', 'b']}, :"a.b"=>['a', 'b']}, {:a=>{:b =>['a', 'b']}})
38
+ end
39
+
40
+ def test_pluralization_logic_and_lookup_works
41
+ counts_hash = {:zero => 'zero', :one => 'one', :other => 'other'}
42
+ @backend.store_translations :en, {:a => counts_hash}
43
+ assert_equal 'one', @backend.translate(:en, :a, :count => 1)
44
+ end
45
+
46
+ def test_translation_subtree_retrieval
47
+ @backend.store_translations :en, :a => {:foo => 'bar'}
48
+ assert_equal({:foo => 'bar'}, @backend.translate(:en, :a))
49
+ end
50
+ end
@@ -8,12 +8,12 @@ class I18nBackendHelpersTest < Test::Unit::TestCase
8
8
 
9
9
  def test_wind_keys
10
10
  hash = { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}
11
- expected = { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
11
+ expected = { :"a.b.c" => "d", :"a.b.e" => "f", :"a.g" => "h", :"i" => "j" }
12
12
  assert_equal expected, @backend.wind_keys(hash)
13
13
  end
14
14
 
15
15
  def test_unwind_keys
16
- hash = { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
16
+ hash = { "a.b.c" => "d", :"a.b.e" => "f", :"a.g" => "h", "i" => "j" }
17
17
  expected = { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}
18
18
  assert_equal expected, @backend.unwind_keys(hash)
19
19
  end
@@ -0,0 +1,107 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
4
+
5
+ class InterpolationCompilerTest < Test::Unit::TestCase
6
+ Compiler = I18n::Backend::InterpolationCompiler::Compiler
7
+
8
+ def compile_and_interpolate(str, values = {})
9
+ Compiler.compile_if_an_interpolation(str).i18n_interpolate(values)
10
+ end
11
+
12
+ def assert_escapes_interpolation_key(expected, malicious_str)
13
+ assert_equal(expected, Compiler.send(:escape_key_sym, malicious_str))
14
+ end
15
+
16
+ def test_escape_key_properly_escapes
17
+ assert_escapes_interpolation_key ':"\""', '"'
18
+ assert_escapes_interpolation_key ':"\\\\"', '\\'
19
+ assert_escapes_interpolation_key ':"\\\\\""', '\\"'
20
+ assert_escapes_interpolation_key ':"\#{}"', '#{}'
21
+ assert_escapes_interpolation_key ':"\\\\\#{}"', '\#{}'
22
+ end
23
+
24
+ def assert_escapes_plain_string(expected, plain_str)
25
+ assert_equal expected, Compiler.send(:escape_plain_str, plain_str)
26
+ end
27
+
28
+ def test_escape_plain_string_properly_escapes
29
+ assert_escapes_plain_string '\\"', '"'
30
+ assert_escapes_plain_string '\'', '\''
31
+ assert_escapes_plain_string '\\#', '#'
32
+ assert_escapes_plain_string '\\#{}', '#{}'
33
+ assert_escapes_plain_string '\\\\\\"','\\"'
34
+ end
35
+
36
+ def test_non_interpolated_strings_or_arrays_dont_get_compiled
37
+ ['abc', '\\{a}}', '{a}}', []].each do |obj|
38
+ Compiler.compile_if_an_interpolation(obj)
39
+ assert_equal false, obj.respond_to?(:i18n_interpolate)
40
+ end
41
+ end
42
+
43
+ def test_interpolated_string_gets_compiled
44
+ assert_equal '-A-', compile_and_interpolate('-{{a}}-', :a => 'A')
45
+ end
46
+
47
+ def assert_handles_key(str, key)
48
+ assert_equal 'A', compile_and_interpolate(str, key => 'A')
49
+ end
50
+
51
+ def test_compiles_fancy_keys
52
+ assert_handles_key('{{\}}', :'\\' )
53
+ assert_handles_key('{{#}}', :'#' )
54
+ assert_handles_key('{{#{}}', :'#{' )
55
+ assert_handles_key('{{#$SAFE}}', :'#$SAFE')
56
+ assert_handles_key('{{\000}}', :'\000' )
57
+ assert_handles_key('{{\'}}', :'\'' )
58
+ assert_handles_key('{{\'\'}}', :'\'\'' )
59
+ assert_handles_key('{{a.b}}', :'a.b' )
60
+ assert_handles_key('{{ }}', :' ' )
61
+ assert_handles_key('{{:}}', :':' )
62
+ assert_handles_key("{{:''}}", :":''" )
63
+ assert_handles_key('{{:"}}', :':"' )
64
+ end
65
+
66
+ def test_str_containing_only_escaped_interpolation_is_handled_correctly
67
+ assert_equal 'abc {{x}}', compile_and_interpolate('abc \\{{x}}')
68
+ end
69
+
70
+ def test_handles_weired_strings
71
+ assert_equal '#{} a', compile_and_interpolate('#{} {{a}}', :a => 'a')
72
+ assert_equal '"#{abc}"', compile_and_interpolate('"#{ab{{a}}c}"', :a => '' )
73
+ assert_equal 'a}', compile_and_interpolate('{{{a}}}', :'{a' => 'a')
74
+ assert_equal '"', compile_and_interpolate('"{{a}}', :a => '' )
75
+ assert_equal 'a{{a}}', compile_and_interpolate('{{a}}\\{{a}}', :a => 'a')
76
+ assert_equal '\\{{a}}', compile_and_interpolate('\\\\{{a}}')
77
+ assert_equal '\";eval("a")', compile_and_interpolate('\";eval("{{a}}")', :a => 'a')
78
+ assert_equal '\";eval("a")', compile_and_interpolate('\";eval("a"){{a}}',:a => '' )
79
+ assert_equal "\na", compile_and_interpolate("\n{{a}}", :a => 'a')
80
+ end
81
+ end
82
+
83
+ class I18nBackendInterpolationCompilerTest < Test::Unit::TestCase
84
+ class Backend
85
+ include I18n::Backend::Base
86
+ include I18n::Backend::InterpolationCompiler
87
+ end
88
+
89
+ include Tests::Api::Interpolation
90
+
91
+ def setup
92
+ I18n.backend = Backend.new
93
+ super
94
+ end
95
+
96
+ # pre-compile default strings to make sure we are testing I18n::Backend::InterpolationCompiler
97
+ def interpolate(*args)
98
+ options = args.last.kind_of?(Hash) ? args.last : {}
99
+ if default_str = options[:default]
100
+ I18n::Backend::InterpolationCompiler::Compiler.compile_if_an_interpolation(default_str)
101
+ end
102
+ super
103
+ end
104
+
105
+ # I kinda don't think this really is a correct behavior
106
+ undef :'test interpolation: given no values it does not alter the string'
107
+ end
@@ -39,7 +39,7 @@ class I18nBackendMetadataTest < Test::Unit::TestCase
39
39
  end
40
40
 
41
41
  define_method "test: translate adds the default to metadata on Strings" do
42
- assert_equal 'bar', I18n.t(:foo, :default => 'bar').translation_metadata[:default]
42
+ assert_equal 'bar', I18n.t(:foo, :default => 'bar', :name => '').translation_metadata[:default]
43
43
  end
44
44
 
45
45
  define_method "test: translation adds the interpolation values to metadata on Strings" do
@@ -62,6 +62,11 @@ class I18nTest < Test::Unit::TestCase
62
62
  assert_equal [:en, :foo, :bar, :baz, :buz], I18n.send(:normalize_translation_keys, :en, [:baz, :buz], [:foo, :bar])
63
63
  end
64
64
 
65
+ def test_normalize_keys_should_not_attempt_to_sym_on_empty_string
66
+ assert_equal [:en, :foo, :bar, :baz, :buz], I18n.send(:normalize_translation_keys, :en, :'baz.buz', :'foo..bar')
67
+ assert_equal [:en, :foo, :bar, :baz, :buz], I18n.send(:normalize_translation_keys, :en, :'baz.buz', :'foo......bar')
68
+ end
69
+
65
70
  def test_uses_passed_separator_to_normalize_keys
66
71
  assert_equal [:en, :foo, :bar, :baz, :buz], I18n.send(:normalize_translation_keys, :en, :'baz|buz', :'foo|bar', '|')
67
72
  end
@@ -1,12 +1,13 @@
1
1
  # encoding: utf-8
2
2
 
3
- $: << "lib"
4
- $: << File.expand_path(File.dirname(__FILE__))
3
+ $:.unshift File.expand_path("../lib", File.dirname(__FILE__))
4
+ $:.unshift File.expand_path(File.dirname(__FILE__))
5
5
 
6
- require 'rubygems'
7
- require 'test/unit'
8
6
  require 'i18n'
9
7
  require 'i18n/core_ext/object/meta_class'
8
+
9
+ require 'rubygems'
10
+ require 'test/unit'
10
11
  require 'time'
11
12
  require 'yaml'
12
13
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -13,7 +13,7 @@ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
15
 
16
- date: 2009-12-12 00:00:00 +01:00
16
+ date: 2009-12-29 00:00:00 +01:00
17
17
  default_executable:
18
18
  dependencies: []
19
19
 
@@ -30,7 +30,6 @@ files:
30
30
  - MIT-LICENSE
31
31
  - README.textile
32
32
  - Rakefile
33
- - VERSION
34
33
  - lib/i18n.rb
35
34
  - lib/i18n/backend.rb
36
35
  - lib/i18n/backend/active_record.rb
@@ -42,8 +41,10 @@ files:
42
41
  - lib/i18n/backend/cascade.rb
43
42
  - lib/i18n/backend/chain.rb
44
43
  - lib/i18n/backend/fallbacks.rb
44
+ - lib/i18n/backend/fast.rb
45
45
  - lib/i18n/backend/gettext.rb
46
46
  - lib/i18n/backend/helpers.rb
47
+ - lib/i18n/backend/interpolation_compiler.rb
47
48
  - lib/i18n/backend/metadata.rb
48
49
  - lib/i18n/backend/pluralization.rb
49
50
  - lib/i18n/backend/simple.rb
@@ -59,6 +60,7 @@ files:
59
60
  - lib/i18n/locale/tag/parents.rb
60
61
  - lib/i18n/locale/tag/rfc4646.rb
61
62
  - lib/i18n/locale/tag/simple.rb
63
+ - lib/i18n/version.rb
62
64
  - test/all.rb
63
65
  - test/api/basics.rb
64
66
  - test/api/defaults.rb
@@ -73,8 +75,10 @@ files:
73
75
  - test/api/procs.rb
74
76
  - test/cases/api/active_record_test.rb
75
77
  - test/cases/api/all_features_test.rb
78
+ - test/cases/api/cascade_test.rb
76
79
  - test/cases/api/chain_test.rb
77
80
  - test/cases/api/fallbacks_test.rb
81
+ - test/cases/api/fast_test.rb
78
82
  - test/cases/api/pluralization_test.rb
79
83
  - test/cases/api/simple_test.rb
80
84
  - test/cases/backend/active_record/missing_test.rb
@@ -83,7 +87,9 @@ files:
83
87
  - test/cases/backend/cascade_test.rb
84
88
  - test/cases/backend/chain_test.rb
85
89
  - test/cases/backend/fallbacks_test.rb
90
+ - test/cases/backend/fast_test.rb
86
91
  - test/cases/backend/helpers_test.rb
92
+ - test/cases/backend/interpolation_compiler_test.rb
87
93
  - test/cases/backend/metadata_test.rb
88
94
  - test/cases/backend/pluralization_test.rb
89
95
  - test/cases/backend/simple_test.rb
@@ -145,8 +151,10 @@ test_files:
145
151
  - test/api/procs.rb
146
152
  - test/cases/api/active_record_test.rb
147
153
  - test/cases/api/all_features_test.rb
154
+ - test/cases/api/cascade_test.rb
148
155
  - test/cases/api/chain_test.rb
149
156
  - test/cases/api/fallbacks_test.rb
157
+ - test/cases/api/fast_test.rb
150
158
  - test/cases/api/pluralization_test.rb
151
159
  - test/cases/api/simple_test.rb
152
160
  - test/cases/backend/active_record/missing_test.rb
@@ -155,7 +163,9 @@ test_files:
155
163
  - test/cases/backend/cascade_test.rb
156
164
  - test/cases/backend/chain_test.rb
157
165
  - test/cases/backend/fallbacks_test.rb
166
+ - test/cases/backend/fast_test.rb
158
167
  - test/cases/backend/helpers_test.rb
168
+ - test/cases/backend/interpolation_compiler_test.rb
159
169
  - test/cases/backend/metadata_test.rb
160
170
  - test/cases/backend/pluralization_test.rb
161
171
  - test/cases/backend/simple_test.rb
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.3.2