theoooo-i18n 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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