diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26180e5dac..5cab47c219 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+### Unreleased
+
+* bug fixes
+ * Respect locale set by controller in failure app. Devise will carry over the current I18n.locale option when triggering authentication, and will wrap the failure app call with it. [#5567](https://github.com/heartcombo/devise/pull/5567)
+
### 4.9.3 - 2023-10-11
* enhancements
diff --git a/app/controllers/devise/sessions_controller.rb b/app/controllers/devise/sessions_controller.rb
index 7c4ee7d4eb..76b780209e 100644
--- a/app/controllers/devise/sessions_controller.rb
+++ b/app/controllers/devise/sessions_controller.rb
@@ -45,7 +45,7 @@ def serialize_options(resource)
end
def auth_options
- { scope: resource_name, recall: "#{controller_path}#new" }
+ { scope: resource_name, recall: "#{controller_path}#new", locale: I18n.locale }
end
def translation_scope
diff --git a/lib/devise/controllers/helpers.rb b/lib/devise/controllers/helpers.rb
index bc6e9fd865..68e8e8d1d6 100644
--- a/lib/devise/controllers/helpers.rb
+++ b/lib/devise/controllers/helpers.rb
@@ -46,6 +46,7 @@ def authenticate_#{group_name}!(favorite = nil, opts = {})
mappings.unshift mappings.delete(favorite.to_sym) if favorite
mappings.each do |mapping|
opts[:scope] = mapping
+ opts[:locale] = I18n.locale
warden.authenticate!(opts) if !devise_controller? || opts.delete(:force)
end
end
@@ -115,6 +116,7 @@ def self.define_helpers(mapping) #:nodoc:
class_eval <<-METHODS, __FILE__, __LINE__ + 1
def authenticate_#{mapping}!(opts = {})
opts[:scope] = :#{mapping}
+ opts[:locale] = I18n.locale
warden.authenticate!(opts) if !devise_controller? || opts.delete(:force)
end
diff --git a/lib/devise/failure_app.rb b/lib/devise/failure_app.rb
index 8458aef327..ff3363fbc0 100644
--- a/lib/devise/failure_app.rb
+++ b/lib/devise/failure_app.rb
@@ -18,6 +18,11 @@ class FailureApp < ActionController::Metal
delegate :flash, to: :request
+ include AbstractController::Callbacks
+ around_action do |failure_app, action|
+ I18n.with_locale(failure_app.i18n_locale, &action)
+ end
+
def self.call(env)
@respond ||= action(:respond)
@respond.call(env)
@@ -107,7 +112,7 @@ def i18n_message(default = nil)
options[:default] = [message]
auth_keys = scope_class.authentication_keys
keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key) }
- options[:authentication_keys] = keys.join(I18n.translate(:"support.array.words_connector"))
+ options[:authentication_keys] = keys.join(I18n.t(:"support.array.words_connector"))
options = i18n_options(options)
I18n.t(:"#{scope}.#{message}", **options)
@@ -116,6 +121,10 @@ def i18n_message(default = nil)
end
end
+ def i18n_locale
+ warden_options[:locale]
+ end
+
def redirect_url
if warden_message == :timeout
flash[:timedout] = true if is_flashing_format?
diff --git a/test/controllers/helpers_test.rb b/test/controllers/helpers_test.rb
index 655a1fb661..57acdba9c3 100644
--- a/test/controllers/helpers_test.rb
+++ b/test/controllers/helpers_test.rb
@@ -64,30 +64,30 @@ def setup
end
test 'proxy authenticate_user! to authenticate with user scope' do
- @mock_warden.expects(:authenticate!).with({ scope: :user })
+ @mock_warden.expects(:authenticate!).with({ scope: :user, locale: :en })
@controller.authenticate_user!
end
test 'proxy authenticate_user! options to authenticate with user scope' do
- @mock_warden.expects(:authenticate!).with({ scope: :user, recall: "foo" })
+ @mock_warden.expects(:authenticate!).with({ scope: :user, recall: "foo", locale: :en })
@controller.authenticate_user!(recall: "foo")
end
test 'proxy authenticate_admin! to authenticate with admin scope' do
- @mock_warden.expects(:authenticate!).with({ scope: :admin })
+ @mock_warden.expects(:authenticate!).with({ scope: :admin, locale: :en })
@controller.authenticate_admin!
end
test 'proxy authenticate_[group]! to authenticate!? with each scope' do
[:user, :admin].each do |scope|
- @mock_warden.expects(:authenticate!).with({ scope: scope })
+ @mock_warden.expects(:authenticate!).with({ scope: scope, locale: :en })
@mock_warden.expects(:authenticate?).with(scope: scope).returns(false)
end
@controller.authenticate_commenter!
end
test 'proxy authenticate_publisher_account! to authenticate with namespaced publisher account scope' do
- @mock_warden.expects(:authenticate!).with({ scope: :publisher_account })
+ @mock_warden.expects(:authenticate!).with({ scope: :publisher_account, locale: :en })
@controller.authenticate_publisher_account!
end
diff --git a/test/failure_app_test.rb b/test/failure_app_test.rb
index 59f291e204..e8f316f0db 100644
--- a/test/failure_app_test.rb
+++ b/test/failure_app_test.rb
@@ -200,6 +200,13 @@ def call_failure(env_params = {})
assert_equal 'User Steve does not exist', @request.flash[:alert]
end
+ test 'respects the i18n locale passed via warden options when redirecting' do
+ call_failure('warden' => OpenStruct.new(message: :invalid), 'warden.options' => { locale: :"pt-BR" })
+
+ assert_equal 'Email ou senha inválidos.', @request.flash[:alert]
+ assert_equal 'http://test.host/users/sign_in', @response.second["Location"]
+ end
+
test 'uses the proxy failure message as string' do
call_failure('warden' => OpenStruct.new(message: 'Hello world'))
assert_equal 'Hello world', @request.flash[:alert]
@@ -284,6 +291,12 @@ def call_failure(env_params = {})
assert_match '