Devise: Devise uses the wrong locale for some flash messages

Created on 26 May 2020  路  13Comments  路  Source: heartcombo/devise

Environment

  • Ruby 2.6
  • Rails 6.0
  • Devise 4.7

Current behavior

Currently I use this code as suggested by the Rails guide:

  # application_controller.rb

  around_action :switch_locale

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action)
  end

This works perfectly for the application... but not for all Devise messages! Some are translated, others are not. Devise keeps using the default language for some messages (even if the translated YML files are present).

Possibly related:

145

https://github.com/heartcombo/devise/issues/5246

In order to reproduce the issue:

  1. try login with an invalid password
  2. you get an error message (flash) in the wrong language (i.e. Devise seems to use the default_locale set in the environment files, not the current one set in application controller)

If you log in successfully, instead, the flash message is in the correct language (the language set in application controller).

I have also tried to copy the official YML file from Devise and translate it, but I have the same issue: some translations appear, other do not.

Expected behavior

Devise should always use the current locale.

Most helpful comment

@sam-kim I have a WIP/possible fix here: https://github.com/heartcombo/devise/compare/ca-fix-i18n-locale-failure-app, haven't been able to wrap it up yet, but if you'd like to give it a shot let me know how it goes. Other than that, no ideas off the top of my head.

All 13 comments

cool, seems like im not the only one haha

I think it's a pretty common issue unfortunately. See https://github.com/heartcombo/devise/issues/3052#issuecomment-49881222

I think the best solution is to set the locale in a middleware rather than in the application controller, and also ensure that this middleware is loaded before warden, so it sets the locale before the warden middleware sets any flash messages. https://github.com/heartcombo/devise/issues/4823#issuecomment-388112897

The Rails guides were updated to use around_action but it wasn't for threadsafety. There's discussion here about it: https://github.com/rails/rails/pull/34356

It's safe to use:

before_action :set_locale

def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end

I'm having this same problem. First I tried before_action :set_locale which seemed to work for a while, but then stopped working, I'm unsure why. Changing it to prepend_before_action :set_locale works for the time being, but I'm uncertain whether this is the final or correct way to solve the problem. The middleware is in my case not an issue, as the Locale middleware is not used.

Update: it seems as if prepend_before_action is the correct solution. The actions without prepend are in this order:

verify_signed_out_user
allow_params_authentication!
require_no_authentication
assert_is_devise_resource!
75720
set_xhr_redirected_to
set_request_method_cookie
verify_authenticity_token
staging_basic_auth
set_gon_controller
ensure_signup_complete
check_terms_agreement
clear_search_mode
configure_permitted_parameters
set_locale

, several of which are devise related, and will by default be executed before set_locale.

@dssjoblom that's right, you need to make sure set_locale is called before anything Devise related there, as it may set a flash message by that time. So prepend_before_action or around_action should generally be preferable.

That said, the issue originally described here with I18n.with_locale exists, I have a possible reproduction and fix in the works (it happens because I18n is set back to the default locale before leaving the controller, and Devise's failure app runs afterwards so it has no awareness of the I18n locale set by the controller.). For the time being, using I18n.locale = should work.

@carlosantoniodasilva Do you have any suggestions other than using I18n.locale=? We would like to keep using I18n.with_locale to avoid any leak issues and may go the route of customizing the devise failure app but wanted to check to see if there was something in the works here.

@sam-kim I have a WIP/possible fix here: https://github.com/heartcombo/devise/compare/ca-fix-i18n-locale-failure-app, haven't been able to wrap it up yet, but if you'd like to give it a shot let me know how it goes. Other than that, no ideas off the top of my head.

Looks like that worked perfectly! Thank you for that and the quick response 馃槂

Cool, thanks for confirming. I'll try to wrap that up and push to master soon.

@carlosantoniodasilva I tried the above fix.
But I think there are two more things that need to be fixed.

https://github.com/heartcombo/devise/blob/45b831c4ea5a35914037bd27fe88b76d7b3683a4/lib/devise/failure_app.rb#L105

human_attribute_name(key, locale: warden_options[:locale])

https://github.com/heartcombo/devise/blob/45b831c4ea5a35914037bd27fe88b76d7b3683a4/lib/devise/hooks/activatable.rb#L10

throw :warden, scope: scope, message: record.inactive_message, locale: locale: I18n.locale

Thanks @unosk! Definitely agree on the first one, I was actually thinking about wrapping the failure app with I18n.with_locale, although that might be a bit more of a breaking change I guess for people using I18n, but perhaps better overall. (and maybe even more expected.)

I'll have to check on the second one, but at a glance seems reasonable. :+1:

Cross posting https://github.com/heartcombo/devise/commit/70828da732afbd9085c44676eb65502d45ab8d8c#commitcomment-46435559 for my future reference:

Thank you! I'm using Rails 6.0 with Devise 4.7.3 and I introduced I18n as it is recommended in the Rails I18n guide, with around_action :switch_locale in ApplicationController. I also followed the advice from https://github.com/heartcombo/devise/wiki/How-To:--Redirect-with-locale-after-authentication-failure .

Excerpt from routes:
image

The i18n always works fine when a route is called directly, no matter whether a user is logged-in, not logged in, or visiting the login page. See this set of tests:
i18n.feature.txt

The only scenario that fails is "Locale persists after Devise redirects as Forbidden", so when a non-logged in user tries to access a page that is limited to logged-in users and Devise redirects to the login page.

After some tinkering today, namely inserting

locale = helpers.get_locale_from_path(session[:user_return_to])
I18n.locale = locale if locale

into SessionsController 'new' method, the login page displays in the correct language, too, but the flash message "You need to sign in or sign up before continuing." (line 21 of the test file) still shows in the wrong language. And on the whole this solution seems quite hacky - shouldn't Devise support i18n out of the box??

Was this page helpful?
0 / 5 - 0 ratings