module I18n::Backend::Base

Public Instance Methods

Returns an array of locales for which translations are available ignoring the reserved translation meta data key :i18n.

# File lib/i18n/backend/base.rb, line 97
def available_locales
  raise NotImplementedError
end
# File lib/i18n/backend/base.rb, line 105
def eager_load!
  @eager_loaded = true
end
# File lib/i18n/backend/base.rb, line 71
def exists?(locale, key, options = EMPTY_HASH)
  lookup(locale, key, options[:scope]) != nil
end

Accepts a list of paths to translation files. Loads translations from plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See load_rb, load_yml, and load_json for details.

# File lib/i18n/backend/base.rb, line 14
def load_translations(*filenames)
  filenames = I18n.load_path if filenames.empty?
  filenames.flatten.each do |filename|
    loaded_translations = load_file(filename)
    yield filename, loaded_translations if block_given?
  end
end

Acts the same as strftime, but uses a localized version of the format string. Takes a key from the date/time formats translations as a format argument (e.g., :short in :'date.formats').

# File lib/i18n/backend/base.rb, line 78
def localize(locale, object, format = :default, options = EMPTY_HASH)
  if object.nil? && options.include?(:default)
    return options[:default]
  end
  raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)

  if Symbol === format
    key  = format
    type = object.respond_to?(:sec) ? 'time' : 'date'
    options = options.merge(:raise => true, :object => object, :locale => locale)
    format  = I18n.t(:"#{type}.formats.#{key}", **options)
  end

  format = translate_localization_format(locale, object, format, options)
  object.strftime(format)
end
# File lib/i18n/backend/base.rb, line 101
def reload!
  eager_load! if eager_loaded?
end

This method receives a locale, a data hash and options for storing translations. Should be implemented

# File lib/i18n/backend/base.rb, line 24
def store_translations(locale, data, options = EMPTY_HASH)
  raise NotImplementedError
end
# File lib/i18n/backend/base.rb, line 28
def translate(locale, key, options = EMPTY_HASH)
  raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty?
  raise InvalidLocale.new(locale) unless locale
  return nil if key.nil? && !options.key?(:default)

  entry = lookup(locale, key, options[:scope], options) unless key.nil?

  if entry.nil? && options.key?(:default)
    entry = default(locale, key, options[:default], options)
  else
    entry = resolve_entry(locale, key, entry, options)
  end

  count = options[:count]

  if entry.nil? && (subtrees? || !count)
    if (options.key?(:default) && !options[:default].nil?) || !options.key?(:default)
      throw(:exception, I18n::MissingTranslation.new(locale, key, options))
    end
  end

  entry = entry.dup if entry.is_a?(String)
  entry = pluralize(locale, entry, count) if count

  if entry.nil? && !subtrees?
    throw(:exception, I18n::MissingTranslation.new(locale, key, options))
  end

  deep_interpolation = options[:deep_interpolation]
  skip_interpolation = options[:skip_interpolation]
  values = Utils.except(options, *RESERVED_KEYS) unless options.empty?
  if !skip_interpolation && values && !values.empty?
    entry = if deep_interpolation
      deep_interpolate(locale, entry, values)
    else
      interpolate(locale, entry, values)
    end
  elsif entry.is_a?(String) && entry =~ I18n.reserved_keys_pattern
    raise ReservedInterpolationKey.new($1.to_sym, entry)
  end
  entry
end

Protected Instance Methods

Deep interpolation

deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
                 ann: 'good', john: 'big'
#=> { people: { ann: "Ann is good", john: "John is big" } }
# File lib/i18n/backend/base.rb, line 217
def deep_interpolate(locale, data, values = EMPTY_HASH)
  return data if values.empty?

  case data
  when ::String
    I18n.interpolate(data, values)
  when ::Hash
    data.each_with_object({}) do |(k, v), result|
      result[k] = deep_interpolate(locale, v, values)
    end
  when ::Array
    data.map do |v|
      deep_interpolate(locale, v, values)
    end
  else
    data
  end
end

Evaluates defaults. If given subject is an Array, it walks the array and returns the first translation that can be resolved. Otherwise it tries to resolve the translation directly.

# File lib/i18n/backend/base.rb, line 128
def default(locale, object, subject, options = EMPTY_HASH)
  if options.size == 1 && options.has_key?(:default)
    options = {}
  else
    options = Utils.except(options, :default)
  end

  case subject
  when Array
    subject.each do |item|
      result = resolve(locale, object, item, options)
      return result unless result.nil?
    end and nil
  else
    resolve(locale, object, subject, options)
  end
end
# File lib/i18n/backend/base.rb, line 111
def eager_loaded?
  @eager_loaded ||= false
end

Interpolates values into a given subject.

if the given subject is a string then:
method interpolates "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
# => "file test.txt opened by %{user}"

if the given subject is an array then:
each element of the array is recursively interpolated (until it finds a string)
method interpolates ["yes, %{user}", ["maybe no, %{user}", "no, %{user}"]], :user => "bartuz"
# => ["yes, bartuz", ["maybe no, bartuz", "no, bartuz"]]
# File lib/i18n/backend/base.rb, line 201
def interpolate(locale, subject, values = EMPTY_HASH)
  return subject if values.empty?

  case subject
  when ::String then I18n.interpolate(subject, values)
  when ::Array then subject.map { |element| interpolate(locale, element, values) }
  else
    subject
  end
