class Dry::Schema::Messages::YAML

Plain YAML message backend

@api public

Constants

EMPTY_CONTEXT
LOCALE_TOKEN
TOKEN_REGEXP

Attributes

data[R]

Loaded localized message templates

@return [Hash]

t[R]

Translation function

@return [Proc]

Public Class Methods

build(options = EMPTY_HASH) click to toggle source

@api private

Calls superclass method Dry::Schema::Messages::Abstract::build
# File lib/dry/schema/messages/yaml.rb, line 38
def self.build(options = EMPTY_HASH)
  super do |config|
    config.default_locale = :en unless config.default_locale

    config.root = -"%<locale>s.#{config.root}"

    config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
      -"%<locale>s.#{path}"
    }
  end
end
cache() click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 74
def self.cache
  @cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
end
flat_hash(hash, path = EMPTY_ARRAY, keys = {}) click to toggle source

@api private rubocop: disable Metrics/PerceivedComplexity

# File lib/dry/schema/messages/yaml.rb, line 52
def self.flat_hash(hash, path = EMPTY_ARRAY, keys = {})
  hash.each do |key, value|
    flat_hash(value, [*path, key], keys) if value.is_a?(Hash)

    if value.is_a?(String) && hash["text"] != value
      keys[[*path, key].join(DOT)] = {
        text: value,
        meta: EMPTY_HASH
      }
    elsif value.is_a?(Hash) && value["text"].is_a?(String)
      keys[[*path, key].join(DOT)] = {
        text: value["text"],
        meta: value.reject { _1.eql?("text") }.transform_keys(&:to_sym)
      }
    end
  end

  keys
end
new(data: EMPTY_HASH, config: nil) click to toggle source

@api private

Calls superclass method
# File lib/dry/schema/messages/yaml.rb, line 84
def initialize(data: EMPTY_HASH, config: nil)
  super()
  @data = data
  @config = config if config
  @t = proc { |key, locale: default_locale| get("%<locale>s.#{key}", locale: locale) }
end
source_cache() click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 79
def self.source_cache
  @source_cache ||= Concurrent::Map.new
end

Public Instance Methods

get(key, options = EMPTY_HASH) click to toggle source

Get a message for the given key and its options

@param [Symbol] key @param [Hash] options

@return [String]

@api public

# File lib/dry/schema/messages/yaml.rb, line 111
def get(key, options = EMPTY_HASH)
  data[evaluated_key(key, options)]
end
interpolatable_data(key, options, **data) click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 152
def interpolatable_data(key, options, **data)
  tokens = evaluation_context(key, options).fetch(:tokens)
  data.select { |k,| tokens.include?(k) }
end
interpolate(key, options, **data) click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 158
def interpolate(key, options, **data)
  evaluator = evaluation_context(key, options).fetch(:evaluator)
  data.empty? ? evaluator.() : evaluator.(**data)
end
key?(key, options = EMPTY_HASH) click to toggle source

Check if given key is defined

@return [Boolean]

@api public

# File lib/dry/schema/messages/yaml.rb, line 120
def key?(key, options = EMPTY_HASH)
  data.key?(evaluated_key(key, options))
end
looked_up_paths(predicate, options) click to toggle source

Get an array of looked up paths

@param [Symbol] predicate @param [Hash] options

@return [String]

@api public

# File lib/dry/schema/messages/yaml.rb, line 99
def looked_up_paths(predicate, options)
  super.map { |path| path % {locale: options[:locale] || default_locale} }
end
merge(overrides) click to toggle source

Merge messages from an additional path

@param [String] overrides

@return [Messages::I18n]

@api public

# File lib/dry/schema/messages/yaml.rb, line 131
def merge(overrides)
  if overrides.is_a?(Hash)
    self.class.new(
      data: data.merge(self.class.flat_hash(overrides)),
      config: config
    )
  else
    self.class.new(
      data: Array(overrides).reduce(data) { |a, e| a.merge(load_translations(e)) },
      config: config
    )
  end
end
prepare() click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 146
def prepare
  @data = config.load_paths.map { |path| load_translations(path) }.reduce({}, :merge)
  self
end

Private Instance Methods

cache() click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 187
def cache
  @cache ||= self.class.cache[self]
end
evaluated_key(key, options) click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 203
def evaluated_key(key, options)
  return key unless key.include?(LOCALE_TOKEN)

  key % {locale: options[:locale] || default_locale}
end
evaluation_context(key, options) click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 166
        def evaluation_context(key, options)
          cache.fetch_or_store(get(key, options).fetch(:text)) do |input|
            tokens = input.scan(TOKEN_REGEXP).flatten(1).map(&:to_sym).to_set
            text = input.gsub("%", "#")

            # rubocop:disable Security/Eval
            # rubocop:disable Style/DocumentDynamicEvalDefinition
            evaluator = eval(<<~RUBY, EMPTY_CONTEXT, __FILE__, __LINE__ + 1)
              -> (#{tokens.map { |token| "#{token}:" }.join(", ")}) { "#{text}" }
            RUBY
            # rubocop:enable Style/DocumentDynamicEvalDefinition
            # rubocop:enable Security/Eval

            {
              tokens: tokens,
              evaluator: evaluator
            }
          end
        end
load_translations(path) click to toggle source

@api private

# File lib/dry/schema/messages/yaml.rb, line 192
def load_translations(path)
  data = self.class.source_cache.fetch_or_store(path) do
    self.class.flat_hash(::YAML.load_file(path)).freeze
  end

  return data unless custom_top_namespace?(path)

  data.transform_keys { _1.gsub(DEFAULT_MESSAGES_ROOT, config.top_namespace) }
end