Detect browser language in Rails

后端 未结 5 649
一个人的身影
一个人的身影 2020-12-30 01:04

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

相关标签:
5条回答
  • 2020-12-30 01:17

    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...

    0 讨论(0)
  • 2020-12-30 01:20

    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.

    0 讨论(0)
  • 2020-12-30 01:21

    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.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
    

    I hope it can help.

    0 讨论(0)
  • 2020-12-30 01:27

    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.

    0 讨论(0)
  • 2020-12-30 01:31

    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
            :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
    

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

    0 讨论(0)
提交回复
热议问题