end

Loads a single translations file by delegating to load_rb or load_yml depending on the file extension and directly merges the data to the existing translations. Raises I18n::UnknownFileType for all other file extensions.

# File lib/i18n/backend/base.rb, line 240
def load_file(filename)
  type = File.extname(filename).tr('.', '').downcase
  raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
  data, keys_symbolized = send(:"load_#{type}", filename)
  unless data.is_a?(Hash)
    raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
  end
  data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) }

  data
end

Loads a JSON translations file. The data must have locales as toplevel keys.

# File lib/i18n/backend/base.rb, line 276
def load_json(filename)
  begin
    # Use #load_file as a proxy for a version of JSON where symbolize_names and freeze are supported.
    if ::JSON.respond_to?(:load_file)
      [::JSON.load_file(filename, symbolize_names: true, freeze: true), true]
    else
      [::JSON.parse(File.read(filename)), false]
    end
  rescue TypeError, StandardError => e
    raise InvalidLocaleData.new(filename, e.inspect)
  end
end

Loads a plain Ruby translations file. eval’ing the file must yield a Hash containing translation data with locales as toplevel keys.

# File lib/i18n/backend/base.rb, line 254
def load_rb(filename)
  translations = eval(IO.read(filename), binding, filename.to_s)
  [translations, false]
end

Loads a YAML translations file. The data must have locales as toplevel keys.

# File lib/i18n/backend/base.rb, line 261
def load_yml(filename)
  begin
    if YAML.respond_to?(:unsafe_load_file) # Psych 4.0 way
      [YAML.unsafe_load_file(filename, symbolize_names: true, freeze: true), true]
    else
      [YAML.load_file(filename), false]
    end
  rescue TypeError, ScriptError, StandardError => e
    raise InvalidLocaleData.new(filename, e.inspect)
  end
end
Also aliased as: load_yaml

The method which actually looks up for the translation in the store.

# File lib/i18n/backend/base.rb, line 116
def lookup(locale, key, scope = [], options = EMPTY_HASH)
  raise NotImplementedError
end
# File lib/i18n/backend/base.rb, line 308
def pluralization_key(entry, count)
  key = :zero if count == 0 && entry.has_key?(:zero)
  key ||= count == 1 ? :one : :other
end

Picks a translation from a pluralized mnemonic subkey according to English pluralization rules :

  • It will pick the :one subkey if count is equal to 1.

  • It will pick the :other subkey otherwise.

  • It will pick the :zero subkey in the special case where count is equal to 0 and there is a :zero subkey present. This behaviour is not standard with regards to the CLDR pluralization rules.

Other backends can implement more flexible or complex pluralization rules.

# File lib/i18n/backend/base.rb, line 182
def pluralize(locale, entry, count)
  entry = entry.reject { |k, _v| k == :attributes } if entry.is_a?(Hash)
  return entry unless entry.is_a?(Hash) && count

  key = pluralization_key(entry, count)
  raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
  entry[key]
end

Resolves a translation. If the given subject is a Symbol, it will be translated with the given options. If it is a Proc then it will be evaluated. All other subjects will be returned directly.

# File lib/i18n/backend/base.rb, line 150
def resolve(locale, object, subject, options = EMPTY_HASH)
  return subject if options[:resolve] == false
  result = catch(:exception) do
    case subject
    when Symbol
      I18n.translate(
        subject,
        **options.merge(
          :locale => locale,
          :throw => true,
          :skip_interpolation => true
        )
      )
    when Proc
      date_or_time = options.delete(:object) || object
      resolve(locale, object, subject.call(date_or_time, **options))
    else
      subject
    end
  end
  result unless result.is_a?(MissingTranslation)
end
Also aliased as: resolve_entry
Alias for: resolve
# File lib/i18n/backend/base.rb, line 120
def subtrees?
  true
end
# File lib/i18n/backend/base.rb, line 289
def translate_localization_format(locale, object, format, options)
  format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match|
    case match
    when '%a' then I18n.t!(:"date.abbr_day_names",                  :locale => locale, :format => format)[object.wday]
    when '%^a' then I18n.t!(:"date.abbr_day_names",                 :locale => locale, :format => format)[object.wday].upcase
    when '%A' then I18n.t!(:"date.day_names",                       :locale => locale, :format => format)[object.wday]
    when '%^A' then I18n.t!(:"date.day_names",                      :locale => locale, :format => format)[object.wday].upcase
    when '%b' then I18n.t!(:"date.abbr_month_names",                :locale => locale, :format => format)[object.mon]
    when '%^b' then I18n.t!(:"date.abbr_month_names",               :locale => locale, :format => format)[object.mon].upcase
    when '%B' then I18n.t!(:"date.month_names",                     :locale => locale, :format => format)[object.mon]
    when '%^B' then I18n.t!(:"date.month_names",                    :locale => locale, :format => format)[object.mon].upcase
    when '%p' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).upcase
    when '%P' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).downcase
    end
  end
rescue MissingTranslationData => e
  e.message
end