thedarkone-i18n 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 The Ruby I18n team
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,20 @@
1
+ h1. Ruby I18n gem
2
+
3
+ I18n and localization solution for Ruby.
4
+
5
+ For information please refer to http://rails-i18n.org
6
+
7
+ h2. Authors
8
+
9
+ * "Matt Aimonetti":http://railsontherun.com
10
+ * "Sven Fuchs":http://www.artweb-design.de
11
+ * "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey
12
+ * "Saimon Moore":http://saimonmoore.net
13
+ * "Stephan Soller":http://www.arkanis-development.de
14
+
15
+ h2. License
16
+
17
+ MIT License. See the included MIT-LICENCE file.
18
+
19
+
20
+
data/i18n.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "i18n"
3
+ s.version = "0.1.4"
4
+ s.date = "2009-01-09"
5
+ s.summary = "Internationalization support for Ruby"
6
+ s.email = "rails-i18n@googlegroups.com"
7
+ s.homepage = "http://rails-i18n.org"
8
+ s.description = "Add Internationalization support to your Ruby application."
9
+ s.has_rdoc = false
10
+ s.authors = ['Sven Fuchs', 'Joshua Harvey', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore', 'thedarkone2@gmail.com']
11
+ s.files = [
12
+ 'i18n.gemspec',
13
+ 'lib/i18n/backend/simple.rb',
14
+ 'lib/i18n/backend/fast.rb',
15
+ 'lib/i18n/backend/fast/pluralization_compiler.rb',
16
+ 'lib/i18n/exceptions.rb',
17
+ 'lib/i18n.rb',
18
+ 'MIT-LICENSE',
19
+ 'README.textile'
20
+ ]
21
+ s.test_files = [
22
+ 'test/all.rb',
23
+ 'test/i18n_exceptions_test.rb',
24
+ 'test/i18n_test.rb',
25
+ 'test/locale/en.rb',
26
+ 'test/locale/en.yml',
27
+ 'test/backend_test.rb',
28
+ 'test/fast_backend_test.rb',
29
+ 'test/pluralization_compiler_test.rb'
30
+ ]
31
+ end
@@ -0,0 +1,50 @@
1
+ module I18n
2
+ module Backend
3
+ class Fast < Simple
4
+ module PluralizationCompiler
5
+ extend self
6
+
7
+ def compile_if_an_interpolation(string)
8
+ if interpolated_str?(string)
9
+ string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
10
+ def i18n_interpolate(values = {})
11
+ "#{compiled_interpolation_body(string)}"
12
+ end
13
+ RUBY_EVAL
14
+ end
15
+
16
+ string
17
+ end
18
+
19
+ def interpolated_str?(str)
20
+ str.kind_of?(String) && str.scan(Simple::MATCH).find{|escape_chars, interpolation| !escape_chars && interpolation}
21
+ end
22
+
23
+ protected
24
+ def compiled_interpolation_body(str)
25
+ str.gsub(Simple::MATCH) do
26
+ escaped, pattern, key = $1, $2, $3.to_sym
27
+
28
+ if escaped
29
+ pattern
30
+ else
31
+ eskey = escape_key_sym(key)
32
+ if Simple::INTERPOLATION_RESERVED_KEYS.include?(key)
33
+ "\#{raise(ReservedInterpolationKey.new(#{eskey}, self))}"
34
+ else
35
+ "\#{values[#{eskey}] || (values.has_key?(#{eskey}) && values[#{eskey}].to_s) || raise(MissingInterpolationArgument.new(#{eskey}, self))}"
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ def escape_key_sym(key)
43
+ # rely on Ruby to do all the hard work :)
44
+ key.to_sym.inspect
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,67 @@
1
+ module I18n
2
+ module Backend
3
+ class Fast < Simple
4
+
5
+ # append any your custom pluralization keys to the constant
6
+ PLURALIZATION_KEYS = [:zero, :one, :other]
7
+
8
+ def reset_flattened_translations!
9
+ @flattened_translations = nil
10
+ end
11
+
12
+ def flattened_translations
13
+ @flattened_translations ||= flatten_translations(translations)
14
+ end
15
+
16
+ def merge_translations(locale, data)
17
+ super
18
+ reset_flattened_translations!
19
+ end
20
+
21
+ def init_translations
22
+ super
23
+ reset_flattened_translations!
24
+ end
25
+
26
+ protected
27
+ # {:a=>'a', :b=>{:c=>'c', :d=>'d', :f=>{:x=>'x'}}} => {:"b.c"=>"c", :"b.f.x"=>"x", :"b.d"=>"d", :a=>"a"}
28
+ def flatten_hash(h, nested_stack = [], flattened_h = {})
29
+ h.each_pair do |k, v|
30
+ new_nested_stack = nested_stack + [k]
31
+
32
+ if v.kind_of?(Hash)
33
+ # short circuit pluralization hashes
34
+ flattened_h[nested_stack_to_flat_key(new_nested_stack)] = v if (v.keys & PLURALIZATION_KEYS).any?
35
+
36
+ flatten_hash(v, new_nested_stack, flattened_h)
37
+ else
38
+ flattened_h[nested_stack_to_flat_key(new_nested_stack)] = PluralizationCompiler.compile_if_an_interpolation(v)
39
+ end
40
+ end
41
+
42
+ flattened_h
43
+ end
44
+
45
+ def nested_stack_to_flat_key(nested_stack)
46
+ nested_stack.join('.').to_sym
47
+ end
48
+
49
+ def flatten_translations(translations)
50
+ # don't flatten locale roots
51
+ translations.inject({}) do |flattened_h, (locale_name, locale_translations)|
52
+ flattened_h[locale_name] = flatten_hash(locale_translations)
53
+ flattened_h
54
+ end
55
+ end
56
+
57
+ def interpolate(string, values)
58
+ string.respond_to?(:i18n_interpolate) ? string.i18n_interpolate(values) : string
59
+ end
60
+
61
+ def lookup(locale, key, scope = nil)
62
+ init_translations unless @initialized
63
+ flattened_translations[locale.to_sym][(scope ? "#{Array(scope).join('.')}.#{key}" : key).to_sym] rescue nil
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,246 @@
1
+ require 'yaml'
2
+
3
+ module I18n
4
+ module Backend
5
+ class Simple
6
+ INTERPOLATION_RESERVED_KEYS = [:scope, :default]
7
+ MATCH = /(\\\\)?(\{\{([^\}]+)\}\})/
8
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
9
+ DEEP_MERGE_PROC = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &DEEP_MERGE_PROC) : v2 }
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, opts = nil)
27
+ raise InvalidLocale.new(locale) unless locale
28
+ return key.map { |k| translate(locale, k, opts) } if key.is_a? Array
29
+
30
+ if opts
31
+ count, scope = opts.values_at(:count, :scope)
32
+
33
+ if entry = lookup(locale, key, scope) || ((default = opts.delete(:default)) && default(locale, default, opts))
34
+ entry = pluralize(locale, entry, count) if count
35
+ entry = interpolate(entry, opts)
36
+ entry
37
+ end
38
+ else
39
+ lookup(locale, key)
40
+ end || raise(I18n::MissingTranslationData.new(locale, key, opts))
41
+ end
42
+
43
+ # Acts the same as +strftime+, but returns a localized version of the
44
+ # formatted date string. Takes a key from the date/time formats
45
+ # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
46
+ def localize(locale, object, format = :default)
47
+ raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
48
+
49
+ format = decide_on_localization_format(locale, object, format)
50
+
51
+ # TODO only translate these if the format string is actually present
52
+ # TODO check which format strings are present, then bulk translate then, then replace them
53
+ format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
54
+ format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday])
55
+ format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon])
56
+ format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon])
57
+ format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour
58
+ object.strftime(format)
59
+ end
60
+
61
+ def initialized?
62
+ @initialized ||= false
63
+ end
64
+
65
+ # Returns an array of locales for which translations are available
66
+ def available_locales
67
+ init_translations unless initialized?
68
+ translations.keys
69
+ end
70
+
71
+ def reload!
72
+ flush_translations if stale?
73
+ end
74
+
75
+ def stale?
76
+ translation_path_removed? || translation_file_updated_or_added?
77
+ end
78
+
79
+ protected
80
+ def init_translations
81
+ load_translations(*load_paths)
82
+ @initialized = true
83
+ end
84
+
85
+ def flush_translations
86
+ @initialized = false
87
+ @translations = nil
88
+ @file_mtimes = {}
89
+ end
90
+
91
+ def load_paths
92
+ I18n.load_path.flatten
93
+ end
94
+
95
+ def translations
96
+ @translations ||= {}
97
+ end
98
+
99
+ def decide_on_localization_format(locale, object, format)
100
+ type = object.respond_to?(:sec) ? 'time' : 'date'
101
+ # TODO raise exception unless format found?
102
+ lookup(locale, :"#{type}.formats.#{format}") || format.to_s.dup
103
+ end
104
+
105
+ # Looks up a translation from the translations hash. Returns nil if
106
+ # eiher key is nil, or locale, scope or key do not exist as a key in the
107
+ # nested translations hash. Splits keys or scopes containing dots
108
+ # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
109
+ # <tt>%w(currency format)</tt>.
110
+ def lookup(locale, key, scope = [])
111
+ return unless key
112
+ init_translations unless initialized?
113
+ keys = I18n.send(:normalize_translation_keys, locale, key, scope)
114
+ keys.inject(translations) do |result, k|
115
+ if (x = result[k.to_sym]).nil?
116
+ return nil
117
+ else
118
+ x
119
+ end
120
+ end
121
+ end
122
+ alias lookup_with_count lookup
123
+
124
+ # Evaluates a default translation.
125
+ # If the given default is a String it is used literally. If it is a Symbol
126
+ # it will be translated with the given options. If it is an Array the first
127
+ # translation yielded will be returned.
128
+ #
129
+ # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if
130
+ # <tt>translate(locale, :foo)</tt> does not yield a result.
131
+ def default(locale, default, options = {})
132
+ case default
133
+ when String then default
134
+ when Symbol then translate locale, default, options
135
+ when Array then default.each do |obj|
136
+ result = default(locale, obj, options.dup) and return result
137
+ end and nil
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
+ # raise InvalidPluralizationData.new(entry, count) unless entry.is_a?(Hash)
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(string, values = {})
165
+ return string unless string.is_a?(String)
166
+
167
+ string.gsub(MATCH) do
168
+ escaped, pattern, key = $1, $2, $3.to_sym
169
+
170
+ if escaped
171
+ pattern
172
+ elsif INTERPOLATION_RESERVED_KEYS.include?(key)
173
+ raise ReservedInterpolationKey.new(key, string)
174
+ elsif !values.include?(key)
175
+ raise MissingInterpolationArgument.new(key, string)
176
+ else
177
+ values[key].to_s
178
+ end
179
+ end
180
+ end
181
+
182
+ # Loads a single translations file by delegating to #load_rb or
183
+ # #load_yml depending on the file extension and directly merges the
184
+ # data to the existing translations. Raises I18n::UnknownFileType
185
+ # for all other file extensions.
186
+ def load_file(filename)
187
+ type = File.extname(filename).tr('.', '').downcase
188
+ raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
189
+ record_mtime_of(filename)
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
+ def record_mtime_of(filename)
195
+ file_mtimes[filename] = File.mtime(filename)
196
+ end
197
+
198
+ def stale_translation_file?(filename)
199
+ (mtime = file_mtimes[filename]).nil? || !File.file?(filename) || mtime < File.mtime(filename)
200
+ end
201
+
202
+ def file_mtimes
203
+ @file_mtimes ||= {}
204
+ end
205
+
206
+ def translation_path_removed?
207
+ (file_mtimes.keys - load_paths).any?
208
+ end
209
+
210
+ def translation_file_updated_or_added?
211
+ load_paths.any? {|path| stale_translation_file?(path)}
212
+ end
213
+
214
+ # Loads a plain Ruby translations file. eval'ing the file must yield
215
+ # a Hash containing translation data with locales as toplevel keys.
216
+ def load_rb(filename)
217
+ eval(IO.read(filename), binding, filename)
218
+ end
219
+
220
+ # Loads a YAML translations file. The data must have locales as
221
+ # toplevel keys.
222
+ def load_yml(filename)
223
+ YAML::load(IO.read(filename))
224
+ end
225
+
226
+ # Deep merges the given translations hash with the existing translations
227
+ # for the given locale
228
+ def merge_translations(locale, data)
229
+ locale = locale.to_sym
230
+ translations[locale] ||= {}
231
+ data = deep_symbolize_keys(data)
232
+
233
+ translations[locale].merge!(data, &DEEP_MERGE_PROC)
234
+ end
235
+
236
+ # Return a new hash with all keys and nested keys converted to symbols.
237
+ def deep_symbolize_keys(hash)
238
+ hash.inject({}) do |result, (key, value)|
239
+ value = deep_symbolize_keys(value) if value.is_a? Hash
240
+ result[(key.to_sym rescue key) || key] = value
241
+ result
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,53 @@
1
+ module I18n
2
+ class ArgumentError < ::ArgumentError; end
3
+
4
+ class InvalidLocale < ArgumentError
5
+ attr_reader :locale
6
+ def initialize(locale)
7
+ @locale = locale
8
+ super "#{locale.inspect} is not a valid locale"
9
+ end
10
+ end
11
+
12
+ class MissingTranslationData < ArgumentError
13
+ attr_reader :locale, :key, :options
14
+ def initialize(locale, key, options)
15
+ @key, @locale, @options = key, locale, options
16
+ keys = I18n.send(:normalize_translation_keys, locale, key, options && options[:scope])
17
+ keys << 'no key' if keys.size < 2
18
+ super "translation missing: #{keys.join(', ')}"
19
+ end
20
+ end
21
+
22
+ class InvalidPluralizationData < ArgumentError
23
+ attr_reader :entry, :count
24
+ def initialize(entry, count)
25
+ @entry, @count = entry, count
26
+ super "translation data #{entry.inspect} can not be used with :count => #{count}"
27
+ end
28
+ end
29
+
30
+ class MissingInterpolationArgument < ArgumentError
31
+ attr_reader :key, :string
32
+ def initialize(key, string)
33
+ @key, @string = key, string
34
+ super "interpolation argument #{key.inspect} missing in #{string.inspect}"
35
+ end
36
+ end
37
+
38
+ class ReservedInterpolationKey < ArgumentError
39
+ attr_reader :key, :string
40
+ def initialize(key, string)
41
+ @key, @string = key, string
42
+ super "reserved key #{key.inspect} used in #{string.inspect}"
43
+ end
44
+ end
45
+
46
+ class UnknownFileType < ArgumentError
47
+ attr_reader :type, :filename
48
+ def initialize(type, filename)
49
+ @type, @filename = type, filename
50
+ super "can not load translations from #{filename}, the file type #{type} is not known"
51
+ end
52
+ end
53
+ end
data/lib/i18n.rb ADDED
@@ -0,0 +1,201 @@
1
+ # Authors:: Matt Aimonetti (http://railsontherun.com/),
2
+ # Sven Fuchs (http://www.artweb-design.de),
3
+ # Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
4
+ # Saimon Moore (http://saimonmoore.net),
5
+ # Stephan Soller (http://www.arkanis-development.de/)
6
+ # Copyright:: Copyright (c) 2008 The Ruby i18n Team
7
+ # License:: MIT
8
+ require 'i18n/backend/simple'
9
+ require 'i18n/backend/fast'
10
+ require 'i18n/backend/fast/pluralization_compiler'
11
+ require 'i18n/exceptions'
12
+
13
+ module I18n
14
+ @@backend = nil
15
+ @@load_path = nil
16
+ @@default_locale = :'en'
17
+ @@exception_handler = :default_exception_handler
18
+
19
+ class << self
20
+ # Returns the current backend. Defaults to +Backend::Simple+.
21
+ def backend
22
+ @@backend ||= Backend::Simple.new
23
+ end
24
+
25
+ # Sets the current backend. Used to set a custom backend.
26
+ def backend=(backend)
27
+ @@backend = backend
28
+ end
29
+
30
+ # Returns the current default locale. Defaults to :'en'
31
+ def default_locale
32
+ @@default_locale
33
+ end
34
+
35
+ # Sets the current default locale. Used to set a custom default locale.
36
+ def default_locale=(locale)
37
+ @@default_locale = locale
38
+ end
39
+
40
+ # Returns the current locale. Defaults to I18n.default_locale.
41
+ def locale
42
+ Thread.current[:locale] ||= default_locale
43
+ end
44
+
45
+ # Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
46
+ def locale=(locale)
47
+ Thread.current[:locale] = locale
48
+ end
49
+
50
+ # Returns an array of locales for which translations are available
51
+ def available_locales
52
+ backend.available_locales
53
+ end
54
+
55
+ # Sets the exception handler.
56
+ def exception_handler=(exception_handler)
57
+ @@exception_handler = exception_handler
58
+ end
59
+
60
+ # Allow clients to register paths providing translation data sources. The
61
+ # backend defines acceptable sources.
62
+ #
63
+ # E.g. the provided SimpleBackend accepts a list of paths to translation
64
+ # files which are either named *.rb and contain plain Ruby Hashes or are
65
+ # named *.yml and contain YAML data. So for the SimpleBackend clients may
66
+ # register translation files like this:
67
+ # I18n.load_path << 'path/to/locale/en.yml'
68
+ def load_path
69
+ @@load_path ||= []
70
+ end
71
+
72
+ # Sets the load path instance. Custom implementations are expected to
73
+ # behave like a Ruby Array.
74
+ def load_path=(load_path)
75
+ @@load_path = load_path
76
+ end
77
+
78
+ # Tells the backend to reload translations. Used in situations like the
79
+ # Rails development environment. Backends can implement whatever strategy
80
+ # is useful.
81
+ def reload!
82
+ backend.reload!
83
+ end
84
+
85
+ # Translates, pluralizes and interpolates a given key using a given locale,
86
+ # scope, and default, as well as interpolation values.
87
+ #
88
+ # *LOOKUP*
89
+ #
90
+ # Translation data is organized as a nested hash using the upper-level keys
91
+ # as namespaces. <em>E.g.</em>, ActionView ships with the translation:
92
+ # <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
93
+ #
94
+ # Translations can be looked up at any level of this hash using the key argument
95
+ # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
96
+ # returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
97
+ #
98
+ # Key can be either a single key or a dot-separated key (both Strings and Symbols
99
+ # work). <em>E.g.</em>, the short format can be looked up using both:
100
+ # I18n.t 'date.formats.short'
101
+ # I18n.t :'date.formats.short'
102
+ #
103
+ # Scope can be either a single key, a dot-separated key or an array of keys
104
+ # or dot-separated keys. Keys and scopes can be combined freely. So these
105
+ # examples will all look up the same short date format:
106
+ # I18n.t 'date.formats.short'
107
+ # I18n.t 'formats.short', :scope => 'date'
108
+ # I18n.t 'short', :scope => 'date.formats'
109
+ # I18n.t 'short', :scope => %w(date formats)
110
+ #
111
+ # *INTERPOLATION*
112
+ #
113
+ # Translations can contain interpolation variables which will be replaced by
114
+ # values passed to #translate as part of the options hash, with the keys matching
115
+ # the interpolation variable names.
116
+ #
117
+ # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option
118
+ # value for the key +bar+ will be interpolated into the translation:
119
+ # I18n.t :foo, :bar => 'baz' # => 'foo baz'
120
+ #
121
+ # *PLURALIZATION*
122
+ #
123
+ # Translation data can contain pluralized translations. Pluralized translations
124
+ # are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
125
+ #
126
+ # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
127
+ # pluralization rules. Other algorithms can be supported by custom backends.
128
+ #
129
+ # This returns the singular version of a pluralized translation:
130
+ # I18n.t :foo, :count => 1 # => 'Foo'
131
+ #
132
+ # These both return the plural version of a pluralized translation:
133
+ # I18n.t :foo, :count => 0 # => 'Foos'
134
+ # I18n.t :foo, :count => 2 # => 'Foos'
135
+ #
136
+ # The <tt>:count</tt> option can be used both for pluralization and interpolation.
137
+ # <em>E.g.</em>, with the translation
138
+ # <tt>:foo => ['{{count}} foo', '{{count}} foos']</tt>, count will
139
+ # be interpolated to the pluralized translation:
140
+ # I18n.t :foo, :count => 1 # => '1 foo'
141
+ #
142
+ # *DEFAULTS*
143
+ #
144
+ # This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
145
+ # I18n.t :foo, :default => 'default'
146
+ #
147
+ # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
148
+ # translation for <tt>:foo</tt> was found:
149
+ # I18n.t :foo, :default => :bar
150
+ #
151
+ # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
152
+ # or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
153
+ # I18n.t :foo, :default => [:bar, 'default']
154
+ #
155
+ # <b>BULK LOOKUP</b>
156
+ #
157
+ # This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>.
158
+ # I18n.t [:foo, :bar]
159
+ #
160
+ # Can be used with dot-separated nested keys:
161
+ # I18n.t [:'baz.foo', :'baz.bar']
162
+ #
163
+ # Which is the same as using a scope option:
164
+ # I18n.t [:foo, :bar], :scope => :baz
165
+ def translate(key, options = nil)
166
+ locale = (options && options.delete(:locale)) || I18n.locale
167
+ backend.translate(locale, key, options)
168
+ rescue I18n::ArgumentError => e
169
+ raise e if options && options[:raise]
170
+ send(@@exception_handler, e, locale, key, options || {})
171
+ end
172
+ alias :t :translate
173
+
174
+ # Localizes certain objects, such as dates and numbers to local formatting.
175
+ def localize(object, options = {})
176
+ locale = options[:locale] || I18n.locale
177
+ format = options[:format] || :default
178
+ backend.localize(locale, object, format)
179
+ end
180
+ alias :l :localize
181
+
182
+ protected
183
+ # Handles exceptions raised in the backend. All exceptions except for
184
+ # MissingTranslationData exceptions are re-raised. When a MissingTranslationData
185
+ # was caught and the option :raise is not set the handler returns an error
186
+ # message string containing the key/scope.
187
+ def default_exception_handler(exception, locale, key, options)
188
+ return exception.message if MissingTranslationData === exception
189
+ raise exception
190
+ end
191
+
192
+ # Merges the given locale, key and scope into a single array of keys.
193
+ # Splits keys that contain dots into multiple keys. Makes sure all
194
+ # keys are Symbols.
195
+ def normalize_translation_keys(locale, key, scope)
196
+ keys = [locale] + Array(scope) + [key]
197
+ keys = keys.map { |k| k.to_s.split(/\./) }
198
+ keys.flatten.map { |k| k.to_sym }
199
+ end
200
+ end
201
+ end