heartcombo / devise

Flexible authentication solution for Rails with Warden.
http://blog.plataformatec.com.br/tag/devise/
MIT License
23.85k stars 5.53k forks source link

[Feature Request] Scoped Views/Mailers/I18n per model inside app/views/devise #5627

Open jamesst20 opened 10 months ago

jamesst20 commented 10 months ago

Environment

Current behavior

config/initializers/devise.rb

config.scoped_views = true

Exemple

/users/sign_in 
Paths: 
app/views/users/sessions/new
app/views/devise/sessions/new

/administrators/sign_in
Paths:
app/views/administrators/sessions/new
app/views/devise/sessions/new

Feature request behavior

/users/sign_in 
Paths: 
app/views/users/sessions/new
app/views/devise/users/sessions/new <--- This one
app/views/devise/sessions/new

/administrators/sign_in
Paths:
app/views/administrators/sessions/new
app/views/devise/administrators/sessions/new <--- This one
app/views/devise/sessions/new

Cons: This is likely going to require changes to I18n lookup path to add default path as a default fallback

Before:  devise.sessions.new
After: devise.users.sessions.new

With proper fallback:

1. devise.users.sessions.new
2. devise.sessions.new (fallback to add)

Temporary workaround to get this working using a single initializer

##
# Allow overwriting devise views from app/views/devise/<scope name>
# It will fallback to the default directory
module DeviseScopedViews
  def _prefixes
    prefixes = super.dup

    return prefixes unless devise_controller?

    devise_prefix = prefixes.index { |dir| dir.include?("devise/") }

    return prefixes if devise_prefix.nil?

    prefixes.insert(devise_prefix, prefixes[devise_prefix].sub("devise/", "devise/#{scope_name.to_s.pluralize}/"))
  end
end

##
# Allow devise i18n to fallback to the default translation path if no specific ones are given per scope.
module DeviseScopedI18n
  def t(key, **options)
    return super(key, **options) unless try(:devise_controller?)

    key = send(:scope_key_by_partial, key)

    translate key, **options.merge(default: [key.sub("#{scope_name.to_s.pluralize}.", "").to_sym])
  end
end

##
# Allow overwriting devise mailer views from app/views/devise/<scope name>
# It will fallback to the default directory
# Add missing methods to have working DeviseScopedI18n
module DeviseMailersScopedViews
  extend ActiveSupport::Concern

  included do
    helper_method :devise_controller?, :scope_name

    def template_paths
      paths = super.dup

      index = paths.index { |dir| dir.include?("devise/mailer") }

      return paths if index.nil?

      paths.insert(index, "devise/#{scope_name.to_s.pluralize}/mailer")
    end

    def devise_controller?
      true
    end
  end
end

Devise::Mailers::Helpers.prepend DeviseMailersScopedViews

ActiveSupport.on_load(:action_controller) { prepend DeviseScopedViews }
ActiveSupport.on_load(:action_view) { prepend DeviseScopedI18n }