检测 Rails 中的浏览器语言

发布于 2024-11-30 13:48:49 字数 65 浏览 1 评论 0原文

如何检测浏览器中设置的用户语言(在 RoR 中)?我将有一个用户可以随时使用的选择框,但我想默认为他们的浏览器语言。

How do you detect the users language (in RoR) as set in the browser? I will have a select box the user can use at anytime, but I'd like to default to whatever their browser language is.

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

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

发布评论

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

评论(5

無處可尋 2024-12-07 13:48:49

这是一个老问题,但我在寻找答案时遇到了它,唯一可用的答案是没有任何上下文的链接。根据我随后的挖掘,这里有更深入的内容。

访问 Accept-Language 标头

查询相关控制器中的 request 对象:

request.env['HTTP_ACCEPT_LANGUAGE'] #returns nil or a string, e.g.:
# => "en-AU,en-US;q=0.7,en;q=0.3"

不过,这是简单的部分。为了在 Rails 中充分利用它,您需要解析该字符串(或者可能是 nil?)。

侧边栏:Accept-* 标头入门

因此,查看上面的示例字符串,某些语言具有“q 值” - 相对质量因子,介于 0 和 1 之间。较高的 q 值意味着客户端更喜欢该语言。缺少 q 值实际上是最高的 q 值 - 隐式 1.0(与上面字符串中的 en-AU 一样)。不过,有一点复杂——浏览器可能会向您发送 q 值为 0 的语言——我认为这意味着如果可能的话,您应该拒绝这些语言。

解析 Accept-Language 标头

记住这一点,我研究过几种不同但相似的方法,用于将这样的字符串解析为语言列表,并按它们的 q 值排序。使用简单的 Ruby:

# to_s to deal with possible nil value, since nil.to_s => ""
langs = request.env['HTTP_ACCEPT_LANGUAGE'].to_s.split(",").map do |lang| 
  l, q = lang.split(";q=")
  [l, (q || '1').to_f]
end
# => [["en-AU", 1.0], ["en-US", 0.7], ["en", 0.3]]

或者如果您精通正则表达式,您可以实现与上面相同的效果,并且可能同时改进我的正则表达式:

rx = /([A-Za-z]{2}(?:-[A-Za-z]{2})?)(?:;q=(1|0?\.[0-9]{1,3}))?/
langs = request.env['HTTP_ACCEPT_LANGUAGE'].to_s.scan(rx).map do |lang, q|
  [lang, (q || '1').to_f]
end

无论哪种方式,您都可以根据需要跟进,例如:

# return array of just languages, ordered by q-value
langs.sort_by(&:last).map(&:first).reverse
# => ["en-AU", "en-US", "en"]

我开始了我通过查看这个要点进行解析,但最终对其进行了相当大的修改。正则表达式尤其抛弃了完美的辅助语言环境,例如,en-AU 变成了 enzh-TW 变成了 zh >。我试图通过对该正则表达式的修改来纠正这个问题。

This is an old question, but I came across it as a soul in search of answers and the only available answer was a link without any context. So here's a bit more depth, based on my subsequent digging.

Accessing the Accept-Language header

Query the request object in the relevant controller:

request.env['HTTP_ACCEPT_LANGUAGE'] #returns nil or a string, e.g.:
# => "en-AU,en-US;q=0.7,en;q=0.3"

That's the easy part though. To make good use of it in Rails, you'll need to parse that string (or maybe it's nil?).

Sidebar: A primer on Accept-* headers

So, looking at the example string above, some languages have "q-values" - the relative quality factor, between 0 and 1. Higher q-values mean that language is preferred by the client. Lack of a q-value is really the highest q-value - an implicit 1.0 (as with en-AU in the above string). A slight complication though - the browser may send you languages with q-values that are, say, 0 - and I gather this means you should reject those languages if possible.

Parsing the Accept-Language header

Bearing that in mind, here's a couple of different yet similar approaches I've looked at for parsing such a string into a list of languages, ordered by their q-values. With straightforward Ruby:

# to_s to deal with possible nil value, since nil.to_s => ""
langs = request.env['HTTP_ACCEPT_LANGUAGE'].to_s.split(",").map do |lang| 
  l, q = lang.split(";q=")
  [l, (q || '1').to_f]
end
# => [["en-AU", 1.0], ["en-US", 0.7], ["en", 0.3]]

Or if you're proficient at regular expressions, you can achieve the same as above, and probably improve upon my butchered regex at the same time:

rx = /([A-Za-z]{2}(?:-[A-Za-z]{2})?)(?:;q=(1|0?\.[0-9]{1,3}))?/
langs = request.env['HTTP_ACCEPT_LANGUAGE'].to_s.scan(rx).map do |lang, q|
  [lang, (q || '1').to_f]
end

Either way, you can follow up as needed with something like:

# return array of just languages, ordered by q-value
langs.sort_by(&:last).map(&:first).reverse
# => ["en-AU", "en-US", "en"]

I started out my parsing by looking at this gist, but ended up modifying it fairly significantly. The regex especially was throwing away perfectly good secondary locales, e.g. en-AU became en, zh-TW became zh. I've attempted to rectify this with my modifications to that regex.

盗琴音 2024-12-07 13:48:49

解决方案如下

2.6 根据客户端提供的信息设置区域设置

在特定情况下,从客户端提供的信息(即不是从 URL)设置区域设置是有意义的。例如,此信息可能来自用户的首选语言(在浏览器中设置),可以基于从其 IP 推断出的用户地理位置,或者用户只需在应用程序界面中选择区域设置并保存即可提供该信息到他们的个人资料。这种方法更适合基于 Web 的应用程序或服务,而不是网站 - 请参阅上面关于会话cookie和 RESTful 架构的框...

客户端提供的信息来源之一是 Accept-Language HTTP 标头。人们可以将其设置为他们的浏览器或其他客户端(例如curl)...

从客户端信息选择区域设置的另一种方法是使用数据库将客户端 IP 映射到区域,例如 GeoIP Lite 国家/地区...

您还可以为应用程序的用户提供在应用程序界面中设置(并可能覆盖)区域设置的方法...

Here's the solution:

2.6 Setting the Locale from the Client Supplied Information

In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users’ preferred language (set in their browser), can be based on the users’ geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites — see the box about sessions, cookies and RESTful architecture above...

One source of client supplied information would be an Accept-Language HTTP header. People may set this in their browser or other clients (such as curl)...

Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as GeoIP Lite Country...

You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well...

云胡 2024-12-07 13:48:49

昨晚我做了这个小宝石: accept_language

它可以集成到 Rails 应用程序中,如下所示:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :best_locale_from_request!

  def best_locale_from_request!
    I18n.locale = best_locale_from_request
  end

  def best_locale_from_request
    return I18n.default_locale unless request.headers.key?("HTTP_ACCEPT_LANGUAGE")

    string = request.headers.fetch("HTTP_ACCEPT_LANGUAGE")
    locale = AcceptLanguage.parse(string).match(*I18n.available_locales)

    # If the server cannot serve any matching language,
    # it can theoretically send back a 406 (Not Acceptable) error code.
    # But, for a better user experience, this is rarely done and more
    # common way is to ignore the Accept-Language header in this case.
    return I18n.default_locale if locale.nil?

    locale
  end
end

我希望它能有所帮助。

Last night I did this tiny gem: accept_language

It can be integrated in a Rails app like that:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :best_locale_from_request!

  def best_locale_from_request!
    I18n.locale = best_locale_from_request
  end

  def best_locale_from_request
    return I18n.default_locale unless request.headers.key?("HTTP_ACCEPT_LANGUAGE")

    string = request.headers.fetch("HTTP_ACCEPT_LANGUAGE")
    locale = AcceptLanguage.parse(string).match(*I18n.available_locales)

    # If the server cannot serve any matching language,
    # it can theoretically send back a 406 (Not Acceptable) error code.
    # But, for a better user experience, this is rarely done and more
    # common way is to ignore the Accept-Language header in this case.
    return I18n.default_locale if locale.nil?

    locale
  end
end

I hope it can help.

合约呢 2024-12-07 13:48:49

这是一个非常老的问题,但我想提一下 Rails 指南有关于如何检测用户浏览器语言的描述。

下面的代码基于 Ruby on Rails 指南中的解决方案并进行了改进:

  • 检查是否存在 HTTP_ACCEPT_LANGUAGE - 有时机器人会缺少该标头,并且您将在错误报告工具中收到错误
  • 检查应用程序代码是否支持语言

  def set_locale_based_on_browser
    locale = extract_locale_from_accept_language_header

    I18n.locale =
      if locale_valid?(locale)
        locale
      else
        I18n.default_locale
      end
  end

  private

  def locale_valid?(locale)
    I18n.available_locales.map(&:to_s).include?(locale)
  end

  def extract_locale_from_accept_language_header
    accept_language = request.env['HTTP_ACCEPT_LANGUAGE']
    return unless accept_language

    accept_language.scan(/^[a-z]{2}/).first
  end
end

有关的更多信息区域设置检测可以在 Ruby on Rails 指南中找到:
https://guides.rubyonrails.org/i18n.html#choosing-隐含区域设置

This is a really old question, but I like to mention that Rails guide has a description of how to detect user browser language.

Below code is based on the solution from Ruby on Rails guide with improvements:

  • checks are there HTTP_ACCEPT_LANGUAGE - sometimes that header is missing for bots, and you will get errors in your error reporting tool
  • checks are language supported by your application

code:

  def set_locale_based_on_browser
    locale = extract_locale_from_accept_language_header

    I18n.locale =
      if locale_valid?(locale)
        locale
      else
        I18n.default_locale
      end
  end

  private

  def locale_valid?(locale)
    I18n.available_locales.map(&:to_s).include?(locale)
  end

  def extract_locale_from_accept_language_header
    accept_language = request.env['HTTP_ACCEPT_LANGUAGE']
    return unless accept_language

    accept_language.scan(/^[a-z]{2}/).first
  end
end

More information about locale detection can be found in Ruby on Rails guide:
https://guides.rubyonrails.org/i18n.html#choosing-an-implied-locale

内心荒芜 2024-12-07 13:48:49

这是一个经过充分测试的 Ruby gem,它完全可以满足您的需求: https://github.com/ioquatix/http -accept

languages = HTTP::Accept::Language.parse("da, en-gb;q=0.8, en;q=0.7")

expect(languages[0].locale).to be == "da"
expect(languages[1].locale).to be == "en-gb"
expect(languages[2].locale).to be == "en"

它对各种输入具有 100% 的测试覆盖率。它按照相关 RFC 指定的顺序返回语言。

此外,如果您尝试将用户语言与特定区域设置中可用的内容相匹配,则可以执行以下操作:

available_localizations = HTTP::Accept::Languages::Locales.new(["en-nz", "en-us"])

# Given the languages that the user wants, and the localizations available, compute the set of desired localizations.
desired_localizations = available_localizations & languages

上例中的desired_localizations是available_localizations的子集,根据用户首选项、可用本地化和RFC 建议。

Here is a well tested Ruby gem which does exactly what you want: https://github.com/ioquatix/http-accept

languages = HTTP::Accept::Language.parse("da, en-gb;q=0.8, en;q=0.7")

expect(languages[0].locale).to be == "da"
expect(languages[1].locale).to be == "en-gb"
expect(languages[2].locale).to be == "en"

It has 100% test coverage on a wide range of inputs. It returns the languages in the order specified by the relevant RFCs.

In addition, if you are trying to match user language to content which is available in a specific set of locales, you can do the following:

available_localizations = HTTP::Accept::Languages::Locales.new(["en-nz", "en-us"])

# Given the languages that the user wants, and the localizations available, compute the set of desired localizations.
desired_localizations = available_localizations & languages

The desired_localizations in the example above is a subset of available_localizations, according to user preference, available localisations, and RFC recommendations.

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