devise: Devise uses the wrong locale for some flash messages

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.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 11
  • Comments: 20 (9 by maintainers)

Commits related to this issue

Most upvoted comments

I am working on a new Rails app, I just installed devise-i18n and generated :en and :fr views.

This is my application_controller.rb, taken from Rails i18n API guide, for which error messages seem to be sent with the default locale:

class ApplicationController < ActionController::Base
  around_action :switch_locale

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

  def default_url_options
    { locale: I18n.locale }
  end
end

This is the application_controller.rb that seems to solve the problem:

class ApplicationController < ActionController::Base
  before_action :switch_locale

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

  def default_url_options
    { locale: I18n.locale }
  end
end

I am very new to i18n, but my current feelings are:

  • having an error message in the wrong language (or any message at all) is really bad UX
  • not using with_locale could lead to leakage issues

What should I do? What is the Rails/Ruby/Devise/i18n way to handle this?

@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.

@pil0u using I18n.with_locale is definitely the ideal here for the long run, but I believe using I18n.locale = shouldn’t be a problem if it’s always set before requests. See this paragraph from the I18n Guide:

I18n.locale can leak into subsequent requests served by the same thread/process if it is not consistently set in every controller.

You could try my branch: https://github.com/heartcombo/devise/compare/ca-fix-i18n-locale-failure-app (which I haven’t been able to wrap up, but just updated it on top of current master), and see if that works for you while still using I18n.with_locale as recommended. I’ll try to work on it next, see if I can get all the tests in place to push a fix for this to master.

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

cool, seems like im not the only one haha

@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.

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.