route_translator: I18n.locale not available to middlewares when request completes
I’m currently having an issue with the route_translator & Devise combo. When a user accesses a localized route, the gem properly sets the locale (in a around_filter
method). If the user is not authenticated, Devise will issue a 401 and redirect to the sign-in page and also build a flash message. That logic is performed by a metal app called by the warden middleware.
Since route_translator remembers the previous I18n.locale and default_locale, and sets them back after the yield, the Devise middleware ends up with the wrong I18n.locale when the requets completes.
I hacked my application controller to make it work (see below). I was wondering if the around_filter
is really necessary in route_translator and if it could be replaced by a before_filter
. I think the locale should not go back to the previous value and the middleware(s) running afterward should have access to that value.
If you agree with the change, it will be a pleasure for me to submit a pull request. Thanks.
class ApplicationController < ActionController::Base
# HACK for route_translators
skip_around_filter :set_locale_from_url
before_filter :set_my_locale_from_url
def set_my_locale_from_url
tmp_default_locale = RouteTranslator::Host.locale_from_host(request.host)
if tmp_default_locale
current_default_locale = I18n.default_locale
I18n.default_locale = tmp_default_locale
end
tmp_locale = params[RouteTranslator.locale_param_key] || tmp_default_locale
if tmp_locale
current_locale = I18n.locale
I18n.locale = tmp_locale
end
end
end
About this issue
- Original URL
- State: open
- Created 9 years ago
- Reactions: 1
- Comments: 27 (1 by maintainers)
This is my current solution: https://gist.github.com/frahugo/705b0854126eb3ae7565 I basically push the “finale” locale (decided by ApplicationController) in the request environment, and pick it up later in the Devise middleware.
I agree. before_filter is not the way to go. I’ll see if I can find some time in the coming days to work on a PR/demo.
I’ve had a deep dive into this (trying to get Devise and route_translator to play nice).
The above solution works because it does set
I18n.default_locale
in addition toI18n.locale
, which is what will be picked up by Devise when deciding which URL helper method to call. If it is left to :en (the default), it will always callnew_user_session_en_url'
, which work somewhat ok if you do path or param based language detection, but break for host based links.The other thing is that the example above is not resetting I18n.default_locale once the controller is done, thus “leaking” it up the middleware chain.
This shouldn’t be a threading issue, as default_locale turns out to be thread local (ie, not shared across threeads).
However, it still isn’t a great solution, as it will set the locale and default locale for the next request as well (up to the point where the before_action triggers), which means that the locale will actually differ within the Warden middleware depending on whether it has called out to the application or not. This might easily cause a lot of headache in the future.
Improving @frahugo’s solution
As a side note, the code snippet given as solution above assigns a few local variables and such that are never actually used and could be shortened to:
Also note that in my example I’m using main locales (en and de) for routes, but also support more specific locales for page content. To make it pick up the right URL helpers I have to set the default locale to the generic locale (ie de for de-CH/de-DE).
This can be done with the following adjustment:
Moving things to a Middleware
Part of a proper solution is, in my opinion, to move the logic for setting the locale (and default locale) into a middleware:
Note that this differs slightly from the code I’m using, as I pick the specific locale from bothe the host and the Accept-Language header.
Why does Devise use
I18n.default_locale
?At first I just moved the locale logic into a middleware, without also setting
default_locale
(which leads to broken redirects), until I saw the above solution. Which got me wondering why Devise seems to useI18n.default_locale
. It doesn’t. Devise ends up calling something likenew_user_session_url
(this is happening inlib/devise/failure_app.rb
if anyone is following along), which gives control back to route translator, which in turn is falling back to default_locale.This could be avoided/improved if
route_name_for
would first tryI18n.fallbacks
instead of going straight toI18n.default_locale
.