Parsing Accept-Language in Rails

Recently, I had to know which language is preferred by someone visiting the web site I was developing for. Hopefully, HTTP is well designed enough that you should not have to ask your visitors for this preference but can elegantly guess what it is by analyzing the Accept-Language HTTP request header.
This header is seamlessly sent to the HTTP server by your browser. It's clearly defined by the HTTP 1.1 specification if you want all the details. Briefly, the value is a "set of natural languages that are preferred as a response to the request.". Each language "may be given an associated quality value which represents an estimate of the user's preference for the languages specified by that range. The quality value defaults to 1."
To leverage this header, I simply wrote a "helper" method that I made available to all the controllers. I named this method accepted_languages and put it on the ApplicationController class (RAILS_ROOT/app/models/application.rb). It takes no parameter and returns an array where each element is a pair [language tag, quality], sorted by ascending quality.
Here's the code

class ApplicationController < ActionController::Base
  
  # ...
  
  ##
  # Returns the languages accepted by the visitors, sorted by quality
  # (order of preference).
  ##
  def accepted_languages()
    # no language accepted
    return [] if request.env["HTTP_ACCEPT_LANGUAGE"].nil?
    
    # parse Accept-Language
    accepted = request.env["HTTP_ACCEPT_LANGUAGE"].split(",")
    accepted = accepted.map { |l| l.strip.split(";") }
    accepted = accepted.map { |l|
      if (l.size == 2)
        # quality present
        [ l[0].split("-")[0].downcase, l[1].sub(/^q=/, "").to_f ]
      else
        # no quality specified =&gt; quality == 1
        [ l[0].split("-")[0].downcase, 1.0 ]
      end
    }
    
    # sort by quality
    accepted.sort { |l1, l2| l1[1] <=> l2[1] }
  end
end
Look at how you get the request header inside Rails. It's available from the environment attached to the request through a Ruby home-made key (HTTP_ACCEPT_LANGUAGE). This is due to how the HTTP headers are transmitted between the Rails framework and the HTTP server that stands upfront. This is directly derived from how the CGI standard Ruby library does for passing an HTTP request from a web server to a standalone program, here your Rails application.
Most of the time, your controllers only want to retain one language, the one presumably preferred by the visitor. Additionally, your application only supports a few languages. This is the context I had to fit into and I found useful to add a second method to the ApplicationController class.
This method, named preferred_language, takes 2 optional parameters

  1. an array that contains the list of language( tag)s supported by your application; it is used to filter out the set of languages accepted by the visitor.
  2. a default language (tag) that is returned to the caller when the browser does not expose any accepted languages or not a single language that is supported by the application; this second parameter represnts the default language of your application.
By default, the application supports any language but has a preference for english.
class ApplicationController < ActionController::Base
  
  # ...
  
  ##
  # Returns the language preferred by the user.
  ##
  def preferred_language(supported_languages=[], default_language="en")
    
    # only keep supported languages
    preferred_languages = accepted_languages.select {|l|
     (supported_languages || []).include?(l[0]) }
    
    if preferred_languages.empty?
      # the browser does accept any supported languages
      # => default to english
      default_language
    else
      # take the highest quality among accepted (and thus supported) languages
      preferred_languages.last[0]
    end
  end
end
If you want to reuse this piece of code (what it's meant for), please remember that it does not take care of country codes. As explained in the HTTP spec, each language tag starts with an ISO-639 language abbreviation and may be followed by an ISO-3166 country code. The latter is simply ignored by this code.
Finally, the parsing and exposure of all the HTTP headers are worth writing a Rails plug-in or a gem. I might do it one day unless someone points me at a similar contribution.


UPDATE: This morning, I found the HTTP Accept-Language plugin that does exactly what I describe hereby, quite similarly. The methods used for accessing the preferred language(s) are bound to the request object, which I admit is better. I'm quite sure this plugin did not exist when I initiated this development (some months ago).

UPDATE (2013/05/10): Naomi Kyoto suggested a much more concise and elegant code snippet for the accepted_languages method. I recommend to use her gist. Thanks for sharing !



24 comments

  1. Unknown  

    May 9, 2013 at 9:04 AM

    This comment has been removed by the author.
  2. Unknown  

    May 9, 2013 at 9:06 AM

    This comment has been removed by the author.
  3. Unknown  

    May 9, 2013 at 9:13 AM

    I would leave a highly superior solution to this problem but pasting code on your blog is a nightmare. I forfeit.

    here's a gist instead

  4. Laurent Farcy  

    May 10, 2013 at 9:27 AM

    Thanks for your comment Naomi. I agree your code is much more concise and elegant. And it does the job !

    I'd recommend everyone to go with your solution. And I'm about to make a second update to the post to suggest so.

    I can read you're not in favor of gems. Otherwise, I'd have suggested to initiate a new one on GitHub.

    Finally, it's great to see that a post written in 2008 can still get readers and, most importantly, constructive feedbacks.

  5. j michel  

    May 23, 2014 at 12:04 PM

    Great post and a good stuff for me to takeaway

  6. Bobb  

    May 23, 2014 at 1:19 PM

    Very Nice post!!thanks for sharing..

  7. Tejuteju  

    June 27, 2018 at 7:31 AM

    Excellent article. Keep upadating
    Ruby on Rails Online Course Bangalore

  8. cloudbeginners  

    August 17, 2021 at 4:30 PM

  9. cloudbeginners  

    August 18, 2021 at 4:00 PM

  10. Techystick  

    November 14, 2021 at 10:41 AM

  11. CodeKing  

    March 10, 2023 at 12:04 PM

    Thanks for sharing your valueable thoughts for us. I truly appreciate your efforts and I will be waiting for your further write ups thank you once again. We provide Website Design Services across the world at comfort price.

  12. Alex Gour  

    April 18, 2023 at 11:41 AM

    Informative blog.
    click here

  13. Anshu Dave  

    April 18, 2023 at 12:32 PM

    Excellent article lots to learn.
    click here

  14. Anshu Dave  

    April 18, 2023 at 12:58 PM

    Great stuff lots of takeaway from it.
    click here

  15. Robbin  

    April 19, 2023 at 9:13 AM

    Great Share...
    click here

  16. Nate Lamaro  

    April 24, 2023 at 12:52 PM

    Extensive information blog, as this one details and gives adequate information regarding the blog. Do follow here for more informations Find Here

  17. Tristan Repin  

    April 26, 2023 at 8:44 AM

    Had great time reading this blog, as its information being provided are very helpful. For additional information's Check Here

  18. Chris  

    April 28, 2023 at 3:13 PM

    Amazing write - Up, For more more informations Check Here

  19. Antil Sen  

    May 2, 2023 at 7:21 AM

    Amazing Information. For more click here

  20. Adarsh Jain  

    May 5, 2023 at 8:39 AM

    Excellent article .Lots to learn. For more information- Read Here

  21. Armaan  

    May 15, 2023 at 2:02 PM

    Great Content.For more such content-

    click here

  22. Anindya Dutta  

    May 16, 2023 at 11:21 AM

    This comment has been removed by the author.
  23. Ananya Dhar  

    May 16, 2023 at 11:25 AM

    Great learning. For more click here

  24. Amitkumar  

    April 20, 2024 at 8:14 AM

    Our Custom Solutions solutions make fast and reliable dedicated server and cloud server accessible to all making us the best server provider.

Recent Entries

Recent Comments

Ruby and Rails> Recommended