【问题标题】:Detect browser language in Rails在 Rails 中检测浏览器语言
【发布时间】:2011-10-30 03:14:21
【问题描述】:

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

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-3 locale


    【解决方案1】:

    这是一个老问题,但我在寻找答案时遇到了它,唯一可用的答案是没有任何上下文的链接。所以这里有一点深度,基于我随后的挖掘。

    访问 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 值”-relative quality factor,介于 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éphaneBruckert 您能否更具体地说明我如何改进这个答案? (附注:我只是看了看,是的——Rails 文档中关于浏览器语言的部分自从第一次编写以来已经有了很大的改进!)
    • 别担心,看起来是对的,只是代码看起来不像文档中的那么简单。还是不错的答案!
    • 这很公平。增加的复杂性来自保存和排序所有浏览器提供的语言环境,包括辅助语言环境信息。有些应用程序需要这些额外信息,有些则不需要。
    【解决方案2】:

    Here's the solution:

    2.6 从客户端提供的信息中设置语言环境

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

    客户端提供的信息的一个来源是 Accept-Language HTTP 标头。人们可能会set this in their browser 或其他客户(如curl)...

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

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

    【讨论】:

      【解决方案3】:

      昨晚我做了这个小宝石: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.intersection(string, I18n.default_locale, *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
      

      希望对你有帮助。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          这是一个经过良好测试的 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 建议。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-04-15
            • 1970-01-01
            • 2018-02-11
            • 1970-01-01
            • 2019-02-17
            • 1970-01-01
            • 2022-01-13
            • 1970-01-01
            相关资源
            最近更新 更多