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,75 @@
1
+ # encoding: utf-8
2
+
3
+ # This module allows you to easily cache all responses from the backend - thus
4
+ # speeding up the I18n aspects of your application quite a bit.
5
+ #
6
+ # To enable caching you can simply include the Cache module to the Simple
7
+ # backend - or whatever other backend you are using:
8
+ #
9
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
10
+ #
11
+ # You will also need to set a cache store implementation that you want to use:
12
+ #
13
+ # I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
14
+ #
15
+ # You can use any cache implementation you want that provides the same API as
16
+ # ActiveSupport::Cache (only the methods #fetch and #write are being used).
17
+ #
18
+ # The cache_key implementation assumes that you only pass values to
19
+ # I18n.translate that return a valid key from #hash (see
20
+ # http://www.ruby-doc.org/core/classes/Object.html#M000337).
21
+ module I18n
22
+ class << self
23
+ @@cache_store = nil
24
+ @@cache_namespace = nil
25
+
26
+ def cache_store
27
+ @@cache_store
28
+ end
29
+
30
+ def cache_store=(store)
31
+ @@cache_store = store
32
+ end
33
+
34
+ def cache_namespace
35
+ @@cache_namespace
36
+ end
37
+
38
+ def cache_namespace=(namespace)
39
+ @@cache_namespace = namespace
40
+ end
41
+
42
+ def perform_caching?
43
+ !cache_store.nil?
44
+ end
45
+ end
46
+
47
+ module Backend
48
+ module Cache
49
+ def translate(*args)
50
+ I18n.perform_caching? ? fetch(*args) { super } : super
51
+ end
52
+
53
+ protected
54
+
55
+ def fetch(*args, &block)
56
+ result = I18n.cache_store.fetch(cache_key(*args), &block)
57
+ raise result if result.is_a?(Exception)
58
+ result
59
+ rescue MissingTranslationData => exception
60
+ I18n.cache_store.write(cache_key(*args), exception)
61
+ raise exception
62
+ end
63
+
64
+ def cache_key(*args)
65
+ # This assumes that only simple, native Ruby values are passed to I18n.translate.
66
+ # Also, in Ruby < 1.8.7 {}.hash != {}.hash
67
+ # (see http://paulbarry.com/articles/2009/09/14/why-rails-3-will-require-ruby-1-8-7)
68
+ # If args.inspect does not work for you for some reason, patches are very welcome :)
69
+ hash = RUBY_VERSION >= "1.8.7" ? args.hash : args.inspect
70
+ keys = ['i18n', I18n.cache_namespace, hash]
71
+ keys.compact.join('-')
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ # EXPERIMENTAL
4
+ #
5
+ # The cascade module adds the ability to do cascading lookups to backends that
6
+ # are compatible to the Simple backend.
7
+ #
8
+ # By cascading lookups we mean that for any key that can not be found the
9
+ # Cascade module strips one segment off the scope part of the key and then
10
+ # tries to look up the key in that scope.
11
+ #
12
+ # E.g. when a lookup for the key :"foo.bar.baz" does not yield a result then
13
+ # the segment :bar will be stripped off the scope part :"foo.bar" and the new
14
+ # scope :foo will be used to look up the key :baz. If that does not succeed
15
+ # then the remaining scope segment :foo will be omitted, too, and again the
16
+ # key :baz will be looked up (now with no scope).
17
+ #
18
+ # Defaults will only kick in after the cascading lookups haven't succeeded.
19
+ #
20
+ # This behavior is useful for libraries like ActiveRecord validations where
21
+ # the library wants to give users a bunch of more or less fine-grained options
22
+ # of scopes for a particular key.
23
+ #
24
+ # Thanks to Clemens Kofler for the initial idea and implementation! See
25
+ # http://github.com/clemens/i18n-cascading-backend
26
+
27
+ module I18n
28
+ @@fallbacks = nil
29
+
30
+ module Backend
31
+ module Cascade
32
+ def lookup(locale, key, scope = [], separator = nil)
33
+ return unless key
34
+ locale, *scope = I18n.send(:normalize_translation_keys, locale, key, scope, separator)
35
+ key = scope.pop
36
+
37
+ begin
38
+ result = super
39
+ return result unless result.nil?
40
+ end while scope.pop
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+
3
+ module I18n
4
+ module Backend
5
+ # Backend that chains multiple other backends and checks each of them when
6
+ # a translation needs to be looked up. This is useful when you want to use
7
+ # standard translations with a Simple backend but store custom application
8
+ # translations in a database or other backends.
9
+ #
10
+ # To use the Chain backend instantiate it and set it to the I18n module.
11
+ # You can add chained backends through the initializer or backends
12
+ # accessor:
13
+ #
14
+ # # preserves the existing Simple backend set to I18n.backend
15
+ # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
16
+ #
17
+ # The implementation assumes that all backends added to the Chain implement
18
+ # a lookup method with the same API as Simple backend does.
19
+ class Chain < Simple
20
+ attr_accessor :backends
21
+
22
+ def initialize(*backends)
23
+ self.backends = backends
24
+ end
25
+
26
+ def reload!
27
+ backends.each { |backend| backend.reload! }
28
+ end
29
+
30
+ def store_translations(locale, data, options = {})
31
+ backends.first.store_translations(locale, data, options = {})
32
+ end
33
+
34
+ def available_locales
35
+ backends.map { |backend| backend.available_locales }.flatten.uniq
36
+ end
37
+
38
+ def translate(locale, key, options = {})
39
+ return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
40
+
41
+ default = options.delete(:default)
42
+ namespace = {}
43
+ backends.each do |backend|
44
+ begin
45
+ options.update(:default => default) if default and backend == backends.last
46
+ translation = backend.translate(locale, key, options)
47
+ if namespace_lookup?(translation, options)
48
+ namespace.update(translation)
49
+ elsif !translation.nil?
50
+ return translation
51
+ end
52
+ rescue MissingTranslationData
53
+ end
54
+ end
55
+ return namespace unless namespace.empty?
56
+ raise(I18n::MissingTranslationData.new(locale, key, options))
57
+ end
58
+
59
+ def localize(locale, object, format = :default, options = {})
60
+ backends.each do |backend|
61
+ begin
62
+ result = backend.localize(locale, object, format, options) and return result
63
+ rescue MissingTranslationData
64
+ end
65
+ end and nil
66
+ end
67
+
68
+ protected
69
+ def namespace_lookup?(result, options)
70
+ result.is_a?(Hash) and not options.has_key?(:count)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ # I18n locale fallbacks are useful when you want your application to use
4
+ # translations from other locales when translations for the current locale are
5
+ # missing. E.g. you might want to use :en translations when translations in
6
+ # your applications main locale :de are missing.
7
+ #
8
+ # To enable locale fallbacks you can simply include the Fallbacks module to
9
+ # the Simple backend - or whatever other backend you are using:
10
+ #
11
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
12
+ module I18n
13
+ @@fallbacks = nil
14
+
15
+ class << self
16
+ # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
17
+ def fallbacks
18
+ @@fallbacks ||= I18n::Locale::Fallbacks.new
19
+ end
20
+
21
+ # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
22
+ def fallbacks=(fallbacks)
23
+ @@fallbacks = fallbacks
24
+ end
25
+ end
26
+
27
+ module Backend
28
+ module Fallbacks
29
+ # Overwrites the Base backend translate method so that it will try each
30
+ # locale given by I18n.fallbacks for the given locale. E.g. for the
31
+ # locale :"de-DE" it might try the locales :"de-DE", :de and :en
32
+ # (depends on the fallbacks implementation) until it finds a result with
33
+ # the given options. If it does not find any result for any of the
34
+ # locales it will then raise a MissingTranslationData exception as
35
+ # usual.
36
+ #
37
+ # The default option takes precedence over fallback locales, i.e. it
38
+ # will first evaluate a given default option before falling back to
39
+ # another locale.
40
+ def translate(locale, key, options = {})
41
+ I18n.fallbacks[locale].each do |fallback|
42
+ begin
43
+ result = super(fallback, key, options)
44
+ return result unless result.nil?
45
+ rescue I18n::MissingTranslationData
46
+ end
47
+ end
48
+ raise(I18n::MissingTranslationData.new(locale, key, options))
49
+ end
50
+ end
51
+ end
52
+ 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
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ require 'i18n/gettext'
4
+ require File.expand_path(File.dirname(__FILE__) + '/../../../vendor/po_parser.rb')
5
+
6
+ # Experimental support for using Gettext po files to store translations.
7
+ #
8
+ # To use this you can simply include the module to the Simple backend - or
9
+ # whatever other backend you are using.
10
+ #
11
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Gettext)
12
+ #
13
+ # Now you should be able to include your Gettext translation (*.po) files to
14
+ # the I18n.load_path so they're loaded to the backend and you can use them as
15
+ # usual:
16
+ #
17
+ # I18n.load_path += Dir["path/to/locales/*.po"]
18
+ #
19
+ # Following the Gettext convention this implementation expects that your
20
+ # translation files are named by their locales. E.g. the file en.po would
21
+ # contain the translations for the English locale.
22
+ module I18n
23
+ module Backend
24
+ module Gettext
25
+ class PoData < Hash
26
+ def set_comment(msgid_or_sym, comment)
27
+ # ignore
28
+ end
29
+ end
30
+
31
+ protected
32
+ def load_po(filename)
33
+ locale = ::File.basename(filename, '.po').to_sym
34
+ data = normalize(locale, parse(filename))
35
+ { locale => data }
36
+ end
37
+
38
+ def parse(filename)
39
+ GetText::PoParser.new.parse(::File.read(filename), PoData.new)
40
+ end
41
+
42
+ def normalize(locale, data)
43
+ data.inject({}) do |result, (key, value)|
44
+ unless key.nil? || key.empty?
45
+ key, value = normalize_pluralization(locale, key, value) if key.index("\000")
46
+
47
+ parts = key.split('|').reverse
48
+ normalized = parts.inject({}) do |normalized, part|
49
+ normalized = { part => normalized.empty? ? value : normalized }
50
+ end
51
+
52
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
53
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
54
+ result.merge!(normalized, &merger)
55
+ end
56
+ result
57
+ end
58
+ end
59
+
60
+ def normalize_pluralization(locale, key, value)
61
+ # FIXME po_parser includes \000 chars that can not be turned into Symbols
62
+ key = key.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR).split(I18n::Gettext::PLURAL_SEPARATOR).first
63
+
64
+ keys = I18n::Gettext.plural_keys(locale)
65
+ values = value.split("\000")
66
+ raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
67
+
68
+ result = {}
69
+ values.each_with_index { |value, ix| result[keys[ix]] = value }
70
+ [key, result]
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,80 @@
1
+ module I18n
2
+ module Backend
3
+ module Helpers
4
+ SEPARATOR_ESCAPE_CHAR = "\001"
5
+
6
+ # Return a new hash with all keys and nested keys converted to symbols.
7
+ def deep_symbolize_keys(hash)
8
+ hash.inject({}) { |result, (key, value)|
9
+ value = deep_symbolize_keys(value) if value.is_a?(Hash)
10
+ result[(key.to_sym rescue key) || key] = value
11
+ result
12
+ }
13
+ end
14
+
15
+ # Flatten keys for nested Hashes by chaining up keys using the separator
16
+ # >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
17
+ # => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
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
+
30
+ if value.is_a?(Hash)
31
+ result[curr_key] = value if subtree
32
+ wind_keys(value, separator, subtree, curr_key, result, orig_hash)
33
+ else
34
+ result[curr_key] = value
35
+ end
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
53
+ end
54
+ end
55
+
56
+ # Expand keys chained by the the given separator through nested Hashes
57
+ # >> { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }.unwind
58
+ # => { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}
59
+ def unwind_keys(hash, separator = ".")
60
+ result = {}
61
+ hash.each do |key, value|
62
+ keys = key.to_s.split(separator)
63
+ curr = result
64
+ curr = curr[keys.shift] ||= {} while keys.size > 1
65
+ curr[keys.shift] = value
66
+ end
67
+ result
68
+ end
69
+
70
+ # # Flatten the given array once
71
+ # def flatten_once(array)
72
+ # result = []
73
+ # for element in array # a little faster than each
74
+ # result.push(*element)
75
+ # end
76
+ # result
77
+ # end
78
+ end
79
+ end
80
+ end