activesupport 2.3.5 → 2.3.6.pre

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

Potentially problematic release.


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

Files changed (121) hide show
  1. data/CHANGELOG +23 -0
  2. data/lib/active_support.rb +1 -0
  3. data/lib/active_support/core_ext/date_time/conversions.rb +14 -3
  4. data/lib/active_support/core_ext/enumerable.rb +6 -0
  5. data/lib/active_support/core_ext/file/atomic.rb +2 -1
  6. data/lib/active_support/core_ext/numeric/conversions.rb +2 -2
  7. data/lib/active_support/core_ext/object.rb +1 -0
  8. data/lib/active_support/core_ext/object/metaclass.rb +6 -5
  9. data/lib/active_support/core_ext/object/singleton_class.rb +13 -0
  10. data/lib/active_support/core_ext/string.rb +0 -1
  11. data/lib/active_support/core_ext/string/output_safety.rb +148 -44
  12. data/lib/active_support/core_ext/time/calculations.rb +1 -1
  13. data/lib/active_support/inflector.rb +1 -1
  14. data/lib/active_support/json/backends/yajl.rb +40 -0
  15. data/lib/active_support/json/decoding.rb +16 -1
  16. data/lib/active_support/json/encoding.rb +11 -2
  17. data/lib/active_support/ordered_hash.rb +24 -1
  18. data/lib/active_support/vendor.rb +10 -2
  19. data/lib/active_support/vendor/i18n-0.3.3/CHANGELOG.textile +76 -0
  20. data/lib/active_support/vendor/{i18n-0.1.3 → i18n-0.3.3}/MIT-LICENSE +0 -0
  21. data/lib/active_support/vendor/i18n-0.3.3/README.textile +81 -0
  22. data/lib/active_support/vendor/i18n-0.3.3/Rakefile +24 -0
  23. data/lib/active_support/vendor/i18n-0.3.3/benchmark/example.yml +144 -0
  24. data/lib/active_support/vendor/i18n-0.3.3/benchmark/run.rb +71 -0
  25. data/lib/active_support/vendor/i18n-0.3.3/contributors.txt +17 -0
  26. data/lib/active_support/vendor/i18n-0.3.3/i18n.gemspec +165 -0
  27. data/lib/active_support/vendor/i18n-0.3.3/init.rb +1 -0
  28. data/lib/active_support/vendor/{i18n-0.1.3 → i18n-0.3.3}/lib/i18n.rb +99 -21
  29. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend.rb +17 -0
  30. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/active_record.rb +70 -0
  31. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/active_record/missing.rb +67 -0
  32. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/active_record/store_procs.rb +38 -0
  33. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/active_record/translation.rb +83 -0
  34. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/base.rb +259 -0
  35. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/cache.rb +75 -0
  36. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/cascade.rb +44 -0
  37. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/chain.rb +74 -0
  38. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/fallbacks.rb +52 -0
  39. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/fast.rb +68 -0
  40. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/gettext.rb +75 -0
  41. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/helpers.rb +80 -0
  42. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/interpolation_compiler.rb +119 -0
  43. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/metadata.rb +73 -0
  44. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/pluralization.rb +57 -0
  45. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/backend/simple.rb +22 -0
  46. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/core_ext/object/meta_class.rb +5 -0
  47. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/core_ext/string/interpolate.rb +99 -0
  48. data/lib/active_support/vendor/{i18n-0.1.3 → i18n-0.3.3}/lib/i18n/exceptions.rb +14 -6
  49. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/gettext.rb +25 -0
  50. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/helpers.rb +5 -0
  51. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/helpers/gettext.rb +64 -0
  52. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/locale.rb +6 -0
  53. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/locale/fallbacks.rb +98 -0
  54. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/locale/tag.rb +28 -0
  55. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/locale/tag/parents.rb +24 -0
  56. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/locale/tag/rfc4646.rb +76 -0
  57. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/locale/tag/simple.rb +41 -0
  58. data/lib/active_support/vendor/i18n-0.3.3/lib/i18n/version.rb +3 -0
  59. data/lib/active_support/vendor/i18n-0.3.3/test/all.rb +8 -0
  60. data/lib/active_support/vendor/i18n-0.3.3/test/api/basics.rb +15 -0
  61. data/lib/active_support/vendor/i18n-0.3.3/test/api/defaults.rb +40 -0
  62. data/lib/active_support/vendor/i18n-0.3.3/test/api/interpolation.rb +92 -0
  63. data/lib/active_support/vendor/i18n-0.3.3/test/api/link.rb +55 -0
  64. data/lib/active_support/vendor/i18n-0.3.3/test/api/localization/date.rb +91 -0
  65. data/lib/active_support/vendor/i18n-0.3.3/test/api/localization/date_time.rb +90 -0
  66. data/lib/active_support/vendor/i18n-0.3.3/test/api/localization/procs.rb +54 -0
  67. data/lib/active_support/vendor/i18n-0.3.3/test/api/localization/time.rb +84 -0
  68. data/lib/active_support/vendor/i18n-0.3.3/test/api/lookup.rb +45 -0
  69. data/lib/active_support/vendor/i18n-0.3.3/test/api/pluralization.rb +35 -0
  70. data/lib/active_support/vendor/i18n-0.3.3/test/api/procs.rb +40 -0
  71. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/active_record_test.rb +29 -0
  72. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/all_features_test.rb +40 -0
  73. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/cascade_test.rb +31 -0
  74. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/chain_test.rb +26 -0
  75. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/fallbacks_test.rb +33 -0
  76. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/fast_test.rb +31 -0
  77. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/pluralization_test.rb +33 -0
  78. data/lib/active_support/vendor/i18n-0.3.3/test/cases/api/simple_test.rb +21 -0
  79. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/active_record/missing_test.rb +60 -0
  80. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/active_record_test.rb +52 -0
  81. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/cache_test.rb +72 -0
  82. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/cascade_test.rb +66 -0
  83. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/chain_test.rb +64 -0
  84. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/fallbacks_test.rb +57 -0
  85. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/fast_test.rb +50 -0
  86. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/helpers_test.rb +26 -0
  87. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/interpolation_compiler_test.rb +107 -0
  88. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/metadata_test.rb +67 -0
  89. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/pluralization_test.rb +43 -0
  90. data/lib/active_support/vendor/i18n-0.3.3/test/cases/backend/simple_test.rb +77 -0
  91. data/lib/active_support/vendor/i18n-0.3.3/test/cases/core_ext/string/interpolate_test.rb +94 -0
  92. data/lib/active_support/vendor/i18n-0.3.3/test/cases/gettext/api_test.rb +201 -0
  93. data/lib/active_support/vendor/i18n-0.3.3/test/cases/gettext/backend_test.rb +91 -0
  94. data/lib/active_support/vendor/{i18n-0.1.3/test → i18n-0.3.3/test/cases}/i18n_exceptions_test.rb +8 -10
  95. data/lib/active_support/vendor/i18n-0.3.3/test/cases/i18n_load_path_test.rb +23 -0
  96. data/lib/active_support/vendor/i18n-0.3.3/test/cases/i18n_test.rb +172 -0
  97. data/lib/active_support/vendor/i18n-0.3.3/test/cases/locale/fallbacks_test.rb +126 -0
  98. data/lib/active_support/vendor/i18n-0.3.3/test/cases/locale/tag/rfc4646_test.rb +143 -0
  99. data/lib/active_support/vendor/i18n-0.3.3/test/cases/locale/tag/simple_test.rb +33 -0
  100. data/lib/active_support/vendor/i18n-0.3.3/test/fixtures/locales/de.po +72 -0
  101. data/lib/active_support/vendor/i18n-0.3.3/test/fixtures/locales/en.rb +3 -0
  102. data/lib/active_support/vendor/i18n-0.3.3/test/fixtures/locales/en.yml +3 -0
  103. data/lib/active_support/vendor/i18n-0.3.3/test/fixtures/locales/plurals.rb +113 -0
  104. data/lib/active_support/vendor/i18n-0.3.3/test/test_helper.rb +100 -0
  105. data/lib/active_support/vendor/i18n-0.3.3/vendor/po_parser.rb +329 -0
  106. data/lib/active_support/version.rb +1 -1
  107. data/lib/active_support/whiny_nil.rb +1 -1
  108. data/lib/active_support/xml_mini/libxml.rb +23 -83
  109. data/lib/active_support/xml_mini/libxmlsax.rb +74 -0
  110. data/lib/active_support/xml_mini/nokogiri.rb +25 -22
  111. data/lib/active_support/xml_mini/nokogirisax.rb +73 -0
  112. metadata +108 -20
  113. data/lib/active_support/vendor/i18n-0.1.3/README.textile +0 -20
  114. data/lib/active_support/vendor/i18n-0.1.3/Rakefile +0 -5
  115. data/lib/active_support/vendor/i18n-0.1.3/i18n.gemspec +0 -27
  116. data/lib/active_support/vendor/i18n-0.1.3/lib/i18n/backend/simple.rb +0 -214
  117. data/lib/active_support/vendor/i18n-0.1.3/test/all.rb +0 -5
  118. data/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb +0 -124
  119. data/lib/active_support/vendor/i18n-0.1.3/test/locale/en.rb +0 -1
  120. data/lib/active_support/vendor/i18n-0.1.3/test/locale/en.yml +0 -3
  121. data/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb +0 -567
