# frozen_string_literal: true
require "mobility/util"
module Mobility
module Plugins
=begin
Falls back to one or more alternative locales in case no value is defined for a
given locale.
For +fallbacks: true+, Mobility will use an instance of
+I18n::Locale::Fallbacks+, but this can be configured by overriding
+generate_fallbacks+ in the translations class.
If a hash is passed to the +fallbacks+ option, a new fallbacks instance will be
created for the model with the hash defining additional fallbacks. To set a
default value for this hash, pass this value to the plugin in your Mobility
configuration.
In addition, fallbacks are disabled in certain situations. To explicitly disable
fallbacks when reading and writing, you can pass the fallback: false
option to the reader method. This can be useful to determine the actual
value of the translated attribute, including a possible +nil+ value.
The other situation where fallbacks are disabled is when the locale is
specified explicitly, either by passing a `locale` option to the accessor or by
using locale or fallthrough accessors. (See example below.)
You can also pass a locale or array of locales to the +fallback+ option to use
that locale or locales that read, e.g. fallback: :fr would fetch the
French translation if the value in the current locale was +nil+, whereas
fallback: [:fr, :es] would try French, then Spanish if the value in
the current locale was +nil+.
@see https://github.com/svenfuchs/i18n/wiki/Fallbacks I18n Fallbacks
@example With default fallbacks enabled (falls through to default locale)
class Post
extend Mobility
translates :title, fallbacks: true
end
I18n.default_locale = :en
Mobility.locale = :en
post = Post.new(title: "foo")
Mobility.locale = :ja
post.title
#=> "foo"
post.title = "bar"
post.title
#=> "bar"
@example With additional fallbacks enabled
class Post
extend Mobility
translates :title, fallbacks: { :'en-US' => 'de-DE', :pt => 'de-DE' }
end
Mobility.locale = :'de-DE'
post = Post.new(title: "foo")
Mobility.locale = :'en-US'
post.title
#=> "foo"
post.title = "bar"
post.title
#=> "bar"
@example Passing fallback option when reading value
class Post
extend Mobility
translates :title, fallbacks: true
end
I18n.default_locale = :en
Mobility.locale = :en
post = Post.new(title: "Mobility")
Mobility.with_locale(:fr) { post.title = "Mobilité" }
Mobility.locale = :ja
post.title
#=> "Mobility"
post.title(fallback: false)
#=> nil
post.title(fallback: :fr)
#=> "Mobilité"
@example Fallbacks disabled
class Post
extend Mobility
translates :title, fallbacks: { :'fr' => 'en' }, locale_accessors: true
end
I18n.default_locale = :en
Mobility.locale = :en
post = Post.new(title: "Mobility")
Mobility.locale = :fr
post.title
#=> "Mobility"
post.title(fallback: false)
#=> nil
post.title(locale: :fr)
#=> nil
post.title_fr
#=> nil
=end
module Fallbacks
extend Plugin
default true
requires :backend, include: :before
# Applies fallbacks plugin to attributes. Completely disables fallbacks
# on model if option is +false+.
included_hook do |_, backend_class|
fallbacks = options[:fallbacks]
backend_class.include(BackendReader.new(fallbacks, method(:generate_fallbacks))) unless fallbacks == false
end
private
def generate_fallbacks(fallbacks)
fallbacks_class = I18n.respond_to?(:fallbacks) ? I18nFallbacks : I18n::Locale::Fallbacks
fallbacks_class.new(fallbacks)
end
class I18nFallbacks < ::I18n::Locale::Fallbacks
def [](locale)
super | I18n.fallbacks[locale]
end
end
class BackendReader < Module
def initialize(fallbacks_option, fallbacks_generator)
@fallbacks_generator = fallbacks_generator
define_read(convert_option_to_fallbacks(fallbacks_option))
end
private
def define_read(fallbacks)
define_method :read do |locale, fallback: true, **options|
return super(locale, **options) if !fallback || options[:locale]
locales = fallback == true ? fallbacks[locale] : [locale, *fallback]
locales.each do |fallback_locale|
value = super(fallback_locale, **options)
return value if Util.present?(value)
end
super(locale, **options)
end
end
def convert_option_to_fallbacks(option)
if option.is_a?(::Hash)
@fallbacks_generator[option]
elsif option == true
@fallbacks_generator[{}]
else
::Hash.new { [] }
end
end
end
end
register_plugin(:fallbacks, Fallbacks)
end
end