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
def accepted_languages()
return [] if request.env["HTTP_ACCEPT_LANGUAGE"].nil?
accepted = request.env["HTTP_ACCEPT_LANGUAGE"].split(",")
accepted = accepted.map { |l| l.strip.split(";") }
accepted = accepted.map { |l|
if (l.size == 2)
[ l[0].split("-")[0].downcase, l[1].sub(/^q=/, "").to_f ]
else
[ l[0].split("-")[0].downcase, 1.0 ]
end
}
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
- 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.
- 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
def preferred_language(supported_languages=[], default_language="en")
preferred_languages = accepted_languages.select {|l|
(supported_languages || []).include?(l[0]) }
if preferred_languages.empty?
default_language
else
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 !