polish 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +25 -0
  3. data/Rakefile +56 -0
  4. data/TODO +5 -0
  5. data/init.rb +1 -0
  6. data/lib/polish.rb +104 -0
  7. data/lib/polish/action_view_ext/helpers/date_helper.rb +94 -0
  8. data/lib/polish/backend/advanced.rb +105 -0
  9. data/lib/polish/locale/actionview.yml +81 -0
  10. data/lib/polish/locale/activerecord.yml +27 -0
  11. data/lib/polish/locale/activesupport.yml +11 -0
  12. data/lib/polish/locale/datetime.yml +25 -0
  13. data/lib/polish/locale/pluralize.rb +16 -0
  14. data/lib/vendor/i18n/CHANGELOG.textile +57 -0
  15. data/lib/vendor/i18n/MIT-LICENSE +20 -0
  16. data/lib/vendor/i18n/README.textile +42 -0
  17. data/lib/vendor/i18n/Rakefile +21 -0
  18. data/lib/vendor/i18n/VERSION +1 -0
  19. data/lib/vendor/i18n/i18n.gemspec +141 -0
  20. data/lib/vendor/i18n/lib/i18n.rb +270 -0
  21. data/lib/vendor/i18n/lib/i18n/backend/base.rb +251 -0
  22. data/lib/vendor/i18n/lib/i18n/backend/cache.rb +71 -0
  23. data/lib/vendor/i18n/lib/i18n/backend/chain.rb +64 -0
  24. data/lib/vendor/i18n/lib/i18n/backend/fallbacks.rb +53 -0
  25. data/lib/vendor/i18n/lib/i18n/backend/gettext.rb +65 -0
  26. data/lib/vendor/i18n/lib/i18n/backend/pluralization.rb +56 -0
  27. data/lib/vendor/i18n/lib/i18n/backend/simple.rb +23 -0
  28. data/lib/vendor/i18n/lib/i18n/exceptions.rb +61 -0
  29. data/lib/vendor/i18n/lib/i18n/gettext.rb +25 -0
  30. data/lib/vendor/i18n/lib/i18n/helpers/gettext.rb +35 -0
  31. data/lib/vendor/i18n/lib/i18n/locale/fallbacks.rb +100 -0
  32. data/lib/vendor/i18n/lib/i18n/locale/tag.rb +27 -0
  33. data/lib/vendor/i18n/lib/i18n/locale/tag/parents.rb +24 -0
  34. data/lib/vendor/i18n/lib/i18n/locale/tag/rfc4646.rb +78 -0
  35. data/lib/vendor/i18n/lib/i18n/locale/tag/simple.rb +44 -0
  36. data/lib/vendor/i18n/lib/i18n/string.rb +95 -0
  37. data/lib/vendor/i18n/test/all.rb +5 -0
  38. data/lib/vendor/i18n/test/api/basics.rb +15 -0
  39. data/lib/vendor/i18n/test/api/interpolation.rb +85 -0
  40. data/lib/vendor/i18n/test/api/lambda.rb +52 -0
  41. data/lib/vendor/i18n/test/api/link.rb +47 -0
  42. data/lib/vendor/i18n/test/api/localization/date.rb +65 -0
  43. data/lib/vendor/i18n/test/api/localization/date_time.rb +63 -0
  44. data/lib/vendor/i18n/test/api/localization/lambda.rb +26 -0
  45. data/lib/vendor/i18n/test/api/localization/time.rb +63 -0
  46. data/lib/vendor/i18n/test/api/pluralization.rb +37 -0
  47. data/lib/vendor/i18n/test/api/translation.rb +51 -0
  48. data/lib/vendor/i18n/test/backend/cache/cache_test.rb +57 -0
  49. data/lib/vendor/i18n/test/backend/chain/api_test.rb +80 -0
  50. data/lib/vendor/i18n/test/backend/chain/chain_test.rb +64 -0
  51. data/lib/vendor/i18n/test/backend/fallbacks/api_test.rb +79 -0
  52. data/lib/vendor/i18n/test/backend/fallbacks/fallbacks_test.rb +29 -0
  53. data/lib/vendor/i18n/test/backend/pluralization/api_test.rb +81 -0
  54. data/lib/vendor/i18n/test/backend/pluralization/pluralization_test.rb +39 -0
  55. data/lib/vendor/i18n/test/backend/simple/all.rb +5 -0
  56. data/lib/vendor/i18n/test/backend/simple/api_test.rb +90 -0
  57. data/lib/vendor/i18n/test/backend/simple/lookup_test.rb +24 -0
  58. data/lib/vendor/i18n/test/backend/simple/setup.rb +147 -0
  59. data/lib/vendor/i18n/test/backend/simple/translations_test.rb +89 -0
  60. data/lib/vendor/i18n/test/fixtures/locales/de.po +61 -0
  61. data/lib/vendor/i18n/test/fixtures/locales/en.rb +3 -0
  62. data/lib/vendor/i18n/test/fixtures/locales/en.yml +3 -0
  63. data/lib/vendor/i18n/test/fixtures/locales/plurals.rb +112 -0
  64. data/lib/vendor/i18n/test/gettext/api_test.rb +78 -0
  65. data/lib/vendor/i18n/test/gettext/backend_test.rb +35 -0
  66. data/lib/vendor/i18n/test/i18n_exceptions_test.rb +97 -0
  67. data/lib/vendor/i18n/test/i18n_load_path_test.rb +23 -0
  68. data/lib/vendor/i18n/test/i18n_test.rb +163 -0
  69. data/lib/vendor/i18n/test/locale/fallbacks_test.rb +128 -0
  70. data/lib/vendor/i18n/test/locale/tag/rfc4646_test.rb +147 -0
  71. data/lib/vendor/i18n/test/locale/tag/simple_test.rb +35 -0
  72. data/lib/vendor/i18n/test/string_test.rb +94 -0
  73. data/lib/vendor/i18n/test/test_helper.rb +71 -0
  74. data/lib/vendor/i18n/test/with_options.rb +34 -0
  75. data/lib/vendor/i18n/vendor/po_parser.rb +329 -0
  76. data/spec/fixtures/en.yml +4 -0
  77. data/spec/fixtures/pl.yml +4 -0
  78. data/spec/i18n/locale/datetime_spec.rb +91 -0
  79. data/spec/i18n/locale/pluralization_spec.rb +41 -0
  80. data/spec/locale_spec.rb +124 -0
  81. data/spec/polish_spec.rb +141 -0
  82. data/spec/spec_helper.rb +4 -0
  83. metadata +138 -0
