File: active_support/vendor/i18n-0.4.1/i18n/backend/base.rb

Overview
Module Structure
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: I18n#6
  module: Backend#7
  module: Base#8
includes
  Transliterator ( I18n::Backend )
has properties
constant: RESERVED_KEYS #11
constant: RESERVED_KEYS_PATTERN #12
constant: DEPRECATED_INTERPOLATION_SYNTAX_PATTERN #13
constant: INTERPOLATION_SYNTAX_PATTERN #14
method: load_translations / 1 #19
method: store_translations / 3 #26
method: translate / 3 #30
method: localize / 4 #56
method: available_locales #81
method: reload! #85
method: lookup / 4 #92
method: default / 4 #100
method: resolve / 4 #116
method: pluralize / 3 #135
method: interpolate / 3 #152
method: preserve_encoding / 1 #190
method: interpolate_lambda? / 3 #203
method: load_file / 1 #211
method: load_rb / 1 #220
method: load_yml / 1 #226
method: warn_syntax_deprecation! #230

Code

   1  # encoding: utf-8
   2 
   3  require 'yaml'
   4  require 'i18n/core_ext/hash'
   5 
   6  module I18n
   7    module Backend
   8      module Base
   9        include I18n::Backend::Transliterator
  10 
  11        RESERVED_KEYS = [:scope, :default, :separator, :resolve]
  12        RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
  13        DEPRECATED_INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
  14        INTERPOLATION_SYNTAX_PATTERN = /%\{([^\}]+)\}/
  15 
  16        # Accepts a list of paths to translation files. Loads translations from
  17        # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
  18        # for details.
  19        def load_translations(*filenames)
  20          filenames = I18n.load_path.flatten if filenames.empty?
  21          filenames.each { |filename| load_file(filename) }
  22        end
  23 
  24        # This method receives a locale, a data hash and options for storing translations.
  25        # Should be implemented
  26        def store_translations(locale, data, options = {})
  27          raise NotImplementedError
  28        end
  29 
  30        def translate(locale, key, options = {})
  31          raise InvalidLocale.new(locale) unless locale
  32          return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
  33 
  34          entry = key && lookup(locale, key, options[:scope], options)
  35 
  36          if options.empty?
  37            entry = resolve(locale, key, entry, options)
  38          else
  39            count, default = options.values_at(:count, :default)
  40            values = options.except(*RESERVED_KEYS)
  41            entry = entry.nil? && default ?
  42              default(locale, key, default, options) : resolve(locale, key, entry, options)
  43          end
  44 
  45          raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
  46          entry = entry.dup if entry.is_a?(String)
  47 
  48          entry = pluralize(locale, entry, count) if count
  49          entry = interpolate(locale, entry, values) if values
  50          entry
  51        end
  52 
  53        # Acts the same as +strftime+, but uses a localized version of the
  54        # format string. Takes a key from the date/time formats translations as
  55        # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
  56        def localize(locale, object, format = :default, options = {})
  57          raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
  58 
  59          if Symbol === format
  60            key = format
  61            type = object.respond_to?(:sec) ? 'time' : 'date'
  62            format = I18n.t(:"#{type}.formats.#{key}", options.merge(:raise => true, :object => object, :locale => locale))
  63          end
  64 
  65          # format = resolve(locale, object, format, options)
  66          format = format.to_s.gsub(/%[aAbBp]/) do |match|
  67            case match
  68            when '%a' then I18n.t(:"date.abbr_day_names",                  :locale => locale, :format => format)[object.wday]
  69            when '%A' then I18n.t(:"date.day_names",                       :locale => locale, :format => format)[object.wday]
  70            when '%b' then I18n.t(:"date.abbr_month_names",                :locale => locale, :format => format)[object.mon]
  71            when '%B' then I18n.t(:"date.month_names",                     :locale => locale, :format => format)[object.mon]
  72            when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
  73            end
  74          end
  75 
  76          object.strftime(format)
  77        end
  78 
  79        # Returns an array of locales for which translations are available
  80        # ignoring the reserved translation meta data key :i18n.
  81        def available_locales
  82          raise NotImplementedError
  83        end
  84 
  85        def reload!
  86          @skip_syntax_deprecation = false
  87        end
  88 
  89        protected
  90 
  91          # The method which actually looks up for the translation in the store.
  92          def lookup(locale, key, scope = [], options = {})
  93            raise NotImplementedError
  94          end
  95 
  96          # Evaluates defaults.
  97          # If given subject is an Array, it walks the array and returns the
  98          # first translation that can be resolved. Otherwise it tries to resolve
  99          # the translation directly.
 100          def default(locale, object, subject, options = {})
 101            options = options.dup.reject { |key, value| key == :default }
 102            case subject
 103            when Array
 104              subject.each do |item|
 105                result = resolve(locale, object, item, options) and return result
 106              end and nil
 107            else
 108              resolve(locale, object, subject, options)
 109            end
 110          end
 111 
 112          # Resolves a translation.
 113          # If the given subject is a Symbol, it will be translated with the
 114          # given options. If it is a Proc then it will be evaluated. All other
 115          # subjects will be returned directly.
 116          def resolve(locale, object, subject, options = nil)
 117            return subject if options[:resolve] == false
 118            case subject
 119            when Symbol
 120              I18n.translate(subject, (options || {}).merge(:locale => locale, :raise => true))
 121            when Proc
 122              date_or_time = options.delete(:object) || object
 123              resolve(locale, object, subject.call(date_or_time, options), options = {})
 124            else
 125              subject
 126            end
 127          rescue MissingTranslationData
 128            nil
 129          end
 130 
 131          # Picks a translation from an array according to English pluralization
 132          # rules. It will pick the first translation if count is not equal to 1
 133          # and the second translation if it is equal to 1. Other backends can
 134          # implement more flexible or complex pluralization rules.
 135          def pluralize(locale, entry, count)
 136            return entry unless entry.is_a?(Hash) && count
 137 
 138            key = :zero if count == 0 && entry.has_key?(:zero)
 139            key ||= count == 1 ? :one : :other
 140            raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
 141            entry[key]
 142          end
 143 
 144          # Interpolates values into a given string.
 145          #
 146          #   interpolate "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
 147          #   # => "file test.txt opened by %{user}"
 148          #
 149          # Note that you have to double escape the <tt>\\</tt> when you want to escape
 150          # the <tt>{{...}}</tt> key in a string (once for the string and once for the
 151          # interpolation).
 152          def interpolate(locale, string, values = {})
 153            return string unless string.is_a?(::String) && !values.empty?
 154            original_values = values.dup
 155 
 156            preserve_encoding(string) do
 157              string = string.gsub(DEPRECATED_INTERPOLATION_SYNTAX_PATTERN) do
 158                escaped, key = $1, $2.to_sym
 159                if escaped
 160                  "{{#{key}}}"
 161                else
 162                  warn_syntax_deprecation!
 163                  "%{#{key}}"
 164                end
 165              end
 166 
 167              keys = string.scan(INTERPOLATION_SYNTAX_PATTERN).flatten
 168              return string if keys.empty?
 169 
 170              values.each do |key, value|
 171                if keys.include?(key.to_s)
 172                  value = value.call(values) if interpolate_lambda?(value, string, key)
 173                  value = value.to_s unless value.is_a?(::String)
 174                  values[key] = value
 175                else
 176                  values.delete(key)
 177                end
 178              end
 179 
 180              string % values
 181            end
 182          rescue KeyError => e
 183            if string =~ RESERVED_KEYS_PATTERN
 184              raise ReservedInterpolationKey.new($1.to_sym, string)
 185            else
 186              raise MissingInterpolationArgument.new(original_values, string)
 187            end
 188          end
 189 
 190          def preserve_encoding(string)
 191            if string.respond_to?(:encoding)
 192              encoding = string.encoding
 193              result = yield
 194              result.force_encoding(encoding) if result.respond_to?(:force_encoding)
 195              result
 196            else
 197              yield
 198            end
 199          end
 200 
 201          # returns true when the given value responds to :call and the key is
 202          # an interpolation placeholder in the given string
 203          def interpolate_lambda?(object, string, key)
 204            object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
 205          end
 206 
 207          # Loads a single translations file by delegating to #load_rb or
 208          # #load_yml depending on the file extension and directly merges the
 209          # data to the existing translations. Raises I18n::UnknownFileType
 210          # for all other file extensions.
 211          def load_file(filename)
 212            type = File.extname(filename).tr('.', '').downcase
 213            raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}")
 214            data = send(:"load_#{type}", filename) # TODO raise a meaningful exception if this does not yield a Hash
 215            data.each { |locale, d| store_translations(locale, d) }
 216          end
 217 
 218          # Loads a plain Ruby translations file. eval'ing the file must yield
 219          # a Hash containing translation data with locales as toplevel keys.
 220          def load_rb(filename)
 221            eval(IO.read(filename), binding, filename)
 222          end
 223 
 224          # Loads a YAML translations file. The data must have locales as
 225          # toplevel keys.
 226          def load_yml(filename)
 227            YAML::load(IO.read(filename))
 228          end
 229 
 230          def warn_syntax_deprecation! #:nodoc:
 231            return if @skip_syntax_deprecation
 232            warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{caller.join("\n")}"
 233            @skip_syntax_deprecation = true
 234          end
 235      end
 236    end
 237  end