File: i18n.rb

package info (click to toggle)
libi18n-ruby 0.4.1-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 576 kB
  • ctags: 619
  • sloc: ruby: 4,655; makefile: 5
file content (322 lines) | stat: -rwxr-xr-x 12,291 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# encoding: utf-8

# Authors::   Sven Fuchs (http://www.artweb-design.de),
#             Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
#             Stephan Soller (http://www.arkanis-development.de/),
#             Saimon Moore (http://saimonmoore.net),
#             Matt Aimonetti (http://railsontherun.com/)
# Copyright:: Copyright (c) 2008 The Ruby i18n Team
# License::   MIT
require 'i18n/exceptions'
require 'i18n/core_ext/string/interpolate'

module I18n
  autoload :Backend, 'i18n/backend'
  autoload :Config,  'i18n/config'
  autoload :Gettext, 'i18n/gettext'
  autoload :Locale,  'i18n/locale'

  class << self
    # Gets I18n configuration object.
    def config
      Thread.current[:i18n_config] ||= I18n::Config.new
    end

    # Sets I18n configuration object.
    def config=(value)
      Thread.current[:i18n_config] = value
    end

    # Write methods which delegates to the configuration object
    %w(locale backend default_locale available_locales default_separator
      exception_handler load_path).each do |method|
      module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
        def #{method}
          config.#{method}
        end

        def #{method}=(value)
          config.#{method} = (value)
        end
      DELEGATORS
    end

    # Tells the backend to reload translations. Used in situations like the
    # Rails development environment. Backends can implement whatever strategy
    # is useful.
    def reload!
      config.backend.reload!
    end

    # Translates, pluralizes and interpolates a given key using a given locale,
    # scope, and default, as well as interpolation values.
    #
    # *LOOKUP*
    #
    # Translation data is organized as a nested hash using the upper-level keys
    # as namespaces. <em>E.g.</em>, ActionView ships with the translation:
    # <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
    #
    # Translations can be looked up at any level of this hash using the key argument
    # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
    # returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
    #
    # Key can be either a single key or a dot-separated key (both Strings and Symbols
    # work). <em>E.g.</em>, the short format can be looked up using both:
    #   I18n.t 'date.formats.short'
    #   I18n.t :'date.formats.short'
    #
    # Scope can be either a single key, a dot-separated key or an array of keys
    # or dot-separated keys. Keys and scopes can be combined freely. So these
    # examples will all look up the same short date format:
    #   I18n.t 'date.formats.short'
    #   I18n.t 'formats.short', :scope => 'date'
    #   I18n.t 'short', :scope => 'date.formats'
    #   I18n.t 'short', :scope => %w(date formats)
    #
    # *INTERPOLATION*
    #
    # Translations can contain interpolation variables which will be replaced by
    # values passed to #translate as part of the options hash, with the keys matching
    # the interpolation variable names.
    #
    # <em>E.g.</em>, with a translation <tt>:foo => "foo %{bar}"</tt> the option
    # value for the key +bar+ will be interpolated into the translation:
    #   I18n.t :foo, :bar => 'baz' # => 'foo baz'
    #
    # *PLURALIZATION*
    #
    # Translation data can contain pluralized translations. Pluralized translations
    # are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
    #
    # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
    # pluralization rules. Other algorithms can be supported by custom backends.
    #
    # This returns the singular version of a pluralized translation:
    #   I18n.t :foo, :count => 1 # => 'Foo'
    #
    # These both return the plural version of a pluralized translation:
    #   I18n.t :foo, :count => 0 # => 'Foos'
    #   I18n.t :foo, :count => 2 # => 'Foos'
    #
    # The <tt>:count</tt> option can be used both for pluralization and interpolation.
    # <em>E.g.</em>, with the translation
    # <tt>:foo => ['%{count} foo', '%{count} foos']</tt>, count will
    # be interpolated to the pluralized translation:
    #   I18n.t :foo, :count => 1 # => '1 foo'
    #
    # *DEFAULTS*
    #
    # This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
    #   I18n.t :foo, :default => 'default'
    #
    # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
    # translation for <tt>:foo</tt> was found:
    #   I18n.t :foo, :default => :bar
    #
    # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
    # or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
    #   I18n.t :foo, :default => [:bar, 'default']
    #
    # *BULK LOOKUP*
    #
    # This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>.
    #   I18n.t [:foo, :bar]
    #
    # Can be used with dot-separated nested keys:
    #   I18n.t [:'baz.foo', :'baz.bar']
    #
    # Which is the same as using a scope option:
    #   I18n.t [:foo, :bar], :scope => :baz
    #
    # *LAMBDAS*
    #
    # Both translations and defaults can be given as Ruby lambdas. Lambdas will be
    # called and passed the key and options.
    #
    # E.g. assuming the key <tt>:salutation</tt> resolves to:
    #   lambda { |key, options| options[:gender] == 'm' ? "Mr. %{options[:name]}" : "Mrs. %{options[:name]}" }
    #
    # Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith".
    #
    # It is recommended to use/implement lambdas in an "idempotent" way. E.g. when
    # a cache layer is put in front of I18n.translate it will generate a cache key
    # from the argument values passed to #translate. Therefor your lambdas should
    # always return the same translations/values per unique combination of argument
    # values.
    def translate(*args)
      options = args.pop if args.last.is_a?(Hash)
      key     = args.shift
      locale  = options && options.delete(:locale) || config.locale
      raises  = options && options.delete(:raise)
      config.backend.translate(locale, key, options || {})
    rescue I18n::ArgumentError => exception
      raise exception if raises
      handle_exception(exception, locale, key, options)
    end
    alias :t :translate

    def translate!(key, options = {})
      translate(key, options.merge( :raise => true ))
    end
    alias :t! :translate!

    # Transliterates UTF-8 characters to ASCII. By default this method will
    # transliterate only Latin strings to an ASCII approximation:
    #
    #    I18n.transliterate("Ærøskøbing")
    #    # => "AEroskobing"
    #
    #    I18n.transliterate("日本語")
    #    # => "???"
    #
    # It's also possible to add support for per-locale transliterations. I18n
    # expects transliteration rules to be stored at
    # <tt>i18n.transliterate.rule</tt>.
    #
    # Transliteration rules can either be a Hash or a Proc. Procs must accept a
    # single string argument. Hash rules inherit the default transliteration
    # rules, while Procs do not.
    #
    # *Examples*
    #
    # Setting a Hash in <locale>.yml:
    #
    #    i18n:
    #      transliterate:
    #        rule:
    #          ü: "ue"
    #          ö: "oe"
    #
    # Setting a Hash using Ruby:
    #
    #     store_translations(:de, :i18n => {
    #       :transliterate => {
    #         :rule => {
    #           "ü" => "ue",
    #           "ö" => "oe"
    #         }
    #       }
    #     )
    #
    # Setting a Proc:
    #
    #     translit = lambda {|string| MyTransliterator.transliterate(string) }
    #     store_translations(:xx, :i18n => {:transliterate => {:rule => translit})
    #
    # Transliterating strings:
    #
    #     I18n.locale = :en
    #     I18n.transliterate("Jürgen") # => "Jurgen"
    #     I18n.locale = :de
    #     I18n.transliterate("Jürgen") # => "Juergen"
    #     I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
    #     I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
    def transliterate(*args)
      options      = args.pop if args.last.is_a?(Hash)
      key          = args.shift
      locale       = options && options.delete(:locale) || config.locale
      raises       = options && options.delete(:raise)
      replacement  = options && options.delete(:replacement)
      config.backend.transliterate(locale, key, replacement)
    rescue I18n::ArgumentError => exception
      raise exception if raises
      handle_exception(exception, locale, key, options)
    end

    # Localizes certain objects, such as dates and numbers to local formatting.
    def localize(object, options = {})
      locale = options.delete(:locale) || config.locale
      format = options.delete(:format) || :default
      config.backend.localize(locale, object, format, options)
    end
    alias :l :localize

    # Executes block with given I18n.locale set.
    def with_locale(tmp_locale = nil)
      if tmp_locale
        current_locale = self.locale
        self.locale    = tmp_locale
      end
      yield
    ensure
      self.locale = current_locale if tmp_locale
    end


    # Merges the given locale, key and scope into a single array of keys.
    # Splits keys that contain dots into multiple keys. Makes sure all
    # keys are Symbols.
    def normalize_keys(locale, key, scope, separator = nil)
      separator ||= I18n.default_separator

      keys = []
      keys.concat normalize_key(locale, separator)
      keys.concat normalize_key(scope, separator)
      keys.concat normalize_key(key, separator)
      keys
    end

  # making these private until Ruby 1.9.2 can send to protected methods again
  # see http://redmine.ruby-lang.org/repositories/revision/ruby-19?rev=24280
  private

    # Handles exceptions raised in the backend. All exceptions except for
    # MissingTranslationData exceptions are re-raised. When a MissingTranslationData
    # was caught and the option :raise is not set the handler returns an error
    # message string containing the key/scope.
    def default_exception_handler(exception, locale, key, options)
      return exception.message if MissingTranslationData === exception
      raise exception
    end

    # Any exceptions thrown in translate will be sent to the @@exception_handler
    # which can be a Symbol, a Proc or any other Object.
    #
    # If exception_handler is a Symbol then it will simply be sent to I18n as
    # a method call. A Proc will simply be called. In any other case the
    # method #call will be called on the exception_handler object.
    #
    # Examples:
    #
    #   I18n.exception_handler = :default_exception_handler             # this is the default
    #   I18n.default_exception_handler(exception, locale, key, options) # will be called like this
    #
    #   I18n.exception_handler = lambda { |*args| ... }                 # a lambda
    #   I18n.exception_handler.call(exception, locale, key, options)    # will be called like this
    #
    #  I18n.exception_handler = I18nExceptionHandler.new                # an object
    #  I18n.exception_handler.call(exception, locale, key, options)     # will be called like this
    def handle_exception(exception, locale, key, options)
      case config.exception_handler
      when Symbol
        send(config.exception_handler, exception, locale, key, options)
      else
        config.exception_handler.call(exception, locale, key, options)
      end
    end

    # Deprecated. Will raise a warning in future versions and then finally be
    # removed. Use I18n.normalize_keys instead.
    def normalize_translation_keys(locale, key, scope, separator = nil)
      normalize_keys(locale, key, scope, separator)
    end

    def normalize_key(key, separator)
      normalized_key_cache[separator][key] ||=
        case key
        when Array
          key.map { |k| normalize_key(k, separator) }.flatten
        else
          keys = key.to_s.split(separator)
          keys.delete('')
          keys.map!{ |k| k.to_sym }
          keys
        end
    end

    def normalized_key_cache
      @normalized_key_cache ||= Hash.new { |h,k| h[k] = {} }
    end
  end
end