为什么 Rails 中的区域设置充当全局(使用 Thin 时)?

发布于 2025-01-05 10:09:17 字数 948 浏览 1 评论 0原文

我刚刚意识到在控制器中设置区域设置的推荐 Rails 方法会

before_filter :set_locale

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

全局设置区域设置。上面的代码可以工作,但我想知道如果您必须显式键入它, default_locale 真的是默认的吗?

我期望的是每个请求都有一个区域设置(就像每个请求都有会话一样)并执行以下操作:

def set_locale
  locale = params[:locale] if params[:locale]
end

并默认使用 I18n.default_locale 否则。这将理想地匹配路径中的可选区域设置:

# config/routes.rb
scope "(:locale)", :locale => /en|nl/ do
  resources :books
end

现在,如果由于某种原因我在某些操作中跳过区域设置,它会使用上一个请求中设置的区域设置,该请求可能来自另一个用户!

是否存在潜在的竞争条件,因为一个请求可以更改全局 I18n.locale 而另一个请求(事先设置了另一个区域设置)正在渲染过程中?


更新:我现在从 I18n 文档中找到了一些详细信息:

伪全局设置当前语言环境,即在 Thread.current 哈希中 def locale=(区域设置)

现在我想了解是否每个请求都是一个单独的线程。


更新2:请参阅我的答案以获取解释。

I just realized that the recommended Rails way to set locale in your controller

before_filter :set_locale

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

sets the locale globally. The code above works, but I wonder is default_locale really default if you have to type it explicitly?

What I'd expect is to have a locale per request (like we have session per request) and doing something like:

def set_locale
  locale = params[:locale] if params[:locale]
end

And having I18n.default_locale used by default otherwise. This would match ideally the optional locale in path:

# config/routes.rb
scope "(:locale)", :locale => /en|nl/ do
  resources :books
end

For now if for some reason I skip locale setting in some action it uses the locale set in the previous request which could be from another user!

And isn't there a potential race condition as one request can change global I18n.locale while another request (having set another locale beforehande) is in the middle of rendering?


UPDATE: Some details I found for now, from the I18n documentstion:

Sets the current locale pseudo-globally, i.e. in the Thread.current hash
def locale=(locale)

Now I want to understand if every request is a separate thread.


UPDATE 2: See my answer for explanation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

甜味拾荒者 2025-01-12 10:09:17

那么现在最终的答案。 TL;DR 仅当您使用线程 Web 服务器(例如 Thin 和 Puma)时,设置区域设置才充当全局设置。

正如我提到的,I18n.locale=

伪全局设置当前语言环境,即在 Thread.current 哈希中

因此它应该是针对每个请求的,并且在 Webrick 和 Unicorn 中以这种方式工作。

但是,如果您使用像 Thin 或 Puma 这样的线程 Web 服务器,线程的寿命似乎会更长,并且该值会保留以供将来的请求使用,直到它被显式更改为止。我从新的 Steve Klabnik 的 gem request_store 了解到它:

如果您需要全局状态,您可能已经使用了 Thread.current。

<...>

所以人们正在使用那些奇特的线程网络服务器,比如 Thin 或 Puma。但如果您使用 Thread.current,并且使用其中一台服务器,请小心!值的保留时间可能比您预期的要长,这可能会导致错误。

So now the final answer. TL;DR Setting locale acts as global only when you use threaded web servers, like Thin and Puma.

As I mentioned, I18n.locale=

Sets the current locale pseudo-globally, i.e. in the Thread.current hash

So it is supposed to be per-request, and it works this way in Webrick and Unicorn.

But if you use threaded web server like Thin or Puma, seems that the thread lives longer, and the value is preserved for future requests, until it is changed explicitly. Where I learned it is from the new Steve Klabnik's gem request_store:

If you need global state, you've probably reached for Thread.current.

<...>

So people are using those fancy threaded web servers, like Thin or Puma. But if you use Thread.current, and you use one of those servers, watch out! Values can stick around longer than you'd expect, and this can cause bugs.

戴着白色围巾的女孩 2025-01-12 10:09:17

上面推荐的代码不会全局设置区域设置,而是根据请求进行设置。

before_filter :set_locale

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

代码通常放置在 BaseController 中,因此在渲染每个页面之前会触发并设置它。不存在竞争条件,因为每个页面都会触发此代码,并且将在那里计算 I18n 区域设置。您可以将其扩展为查找用户区域设置、会话区域设置、请求参数、使用英语。

def set_locale
  I18n.locale = @user.locale || session[:locale] || params[:locale] || :en
end

换句话说,如果您在一页上设置本地,假设在家庭控制器中设置为德语,然后进入仪表板控制器,您将看到默认语言(英语)。因为变化不是全球性的。这就是代码放置在基本控制器中的原因。希望这是有道理的。

Recommended code from above does not set locale globally it sets it by request.

before_filter :set_locale

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

Code is usually place in BaseController so before each page is render it is triggered and set. There is no race conditions since every page will trigger this code and I18n locale will be calculated there. You can expand this to let's say looks for users locale, than session locale, than request params, than uses English.

def set_locale
  I18n.locale = @user.locale || session[:locale] || params[:locale] || :en
end

In other words if you set local on one page let's say in home controller to german and got to dashboard controller you will see default language (english). Since change is not global. That is why code is placed in base controller. Hope it makes sense.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文