theoooo-i18n 0.2.0 → 0.2.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.
data/Rakefile CHANGED
@@ -15,6 +15,7 @@ begin
15
15
  s.description = "Add Internationalization support to your Ruby application."
16
16
  s.authors = ['Sven Fuchs', 'Joshua Harvey', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore']
17
17
  s.files = FileList["[A-Z]*", "{lib,test}/**/*"]
18
+ s.add_dependency('ruby2ruby', '1.2.2')
18
19
  end
19
20
  rescue LoadError
20
21
  puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 2
3
+ :patch: 1
4
+ :major: 0
@@ -0,0 +1,237 @@
1
+ require 'yaml'
2
+
3
+ module I18n
4
+ module Backend
5
+ class Base
6
+ RESERVED_KEYS = [:scope, :default, :separator]
7
+ INTERPOLATION_SYNTAX_PATTERN = /(\\\\)?\{\{([^\}]+)\}\}/
8
+
9
+ # Accepts a list of paths to translation files. Loads translations from
10
+ # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
11
+ # for details.
12
+ def load_translations(*filenames)
13
+ filenames.each { |filename| load_file(filename) }
14
+ end
15
+
16
+ # Stores translations for the given locale in memory.
17
+ # This uses a deep merge for the translations hash, so existing
18
+ # translations will be overwritten by new ones only at the deepest
19
+ # level of the hash.
20
+ def store_translations(locale, data)
21
+ merge_translations(locale, data)
22
+ end
23
+
24
+ def translate(locale, key, options = {})
25
+ raise InvalidLocale.new(locale) if locale.nil?
26
+ return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
27
+
28
+ count, scope, default, separator = options.values_at(:count, *RESERVED_KEYS)
29
+ values = options.reject { |name, value| RESERVED_KEYS.include?(name) }
30
+
31
+ entry = lookup(locale, key, scope, separator)
32
+ entry = entry.nil? ? default(locale, key, default, options) : resolve(locale, key, entry, options)
33
+
34
+ raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
35
+ entry = pluralize(locale, entry, count)
36
+ entry = interpolate(locale, entry, values)
37
+ entry
38
+ end
39
+
40
+ # Acts the same as +strftime+, but returns a localized version of the
41
+ # formatted date string. Takes a key from the date/time formats
42
+ # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
43
+ def localize(locale, object, format = :default, options={})
44
+ raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
45
+
46
+ if Symbol === format
47
+ type = object.respond_to?(:sec) ? 'time' : 'date'
48
+ format = lookup(locale, :"#{type}.formats.#{format}")
49
+ end
50
+
51
+ format = resolve(locale, object, format, options.merge(:raise => true))
52
+
53
+ # TODO check which format strings are present, then bulk translate them, then replace them
54
+ format.gsub!(/%a/, translate(locale, :"date.abbr_day_names", :format => format)[object.wday]) if format.include?('%a')
55
+ format.gsub!(/%A/, translate(locale, :"date.day_names", :format => format)[object.wday]) if format.include?('%A')
56
+ format.gsub!(/%b/, translate(locale, :"date.abbr_month_names", :format => format)[object.mon]) if format.include?('%b')
57
+ format.gsub!(/%B/, translate(locale, :"date.month_names", :format => format)[object.mon]) if format.include?('%B')
58
+ format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}", :format => format)) if format.include?('%p') && object.respond_to?(:hour)
59
+
60
+ object.strftime(format)
61
+ end
62
+
63
+ def initialized?
64
+ @initialized ||= false
65
+ end
66
+
67
+ # Returns an array of locales for which translations are available
68
+ # ignoring the reserved translation meta data key :i18n.
69
+ def available_locales
70
+ init_translations unless initialized?
71
+ translations.inject([]) do |locales, (locale, data)|
72
+ locales << locale unless data.keys.tap { |keys| keys.delete(:i18n) }.empty?
73
+ locales
74
+ end
75
+ end
76
+
77
+ def reload!
78
+ @initialized = false
79
+ @translations = nil
80
+ end
81
+
82
+ protected
83
+ def init_translations
84
+ load_translations(*I18n.load_path.flatten)
85
+ @initialized = true
86
+ end
87
+
88
+ def translations
89
+ @translations ||= {}
90
+ end
91
+
92
+ # Looks up a translation from the translations hash. Returns nil if
93
+ # eiher key is nil, or locale, scope or key do not exist as a key in the
94
+ # nested translations hash. Splits keys or scopes containing dots
95
+ # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
96
+ # <tt>%w(currency format)</tt>.
97
+ def lookup(locale, key, scope = [], separator = nil)
98
+ return unless key
99
+ init_translations unless initialized?
100
+ keys = I18n.send(:normalize_translation_keys, locale, key, scope, separator)
101
+ keys.inject(translations) do |result, k|
102
+ if (x = result[k.to_sym]).nil?
103
+ return nil
104
+ else
105
+ x
106
+ end
107
+ end
108
+ end
109
+
110
+ # Evaluates defaults.
111
+ # If given subject is an Array, it walks the array and returns the
112
+ # first translation that can be resolved. Otherwise it tries to resolve
113
+ # the translation directly.
114
+ def default(locale, object, subject, options = {})
115
+ options = options.dup.reject { |key, value| key == :default }
116
+ case subject
117
+ when Array
118
+ subject.each do |subject|
119
+ result = resolve(locale, object, subject, options) and return result
120
+ end and nil
121
+ else
122
+ resolve(locale, object, subject, options)
123
+ end
124
+ end
125
+
126
+ # Resolves a translation.
127
+ # If the given subject is a Symbol, it will be translated with the
128
+ # given options. If it is a Proc then it will be evaluated. All other
129
+ # subjects will be returned directly.
130
+ def resolve(locale, object, subject, options = {})
131
+ case subject
132
+ when Symbol
133
+ translate(locale, subject, options)
134
+ when Proc
135
+ resolve(locale, object, subject.call(object, options), options = {})
136
+ else
137
+ subject
138
+ end
139
+ rescue MissingTranslationData
140
+ nil
141
+ end
142
+
143
+ # Picks a translation from an array according to English pluralization
144
+ # rules. It will pick the first translation if count is not equal to 1
145
+ # and the second translation if it is equal to 1. Other backends can
146
+ # implement more flexible or complex pluralization rules.
147
+ def pluralize(locale, entry, count)
148
+ return entry unless entry.is_a?(Hash) and count
149
+
150
+ key = :zero if count == 0 && entry.has_key?(:zero)
151
+ key ||= count == 1 ? :one : :other
152
+ raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
153
+ entry[key]
154
+ end
155
+
156
+ # Interpolates values into a given string.
157
+ #
158
+ # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
159
+ # # => "file test.txt opened by {{user}}"
160
+ #
161
+ # Note that you have to double escape the <tt>\\</tt> when you want to escape
162
+ # the <tt>{{...}}</tt> key in a string (once for the string and once for the
163
+ # interpolation).
164
+ def interpolate(locale, string, values = {})
165
+ return string unless string.is_a?(String) && !values.empty?
166
+
167
+ string.gsub(INTERPOLATION_SYNTAX_PATTERN) do
168
+ escaped, key = $1, $2.to_sym
169
+
170
+ if escaped
171
+ key
172
+ elsif RESERVED_KEYS.include?(key)
173
+ raise ReservedInterpolationKey.new(key, string)
174
+ else
175
+ "%{#{key}}"
176
+ end
177
+ end % values
178
+
179
+ rescue KeyError => e
180
+ raise MissingInterpolationArgument.new(values, string)
181
+ end
182
+
183
+ # Loads a single translations file by delegating to #load_rb or
184
+ # #load_yml depending on the file extension and directly merges the
185
+ # data to the existing translations. Raises I18n::UnknownFileType
186
+ # for all other file extensions.
187
+ def load_file(filename)
188
+ type = File.extname(filename).tr('.', '').downcase
189
+ raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
190
+ data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash
191
+ data.each { |locale, d| merge_translations(locale, d) }
192
+ end
193
+
194
+ # Loads a plain Ruby translations file. eval'ing the file must yield
195
+ # a Hash containing translation data with locales as toplevel keys.
196
+ def load_rb(filename)
197
+ eval(IO.read(filename), binding, filename)
198
+ end
199
+
200
+ # Loads a YAML translations file. The data must have locales as
201
+ # toplevel keys.
202
+ def load_yml(filename)
203
+ YAML::load(IO.read(filename))
204
+ end
205
+
206
+ # Deep merges the given translations hash with the existing translations
207
+ # for the given locale
208
+ def merge_translations(locale, data)
209
+ locale = locale.to_sym
210
+ translations[locale] ||= {}
211
+ data = deep_symbolize_keys(data)
212
+
213
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
214
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
215
+ translations[locale].merge!(data, &merger)
216
+ end
217
+
218
+ # Return a new hash with all keys and nested keys converted to symbols.
219
+ def deep_symbolize_keys(hash)
220
+ hash.inject({}) { |result, (key, value)|
221
+ value = deep_symbolize_keys(value) if value.is_a?(Hash)
222
+ result[(key.to_sym rescue key) || key] = value
223
+ result
224
+ }
225
+ end
226
+
227
+ # Flatten the given array once
228
+ def flatten_once(array)
229
+ result = []
230
+ for element in array # a little faster than each
231
+ result.push(*element)
232
+ end
233
+ result
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,69 @@
1
+ # This module allows you to easily cache all responses from the backend - thus
2
+ # speeding up the I18n aspects of your application quite a bit.
3
+ #
4
+ # To enable caching you can simply include the Cache module to the Simple
5
+ # backend - or whatever other backend you are using:
6
+ #
7
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
8
+ #
9
+ # You will also need to set a cache store implementation that you want to use:
10
+ #
11
+ # I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
12
+ #
13
+ # You can use any cache implementation you want that provides the same API as
14
+ # ActiveSupport::Cache (only the methods #fetch and #write are being used).
15
+ #
16
+ # The cache_key implementation assumes that you only pass values to
17
+ # I18n.translate that return a valid key from #hash (see
18
+ # http://www.ruby-doc.org/core/classes/Object.html#M000337).
19
+ module I18n
20
+ class << self
21
+ @@cache_store = nil
22
+ @@cache_namespace = nil
23
+
24
+ def cache_store
25
+ @@cache_store
26
+ end
27
+
28
+ def cache_store=(store)
29
+ @@cache_store = store
30
+ end
31
+
32
+ def cache_namespace
33
+ @@cache_namespace
34
+ end
35
+
36
+ def cache_namespace=(namespace)
37
+ @@cache_namespace = namespace
38
+ end
39
+
40
+ def perform_caching?
41
+ !cache_store.nil?
42
+ end
43
+ end
44
+
45
+ module Backend
46
+ module Cache
47
+ def translate(*args)
48
+ I18n.perform_caching? ? fetch(*args) { super } : super
49
+ end
50
+
51
+ protected
52
+
53
+ def fetch(*args, &block)
54
+ result = I18n.cache_store.fetch(cache_key(*args), &block)
55
+ raise result if result.is_a?(Exception)
56
+ result
57
+ rescue MissingTranslationData => exception
58
+ I18n.cache_store.write(cache_key(*args), exception)
59
+ raise exception
60
+ end
61
+
62
+ def cache_key(*args)
63
+ # this assumes that only simple, native Ruby values are passed to I18n.translate
64
+ keys = ['i18n', I18n.cache_namespace, args.hash]
65
+ keys.compact.join('-')
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ module I18n
2
+ module Backend
3
+ # Backend that chains multiple other backends and checks each of them when
4
+ # a translation needs to be looked up. This is useful when you want to use
5
+ # standard translations with a Simple backend but store custom application
6
+ # translations in a database or other backends.
7
+ #
8
+ # To use the Chain backend instantiate it and set it to the I18n module.
9
+ # You can add chained backends through the initializer or backends
10
+ # accessor:
11
+ #
12
+ # # preserves the existing Simple backend set to I18n.backend
13
+ # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
14
+ #
15
+ # The implementation assumes that all backends added to the Chain implement
16
+ # a lookup method with the same API as Simple backend does.
17
+ class Chain < Base
18
+ attr_accessor :backends
19
+
20
+ def initialize(*backends)
21
+ self.backends = backends
22
+ end
23
+
24
+ def reload!
25
+ backends.each { |backend| backend.reload! }
26
+ end
27
+
28
+ def store_translations(locale, data)
29
+ backends.first.store_translations(locale, data)
30
+ end
31
+
32
+ def available_locales
33
+ backends.map { |backend| backend.available_locales }.flatten.uniq
34
+ end
35
+
36
+ def localize(locale, object, format = :default)
37
+ backends.each do |backend|
38
+ result = backend.localize(locale, object, format) and return result
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def lookup(locale, key, scope = [], separator = nil)
45
+ return unless key
46
+ result = {}
47
+ backends.each do |backend|
48
+ entry = backend.lookup(locale, key, scope, separator)
49
+ if entry.is_a?(Hash)
50
+ result.merge!(entry)
51
+ elsif !entry.nil?
52
+ return entry
53
+ end
54
+ end
55
+ result.empty? ? nil : result
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,51 @@
1
+ require 'i18n/locale/fallbacks'
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) and return result
44
+ rescue I18n::MissingTranslationData
45
+ end
46
+ end
47
+ raise(I18n::MissingTranslationData.new(locale, key, options))
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ require 'i18n/gettext'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../../vendor/po_parser.rb')
3
+
4
+ # Experimental support for using Gettext po files to store translations.
5
+ #
6
+ # To use this you can simply include the module to the Simple backend - or
7
+ # whatever other backend you are using.
8
+ #
9
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Gettext)
10
+ #
11
+ # Now you should be able to include your Gettext translation (*.po) files to
12
+ # the I18n.load_path so they're loaded to the backend and you can use them as
13
+ # usual:
14
+ #
15
+ # I18n.load_path += Dir["path/to/locales/*.po"]
16
+ #
17
+ # Following the Gettext convention this implementation expects that your
18
+ # translation files are named by their locales. E.g. the file en.po would
19
+ # contain the translations for the English locale.
20
+ module I18n
21
+ module Backend
22
+ module Gettext
23
+ class PoData < Hash
24
+ def set_comment(msgid_or_sym, comment)
25
+ # ignore
26
+ end
27
+ end
28
+
29
+ protected
30
+ def load_po(filename)
31
+ locale = ::File.basename(filename, '.po').to_sym
32
+ data = normalize(locale, parse(filename))
33
+ { locale => data }
34
+ end
35
+
36
+ def parse(filename)
37
+ GetText::PoParser.new.parse(::File.read(filename), PoData.new)
38
+ end
39
+
40
+ def normalize(locale, data)
41
+ data.inject({}) do |result, (key, value)|
42
+ key, value = normalize_pluralization(locale, key, value) if key.index("\000")
43
+ result[key] = value
44
+ result
45
+ end
46
+ end
47
+
48
+ def normalize_pluralization(locale, key, value)
49
+ # FIXME po_parser includes \000 chars that can not be turned into Symbols
50
+ key = key.dup.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR)
51
+
52
+ keys = I18n::Gettext.plural_keys(locale)
53
+ values = value.split("\000")
54
+ raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
55
+
56
+ result = {}
57
+ values.each_with_index { |value, ix| result[keys[ix]] = value }
58
+ [key, result]
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,54 @@
1
+ # I18n locale fallbacks are useful when you want your application to use
2
+ # translations from other locales when translations for the current locale are
3
+ # missing. E.g. you might want to use :en translations when translations in
4
+ # your applications main locale :de are missing.
5
+ #
6
+ # To enable locale specific pluralizations you can simply include the
7
+ # Pluralization module to the Simple backend - or whatever other backend you
8
+ # are using.
9
+ #
10
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
11
+ #
12
+ # You also need to make sure to provide pluralization algorithms to the
13
+ # backend, i.e. include them to your I18n.load_path accordingly.
14
+ module I18n
15
+ module Backend
16
+ module Pluralization
17
+ # Overwrites the Base backend translate method so that it will check the
18
+ # translation meta data space (:i18n) for locale specific pluralizers
19
+ # and use them to pluralize the given entry.
20
+ #
21
+ # Pluralizers are expected to respond to #call(entry, count) and return
22
+ # a pluralization key. Valid keys depend on the translation data hash
23
+ # (entry) but it is generally recommended to follow CLDR's style, i.e.
24
+ # return one of the keys :zero, :one, :few, :many, :other.
25
+ #
26
+ # The :zero key is always picked directly when count equals 0 AND the
27
+ # translation data has the key :zero. This way translators are free to
28
+ # either pick a special :zero translation even for languages where the
29
+ # pluralizer does not return a :zero key.
30
+ def pluralize(locale, entry, count)
31
+ return entry unless entry.is_a?(Hash) and count
32
+
33
+ pluralizer = pluralizer(locale)
34
+ if pluralizer.respond_to?(:call)
35
+ key = count == 0 && entry.has_key?(:zero) ? :zero : pluralizer.call(count)
36
+ raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
37
+ entry[key]
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def pluralizers
46
+ @pluralizers ||= {}
47
+ end
48
+
49
+ def pluralizer(locale)
50
+ pluralizers[locale] ||= lookup(locale, :"i18n.pluralize")
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ module I18n
2
+ module Gettext
3
+ PLURAL_SEPARATOR = "\001"
4
+ CONTEXT_SEPARATOR = "\004"
5
+
6
+ @@plural_keys = { :en => [:one, :other] }
7
+
8
+ class << self
9
+ # returns an array of plural keys for the given locale so that we can
10
+ # convert from gettext's integer-index based style
11
+ # TODO move this information to the pluralization module
12
+ def plural_keys(locale)
13
+ @@plural_keys[locale] || @@plural_keys[:en]
14
+ end
15
+
16
+ def extract_scope(msgid, separator = nil)
17
+ scope = msgid.to_s.split(separator || '|')
18
+ msgid = scope.pop
19
+ [scope, msgid]
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/i18n/hash.rb ADDED
@@ -0,0 +1,37 @@
1
+ class Hash
2
+
3
+ # >> {"a"=>{"b"=>{"c"=>"foo", "d"=>"bar"}, "c"=>"j"}, "q"=>"asd"}.unwind
4
+ # => {"a.c"=>"j", "a.b.d"=>"bar", "q"=>"asd", "a.b.c"=>"foo"}
5
+ def unwind(separator = ".", key = nil, start = {})
6
+ self.inject(start){|hash,k|
7
+ expanded_key = [key, k[0]].compact.join( separator )
8
+ if k[1].is_a? Hash
9
+ k[1].unwind(separator, expanded_key, hash)
10
+ else
11
+ hash[ expanded_key ] = k[1]
12
+ end
13
+ hash
14
+ }
15
+ end
16
+
17
+ # >> {"a.b.c" => "foo", "a.b.d" => "bar", "a.c" => "j", "q" => "asd"}.wind
18
+ # => {"a"=>{"b"=>{"c"=>"foo", "d"=>"bar"}, "c"=>"j"}, "q"=>"asd"}
19
+ def wind(separator = ".", key = nil, start = {})
20
+ wound = Hash.new
21
+ self.each {|key, value|
22
+ keys = key.split( separator )
23
+ index = 0
24
+ keys.inject(wound){|h,v|
25
+ index += 1
26
+ if index >= keys.size
27
+ h[v.to_sym] = value
28
+ break
29
+ else
30
+ h[v.to_sym] ||= {}
31
+ end
32
+ }
33
+ }
34
+ wound
35
+ end
36
+
37
+ end
@@ -0,0 +1,33 @@
1
+ module I18n
2
+ module Helpers
3
+ # Implements classical Gettext style accessors. To use this include the
4
+ # module to the global namespace or wherever you want to use it.
5
+ #
6
+ # include I18n::Helpers::Gettext
7
+ module Gettext
8
+ def _(msgid, options = {})
9
+ I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options))
10
+ end
11
+
12
+ def sgettext(msgid, separator = '|')
13
+ scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
14
+ I18n.t(msgid, :scope => scope, :default => msgid)
15
+ end
16
+
17
+ def pgettext(msgctxt, msgid, separator = I18n::Gettext::CONTEXT_SEPARATOR)
18
+ sgettext([msgctxt, msgid].join(separator), separator)
19
+ end
20
+
21
+ def ngettext(msgid, msgid_plural, n = 1)
22
+ nsgettext(msgid, msgid_plural, n, nil)
23
+ end
24
+
25
+ def nsgettext(msgid, msgid_plural, n = 1, separator = nil)
26
+ scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
27
+ default = { :one => msgid, :other => msgid_plural }
28
+ msgid = [msgid, I18n::Gettext::PLURAL_SEPARATOR, msgid_plural].join
29
+ I18n.t(msgid, :default => default, :count => n, :scope => scope)
30
+ end
31
+ end
32
+ end
33
+ end