在Rails中检测浏览器语言

23

如何在RoR中侦测用户在浏览器中设置的语言? 我将有一个选择框供用户随时使用,但我希望默认为他们的浏览器语言。

5个回答

26
这是一个旧问题,但我作为寻找答案的灵魂之一遇到了它,唯一可用的答案是一个没有任何上下文的链接。因此,以下是更深入的解释,基于我的进一步挖掘。

访问 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"]

我开始阅读this gist,但最终对其进行了相当大的修改。特别是正则表达式会丢弃完好的次要语言环境,例如en-AU变成了enzh-TW变成了zh。我尝试通过修改该正则表达式来纠正这一问题。

不太易读,我更倾向于使用并调整Rails文档中的示例。 - Stéphane Bruckert
@StéphaneBruckert,你能具体说明我如何改进这个答案吗?(顺便说一句,我刚刚看了一下,是的 - Rails文档中关于浏览器语言的部分自从第一次编写以来确实有了很大的改进!) - Kimball
不用担心,看起来是正确的,只是代码看起来不像文档中那么简单。但仍然是一个好答案! - Stéphane Bruckert
这很公平。增加的复杂性来自于保留和排序所有浏览器提供的语言环境,包括次要的语言环境信息。有些应用程序需要额外的信息,有些则不需要。 - Kimball

22

这是解决方案

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

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

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

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

您还可以为应用程序的用户提供设置(并可能覆盖)区域设置的手段,如此等...


7

这是一个非常老的问题,但我想提一下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-an-implied-locale


1
很好,谢谢。我认为可以稍作改进:使用 I18n.default_locale 而不是 :en - Rorrim
1
@Rorrim 谢谢!我更新了代码。使用 I18n.default_locale 而不是硬编码语言是个好主意。 - Michał Zalewski

7

昨晚我完成了这个小巧的宝石: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

我希望这可以有所帮助。


3

这里有一个经过充分测试的 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 是可用本地化集合的一个子集,根据用户偏好、可用本地化以及 RFC 推荐进行选择。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接