Detect browser language in Rails

后端 未结 5 671
一个人的身影
一个人的身影 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: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.

提交回复
热议问题