@@ -0,0 +1,251 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ module I18n
6
+ module Backend
7
+ class Base
8
+ RESERVED_KEYS = [:scope, :default, :separator]
9
+ INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
10
+
11
+ # Accepts a list of paths to translation files. Loads translations from
12
+ # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
13
+ # for details.
14
+ def load_translations(*filenames)
15
+ filenames.each { |filename| load_file(filename) }
16
+ end
17
+
18
+ # Stores translations for the given locale in memory.
19
+ # This uses a deep merge for the translations hash, so existing
20
+ # translations will be overwritten by new ones only at the deepest
21
+ # level of the hash.
22
+ def store_translations(locale, data)
23
+ merge_translations(locale, data)
24
+ end
25
+
26
+ def translate(locale, key, options = {})
27
+ raise InvalidLocale.new(locale) if locale.nil?
28
+ return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
29
+
30
+ count, scope, default, separator = options.values_at(:count, *RESERVED_KEYS)
31
+ values = options.reject { |name, value| RESERVED_KEYS.include?(name) }
32
+
33
+ entry = lookup(locale, key, scope, separator)
34
+ entry = entry.nil? ? default(locale, key, default, options) : resolve(locale, key, entry, options)
35
+
36
+ raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
37
+ entry = pluralize(locale, entry, count)
38
+ entry = interpolate(locale, entry, values)
39
+ entry
40
+ end
41
+
42
+ # Acts the same as +strftime+, but uses a localized version of the
43
+ # format string. Takes a key from the date/time formats translations as
44
+ # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
45
+ def localize(locale, object, format = :default, options = {})
46
+ raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
47
+
48
+ if Symbol === format
49
+ key = format
50
+ type = object.respond_to?(:sec) ? 'time' : 'date'
51
+ format = lookup(locale, :"#{type}.formats.#{key}")
52
+ raise(MissingTranslationData.new(locale, key, options)) if format.nil?
53
+ end
54
+
55
+ format = resolve(locale, object, format, options)
56
+ format = format.to_s.gsub(/%[aAbBp]/) do |match|
57
+ case match
58
+ when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
59
+ when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
60
+ when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
61
+ when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
62
+ when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
63
+ end
64
+ end
65
+
66
+ object.strftime(format)
67
+ end
68
+
69
+ def initialized?
70
+ @initialized ||= false
71
+ end
72
+
73
+ # Returns an array of locales for which translations are available
74
+ # ignoring the reserved translation meta data key :i18n.
75
+ def available_locales
76
+ init_translations unless initialized?
77
+ translations.inject([]) do |locales, (locale, data)|
78
+ locales << locale unless (data.keys - [:i18n]).empty?
79
+ locales
80
+ end
81
+ end
82
+
83
+ def reload!
84
+ @initialized = false
85
+ @translations = nil
86
+ end
87
+
88
+ protected
89
+ def init_translations
90
+ load_translations(*I18n.load_path.flatten)
91
+ @initialized = true
92
+ end
93
+
94
+ def translations
95
+ @translations ||= {}
96
+ end
97
+
98
+ # Looks up a translation from the translations hash. Returns nil if
99
+ # eiher key is nil, or locale, scope or key do not exist as a key in the
100
+ # nested translations hash. Splits keys or scopes containing dots
101
+ # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
102
+ # <tt>%w(currency format)</tt>.
103
+ def lookup(locale, key, scope = [], separator = nil)
104
+ return unless key
105
+ init_translations unless initialized?
106
+ keys = I18n.send(:normalize_translation_keys, locale, key, scope, separator)
107
+ keys.inject(translations) do |result, key|
108
+ key = key.to_sym
109
+ if result.respond_to?(:has_key?) and result.has_key?(key)
110
+ result[key]
111
+ else
112
+ return nil
113
+ end
114
+ end
115
+ end
116
+
117
+ # Evaluates defaults.
118
+ # If given subject is an Array, it walks the array and returns the
119
+ # first translation that can be resolved. Otherwise it tries to resolve
120
+ # the translation directly.
121
+ def default(locale, object, subject, options = {})
122
+ options = options.dup.reject { |key, value| key == :default }
123
+ case subject
124
+ when Array
125
+ subject.each do |subject|
126
+ result = resolve(locale, object, subject, options) and return result
127
+ end and nil
128
+ else
129
+ resolve(locale, object, subject, options)
130
+ end
131
+ end
132
+
133
+ # Resolves a translation.
134
+ # If the given subject is a Symbol, it will be translated with the
135
+ # given options. If it is a Proc then it will be evaluated. All other
136
+ # subjects will be returned directly.
137
+ def resolve(locale, object, subject, options = {})
138
+ case subject
139
+ when Symbol
140
+ translate(locale, subject, options)
141
+ when Proc
142
+ resolve(locale, object, subject.call(object, options), options = {})
143
+ else
144
+ subject
145
+ end
146
+ rescue MissingTranslationData
147
+ nil
148
+ end
149
+
150
+ # Picks a translation from an array according to English pluralization
151
+ # rules. It will pick the first translation if count is not equal to 1
152
+ # and the second translation if it is equal to 1. Other backends can
153
+ # implement more flexible or complex pluralization rules.
154
+ def pluralize(locale, entry, count)
155
+ return entry unless entry.is_a?(Hash) and count
156
+
157
+ key = :zero if count == 0 && entry.has_key?(:zero)
158
+ key ||= count == 1 ? :one : :other
159
+ raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
160
+ entry[key]
161
+ end
162
+
163
+ # Interpolates values into a given string.
164
+ #
165
+ # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
166
+ # # => "file test.txt opened by {{user}}"
167
+ #
168
+ # Note that you have to double escape the <tt>\\</tt> when you want to escape
169
+ # the <tt>{{...}}</tt> key in a string (once for the string and once for the
170
+ # interpolation).
171
+ def interpolate(locale, string, values = {})
172
+ return string unless string.is_a?(String) && !values.empty?
173
+
174
+ s = string.gsub(INTERPOLATION_SYNTAX_PATTERN) do
175
+ escaped, key = $1, $2.to_sym
176
+ if escaped
177
+ "{{#{key}}}"
178
+ elsif RESERVED_KEYS.include?(key)
179
+ raise ReservedInterpolationKey.new(key, string)
180
+ else
181
+ "%{#{key}}"
182
+ end
183
+ end
184
+ values.each { |key, value| values[key] = value.call if interpolate_lambda?(value, s, key) }
185
+ s % values
186
+
187
+ rescue KeyError => e
188
+ raise MissingInterpolationArgument.new(values, string)
189
+ end
190
+
191
+ # returns true when the given value responds to :call and the key is
192
+ # an interpolation placeholder in the given string
193
+ def interpolate_lambda?(object, string, key)
194
+ object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
195
+ end
196
+
197
+ # Loads a single translations file by delegating to #load_rb or
198
+ # #load_yml depending on the file extension and directly merges the
199
+ # data to the existing translations. Raises I18n::UnknownFileType
200
+ # for all other file extensions.
201
+ def load_file(filename)
202
+ type = File.extname(filename).tr('.', '').downcase
203
+ raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
204
+ data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash
205
+ data.each { |locale, d| merge_translations(locale, d) }
206
+ end
207
+
208
+ # Loads a plain Ruby translations file. eval'ing the file must yield
209
+ # a Hash containing translation data with locales as toplevel keys.
210
+ def load_rb(filename)
211
+ eval(IO.read(filename), binding, filename)
212
+ end
213
+
214
+ # Loads a YAML translations file. The data must have locales as
215
+ # toplevel keys.
216
+ def load_yml(filename)
217
+ YAML::load(IO.read(filename))
218
+ end
219
+
220
+ # Deep merges the given translations hash with the existing translations
221
+ # for the given locale
222
+ def merge_translations(locale, data)
223
+ locale = locale.to_sym
224
+ translations[locale] ||= {}
225
+ data = deep_symbolize_keys(data)
226
+
227
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
228
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
229
+ translations[locale].merge!(data, &merger)
230
+ end
231
+
232
+ # Return a new hash with all keys and nested keys converted to symbols.
233
+ def deep_symbolize_keys(hash)
234
+ hash.inject({}) { |result, (key, value)|
235
+ value = deep_symbolize_keys(value) if value.is_a?(Hash)
236
+ result[(key.to_sym rescue key) || key] = value
237
+ result
238
+ }
239
+ end
240
+
241
+ # Flatten the given array once
242
+ def flatten_once(array)
243
+ result = []
244
+ for element in array # a little faster than each
245
+ result.push(*element)
246
+ end
247
+ result
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,71 @@
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
+ keys = ['i18n', I18n.cache_namespace, args.hash]
67
+ keys.compact.join('-')
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,64 @@
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 < Base
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)
31
+ backends.first.store_translations(locale, data)
32
+ end
33
+
34
+ def available_locales
35
+ backends.map { |backend| backend.available_locales }.flatten.uniq
36
+ end
37
+
38
+ def localize(locale, object, format = :default, options = {})
39
+ backends.each do |backend|
40
+ begin
41
+ result = backend.localize(locale, object, format, options) and return result
42
+ rescue MissingTranslationData
43
+ end
44
+ end or nil
45
+ end
46
+
47
+ protected
48
+
49
+ def lookup(locale, key, scope = [], separator = nil)
50
+ return unless key
51
+ result = {}
52
+ backends.each do |backend|
53
+ entry = backend.lookup(locale, key, scope, separator)
54
+ if entry.is_a?(Hash)
55
+ result.merge!(entry)
56
+ elsif !entry.nil?
57
+ return entry
58
+ end
59
+ end
60
+ result.empty? ? nil : result
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'i18n/locale/fallbacks'
4
+
5
+ # I18n locale fallbacks are useful when you want your application to use
6
+ # translations from other locales when translations for the current locale are
7
+ # missing. E.g. you might want to use :en translations when translations in
8
+ # your applications main locale :de are missing.
9
+ #
10
+ # To enable locale fallbacks you can simply include the Fallbacks module to
11
+ # the Simple backend - or whatever other backend you are using:
12
+ #
13
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
14
+ module I18n
15
+ @@fallbacks = nil
16
+
17
+ class << self
18
+ # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
19
+ def fallbacks
20
+ @@fallbacks ||= I18n::Locale::Fallbacks.new
21
+ end
22
+
23
+ # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
24
+ def fallbacks=(fallbacks)
25
+ @@fallbacks = fallbacks
26
+ end
27
+ end
28
+
29
+ module Backend
30
+ module Fallbacks
31
+ # Overwrites the Base backend translate method so that it will try each
32
+ # locale given by I18n.fallbacks for the given locale. E.g. for the
33
+ # locale :"de-DE" it might try the locales :"de-DE", :de and :en
34
+ # (depends on the fallbacks implementation) until it finds a result with
35
+ # the given options. If it does not find any result for any of the
36
+ # locales it will then raise a MissingTranslationData exception as
37
+ # usual.
38
+ #
39
+ # The default option takes precedence over fallback locales, i.e. it
40
+ # will first evaluate a given default option before falling back to
41
+ # another locale.
42
+ def translate(locale, key, options = {})
43
+ I18n.fallbacks[locale].each do |fallback|
44
+ begin
45
+ result = super(fallback, key, options) and return result
46
+ rescue I18n::MissingTranslationData
47
+ end
48
+ end
49
+ raise(I18n::MissingTranslationData.new(locale, key, options))
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,65 @@
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
+ key, value = normalize_pluralization(locale, key, value) if key.index("\000")
45
+ result[key] = value
46
+ result
47
+ end
48
+ end
49
+
50
+ def normalize_pluralization(locale, key, value)
51
+ # FIXME po_parser includes \000 chars that can not be turned into Symbols
52
+ key = key.dup.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR)
53
+
54
+ keys = I18n::Gettext.plural_keys(locale)
55
+ values = value.split("\000")
56
+ raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
57
+
58
+ result = {}
59
+ values.each_with_index { |value, ix| result[keys[ix]] = value }
60
+ [key, result]
61
+ end
62
+
63
+ end
64
+ end
65
+ end