# 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