Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

executable file 334 lines (311 sloc) 12.974 kb
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 323 324 325 326 327 328 329 330 331 332 333
require 'i18n/version'
require 'i18n/exceptions'
require 'i18n/interpolate/ruby'

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

  RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :format, :cascade, :throw, :raise, :rescue_format]
  RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/

  extend(Module.new {
    # 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.last.is_a?(Hash) ? args.pop.dup : {}
      key = args.shift
      backend = config.backend
      locale = options.delete(:locale) || config.locale
      handling = options.delete(:throw) && :throw || options.delete(:raise) && :raise # TODO deprecate :raise

      raise I18n::ArgumentError if key.is_a?(String) && key.empty?

      result = catch(:exception) do
        if key.is_a?(Array)
          key.map { |k| backend.translate(locale, k, options) }
        else
          backend.translate(locale, key, options)
        end
      end
      result.is_a?(MissingTranslation) ? handle_exception(handling, result, locale, key, options) : result
    end
    alias :t :translate

    # Wrapper for <tt>translate</tt> that adds <tt>:raise => true</tt>. With
    # this option, if no translation is found, it will raise <tt>I18n::MissingTranslationData</tt>
    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.dup if args.last.is_a?(Hash)
      key = args.shift
      locale = options && options.delete(:locale) || config.locale
      handling = options && (options.delete(:throw) && :throw || options.delete(:raise) && :raise)
      replacement = options && options.delete(:replacement)
      config.backend.transliterate(locale, key, replacement)
    rescue I18n::ArgumentError => exception
      handle_exception(handling, exception, locale, key, options || {})
    end

    # Localizes certain objects, such as dates and numbers to local formatting.
    def localize(object, options = nil)
      options = options ? options.dup : {}
      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

    # Any exceptions thrown in translate will be sent to the @@exception_handler
    # which can be a Symbol, a Proc or any other Object unless they're forced to
    # be raised or thrown (MissingTranslation).
    #
    # 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(handling, exception, locale, key, options)
      case handling
      when :raise
        raise(exception.respond_to?(:to_exception) ? exception.to_exception : exception)
      when :throw
        throw :exception, exception
      else
        case handler = options[:exception_handler] || config.exception_handler
        when Symbol
          send(handler, exception, locale, key, options)
        else
          handler.call(exception, locale, key, options)
        end
      end
    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

    # DEPRECATED. Use I18n.normalize_keys instead.
    def normalize_translation_keys(locale, key, scope, separator = nil)
      puts "I18n.normalize_translation_keys is deprecated. Please use the class I18n.normalize_keys instead."
      normalize_keys(locale, key, scope, separator)
    end

    # DEPRECATED. Please use the I18n::ExceptionHandler class instead.
    def default_exception_handler(exception, locale, key, options)
      puts "I18n.default_exception_handler is deprecated. Please use the class I18n::ExceptionHandler instead " +
           "(an instance of which is set to I18n.exception_handler by default)."
      exception.is_a?(MissingTranslation) ? exception.message : raise(exception)
    end
  })
end
Something went wrong with that request. Please try again.