@@ -0,0 +1,67 @@
1
+ # This extension stores translation stub records for missing translations to
2
+ # the database.
3
+ #
4
+ # This is useful if you have a web based translation tool. It will populate
5
+ # the database with untranslated keys as the application is being used. A
6
+ # translator can then go through these and add missing translations.
7
+ #
8
+ # Example usage:
9
+ #
10
+ # I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
11
+ # I18n.backend = I18nChainBackend.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
12
+ #
13
+ # Stub records for pluralizations will also be created for each key defined
14
+ # in i18n.plural.keys.
15
+ #
16
+ # For example:
17
+ #
18
+ # # en.yml
19
+ # en:
20
+ # i18n:
21
+ # plural:
22
+ # keys: [:zero, :one, :other]
23
+ #
24
+ # # pl.yml
25
+ # pl:
26
+ # i18n:
27
+ # plural:
28
+ # keys: [:zero, :one, :few, :other]
29
+ #
30
+ # It will also persist interpolation keys in Translation#interpolations so
31
+ # translators will be able to review and use them.
32
+ module I18n
33
+ module Backend
34
+ class ActiveRecord
35
+ module Missing
36
+ def store_default_translations(locale, key, options = {})
37
+ count, scope, default, separator = options.values_at(:count, *Base::RESERVED_KEYS)
38
+ separator ||= I18n.default_separator
39
+
40
+ keys = I18n.send(:normalize_translation_keys, locale, key, scope, separator)[1..-1]
41
+ key = keys.join(separator || I18n.default_separator)
42
+
43
+ unless ActiveRecord::Translation.locale(locale).lookup(key, separator).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]
46
+ keys.each { |key| store_default_translation(locale, key, interpolations) }
47
+ end
48
+ end
49
+
50
+ def store_default_translation(locale, key, interpolations)
51
+ translation = ActiveRecord::Translation.new :locale => locale.to_s, :key => key
52
+ translation.interpolations = interpolations
53
+ translation.save
54
+ end
55
+
56
+ def translate(locale, key, options = {})
57
+ super
58
+
59
+ rescue I18n::MissingTranslationData => e
60
+ self.store_default_translations(locale, key, options)
61
+
62
+ raise e
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,38 @@
1
+ # This module is intended to be mixed into the ActiveRecord backend to allow
2
+ # storing Ruby Procs as translation values in the database.
3
+ #
4
+ # I18n.backend = I18n::Backend::ActiveRecord.new
5
+ # I18n::Backend::ActiveRecord::Translation.send(:include, I18n::Backend::ActiveRecord::StoreProcs)
6
+ #
7
+ # The StoreProcs module requires the ParseTree and ruby2ruby gems and therefor
8
+ # was extracted from the original backend.
9
+ #
10
+ # ParseTree is not compatible with Ruby 1.9.
11
+
12
+ begin
13
+ require 'ruby2ruby'
14
+ require 'parse_tree'
15
+ require 'parse_tree_extensions'
16
+ rescue LoadError => e
17
+ puts "can't use StoreProcs because: #{e.message}"
18
+ end
19
+
20
+ module I18n
21
+ module Backend
22
+ class ActiveRecord
23
+ module StoreProcs
24
+ def value=(v)
25
+ case v
26
+ when Proc
27
+ write_attribute(:value, v.to_ruby)
28
+ write_attribute(:is_proc, true)
29
+ else
30
+ write_attribute(:value, v)
31
+ end
32
+ end
33
+
34
+ Translation.send(:include, self) unless RUBY_VERSION >= '1.9'
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,83 @@
1
+ require 'active_record'
2
+
3
+ module I18n
4
+ module Backend
5
+ # ActiveRecord model used to store actual translations to the database.
6
+ #
7
+ # This model expects a table like the following to be already set up in
8
+ # your the database:
9
+ #
10
+ # create_table :translations do |t|
11
+ # t.string :locale
12
+ # t.string :key
13
+ # t.string :value
14
+ # t.boolean :is_proc, :default => false
15
+ # end
16
+ #
17
+ # This model supports to named scopes :locale and :lookup. The :locale
18
+ # scope simply adds a condition for a given locale:
19
+ #
20
+ # I18n::Backend::ActiveRecord::Translation.locale(:en).all
21
+ # # => all translation records that belong to the :en locale
22
+ #
23
+ # The :lookup scope adds a condition for looking up all translations
24
+ # that either start with the given keys (joined by an optionally given
25
+ # separator or I18n.default_separator) or that exactly have this key.
26
+ #
27
+ # # with translations present for :"foo.bar" and :"foo.baz"
28
+ # I18n::Backend::ActiveRecord::Translation.lookup(:foo)
29
+ # # => an array with both translation records :"foo.bar" and :"foo.baz"
30
+ #
31
+ # I18n::Backend::ActiveRecord::Translation.lookup([:foo, :bar])
32
+ # I18n::Backend::ActiveRecord::Translation.lookup(:"foo.bar")
33
+ # # => an array with the translation record :"foo.bar"
34
+ #
35
+ # When the StoreProcs module was mixed into this model then Procs will
36
+ # be stored to the database as Ruby code and evaluated when :value is
37
+ # called.
38
+ #
39
+ # Translation = I18n::Backend::ActiveRecord::Translation
40
+ # Translation.create \
41
+ # :locale => 'en'
42
+ # :key => 'foo'
43
+ # :value => lambda { |key, options| 'FOO' }
44
+ # Translation.find_by_locale_and_key('en', 'foo').value
45
+ # # => 'FOO'
46
+ class ActiveRecord
47
+ class Translation < ::ActiveRecord::Base
48
+ set_table_name 'translations'
49
+ attr_protected :is_proc, :interpolations
50
+
51
+ serialize :value
52
+ serialize :interpolations, Array
53
+
54
+ named_scope :locale, lambda { |locale|
55
+ { :conditions => { :locale => locale.to_s } }
56
+ }
57
+
58
+ named_scope :lookup, lambda { |keys, *separator|
59
+ keys = Array(keys).map! { |key| key.to_s }
60
+ separator = separator.first || I18n.default_separator
61
+ { :conditions => ["`key` IN (?) OR `key` LIKE '#{keys.last}#{separator}%'", keys] }
62
+ }
63
+
64
+ def self.available_locales
65
+ Translation.find(:all, :select => 'DISTINCT locale').map { |t| t.locale.to_sym }
66
+ end
67
+
68
+ def interpolates?(key)
69
+ self.interpolations.include?(key) if self.interpolations
70
+ end
71
+
72
+ def value
73
+ if is_proc
74
+ Kernel.eval read_attribute(:value)
75
+ else
76
+ value = read_attribute(:value)
77
+ value == 'f' ? false : value
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,259 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ module I18n
6
+ module Backend
7
+ module Base
8
+ include I18n::Backend::Helpers
9
+
10
+ RESERVED_KEYS = [:scope, :default, :separator]
11
+ INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
12
+
13
+ # Accepts a list of paths to translation files. Loads translations from
14
+ # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
15
+ # for details.
16
+ def load_translations(*filenames)
17
+ filenames.each { |filename| load_file(filename) }
18
+ end
19
+
20
+ # Stores translations for the given locale in memory.
21
+ # This uses a deep merge for the translations hash, so existing
22
+ # translations will be overwritten by new ones only at the deepest
23
+ # level of the hash.
24
+ def store_translations(locale, data, options = {})
25
+ merge_translations(locale, data)
26
+ end
27
+
28
+ def translate(locale, key, options = {})
29
+ raise InvalidLocale.new(locale) unless locale
30
+ return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
31
+
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) }
38
+
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
46
+
47
+ entry
48
+ end
49
+
50
+ # Acts the same as +strftime+, but uses a localized version of the
51
+ # format string. Takes a key from the date/time formats translations as
52
+ # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
53
+ def localize(locale, object, format = :default, options = {})
54
+ raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
55
+
56
+ if Symbol === format
57
+ key = format
58
+ type = object.respond_to?(:sec) ? 'time' : 'date'
59
+ format = lookup(locale, :"#{type}.formats.#{key}")
60
+ raise(MissingTranslationData.new(locale, key, options)) if format.nil?
61
+ end
62
+
63
+ format = resolve(locale, object, format, options)
64
+ format = format.to_s.gsub(/%[aAbBp]/) do |match|
65
+ case match
66
+ when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
67
+ when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
68
+ when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
69
+ when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
70
+ when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
71
+ end
72
+ end
73
+
74
+ object.strftime(format)
75
+ end
76
+
77
+ def initialized?
78
+ @initialized ||= false
79
+ end
80
+
81
+ # Returns an array of locales for which translations are available
82
+ # ignoring the reserved translation meta data key :i18n.
83
+ def available_locales
84
+ init_translations unless initialized?
85
+ translations.inject([]) do |locales, (locale, data)|
86
+ locales << locale unless (data.keys - [:i18n]).empty?
87
+ locales
88
+ end
89
+ end
90
+
91
+ def reload!
92
+ @initialized = false
93
+ @translations = nil
94
+ end
95
+
96
+ protected
97
+ def init_translations
98
+ load_translations(*I18n.load_path.flatten)
99
+ @initialized = true
100
+ end
101
+
102
+ def translations
103
+ @translations ||= {}
104
+ end
105
+
106
+ # Looks up a translation from the translations hash. Returns nil if
107
+ # eiher key is nil, or locale, scope or key do not exist as a key in the
108
+ # nested translations hash. Splits keys or scopes containing dots
109
+ # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
110
+ # <tt>%w(currency format)</tt>.
111
+ def lookup(locale, key, scope = [], separator = nil)
112
+ return unless key
113
+ init_translations unless initialized?
114
+ keys = I18n.send(:normalize_translation_keys, locale, key, scope, separator)
115
+ keys.inject(translations) do |result, key|
116
+ key = key.to_sym
117
+ return nil unless result.is_a?(Hash) && result.has_key?(key)
118
+ result = result[key]
119
+ result = resolve(locale, key, result, :separator => separator) if result.is_a?(Symbol)
120
+ result
121
+ end
122
+ end
123
+
124
+ # Evaluates defaults.
125
+ # If given subject is an Array, it walks the array and returns the
126
+ # first translation that can be resolved. Otherwise it tries to resolve
127
+ # the translation directly.
128
+ def default(locale, object, subject, options = {})
129
+ options = options.dup.reject { |key, value| key == :default }
130
+ case subject
131
+ when Array
132
+ subject.each do |item|
133
+ result = resolve(locale, object, item, options) and return result
134
+ end and nil
135
+ else
136
+ resolve(locale, object, subject, options)
137
+ end
138
+ end
139
+
140
+ # Resolves a translation.
141
+ # If the given subject is a Symbol, it will be translated with the
142
+ # given options. If it is a Proc then it will be evaluated. All other
143
+ # subjects will be returned directly.
144
+ def resolve(locale, object, subject, options = nil)
145
+ case subject
146
+ when Symbol
147
+ I18n.translate(subject, (options || {}).merge(:locale => locale, :raise => true))
148
+ when Proc
149
+ resolve(locale, object, subject.call(object, options), options = {})
150
+ else
151
+ subject
152
+ end
153
+ rescue MissingTranslationData
154
+ nil
155
+ end
156
+
157
+ # Picks a translation from an array according to English pluralization
158
+ # rules. It will pick the first translation if count is not equal to 1
159
+ # and the second translation if it is equal to 1. Other backends can
160
+ # implement more flexible or complex pluralization rules.
161
+ def pluralize(locale, entry, count)
162
+ return entry unless entry.is_a?(Hash) and count
163
+
164
+ key = :zero if count == 0 && entry.has_key?(:zero)
165
+ key ||= count == 1 ? :one : :other
166
+ raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
167
+ entry[key]
168
+ end
169
+
170
+ # Interpolates values into a given string.
171
+ #
172
+ # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
173
+ # # => "file test.txt opened by {{user}}"
174
+ #
175
+ # Note that you have to double escape the <tt>\\</tt> when you want to escape
176
+ # the <tt>{{...}}</tt> key in a string (once for the string and once for the
177
+ # interpolation).
178
+ def interpolate(locale, string, values = {})
179
+ return string unless string.is_a?(String) && !values.empty?
180
+
181
+ preserve_encoding(string) do
182
+ s = string.gsub(INTERPOLATION_SYNTAX_PATTERN) do
183
+ escaped, key = $1, $2.to_sym
184
+ if escaped
185
+ "{{#{key}}}"
186
+ elsif RESERVED_KEYS.include?(key)
187
+ raise ReservedInterpolationKey.new(key, string)
188
+ else
189
+ "%{#{key}}"
190
+ end
191
+ end
192
+
193
+ values.each do |key, value|
194
+ value = value.call(values) if interpolate_lambda?(value, s, key)
195
+ value = value.to_s unless value.is_a?(String)
196
+ values[key] = value
197
+ end
198
+
199
+ s % values
200
+ end
201
+
202
+ rescue KeyError => e
203
+ raise MissingInterpolationArgument.new(values, string)
204
+ end
205
+
206
+ def preserve_encoding(string)
207
+ if string.respond_to?(:encoding)
208
+ encoding = string.encoding
209
+ result = yield
210
+ result.force_encoding(encoding) if result.respond_to?(:force_encoding)
211
+ result
212
+ else
213
+ yield
214
+ end
215
+ end
216
+
217
+ # returns true when the given value responds to :call and the key is
218
+ # an interpolation placeholder in the given string
219
+ def interpolate_lambda?(object, string, key)
220
+ object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
221
+ end
222
+
223
+ # Loads a single translations file by delegating to #load_rb or
224
+ # #load_yml depending on the file extension and directly merges the
225
+ # data to the existing translations. Raises I18n::UnknownFileType
226
+ # for all other file extensions.
227
+ def load_file(filename)
228
+ type = File.extname(filename).tr('.', '').downcase
229
+ raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
230
+ data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash
231
+ data.each { |locale, d| merge_translations(locale, d) }
232
+ end
233
+
234
+ # Loads a plain Ruby translations file. eval'ing the file must yield
235
+ # a Hash containing translation data with locales as toplevel keys.
236
+ def load_rb(filename)
237
+ eval(IO.read(filename), binding, filename)
238
+ end
239
+
240
+ # Loads a YAML translations file. The data must have locales as
241
+ # toplevel keys.
242
+ def load_yml(filename)
243
+ YAML::load(IO.read(filename))
244
+ end
245
+
246
+ # Deep merges the given translations hash with the existing translations
247
+ # for the given locale
248
+ def merge_translations(locale, data)
249
+ locale = locale.to_sym
250
+ translations[locale] ||= {}
251
+ data = deep_symbolize_keys(data)
252
+
253
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
254
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
255
+ translations[locale].merge!(data, &merger)
256
+ end
257
+ end
258
+ end
259
+ end