class Rack::Locale

Public Class Methods

new(app) click to toggle source
  # File lib/rack/contrib/locale.rb
7 def initialize(app)
8   @app = app
9 end

Public Instance Methods

call(env) click to toggle source
   # File lib/rack/contrib/locale.rb
11 def call(env)
12   locale_to_restore = I18n.locale
13 
14   locale = user_preferred_locale(env["HTTP_ACCEPT_LANGUAGE"])
15   locale ||= I18n.default_locale
16 
17   env['rack.locale'] = I18n.locale = locale.to_s
18   status, headers, body = @app.call(env)
19   headers = Utils::HeaderHash.new(headers)
20 
21   unless headers['Content-Language']
22     headers['Content-Language'] = locale.to_s
23   end
24 
25   [status, headers, body]
26 ensure
27   I18n.locale = locale_to_restore
28 end

Private Instance Methods

match?(s1, s2) click to toggle source
   # File lib/rack/contrib/locale.rb
80 def match?(s1, s2)
81   s1.to_s.casecmp(s2.to_s) == 0
82 end
user_preferred_locale(header) click to toggle source

Accept-Language header is covered mainly by RFC 7231 tools.ietf.org/html/rfc7231

Related sections:

There is an obsolete RFC 2616 (tools.ietf.org/html/rfc2616)

Edge cases:

  • Value can be a comma separated list with optional whitespaces: Accept-Language: da, en-gb;q=0.8, en;q=0.7

  • Quality value can contain optional whitespaces as well: Accept-Language: ru-UA, ru; q=0.8, uk; q=0.6, en-US; q=0.4, en; q=0.2

  • Quality prefix 'q=' can be in upper case (Q=)

  • Ignore case when match locale with I18n available locales

   # File lib/rack/contrib/locale.rb
55 def user_preferred_locale(header)
56   return if header.nil?
57 
58   locales = header.gsub(/\s+/, '').split(",").map do |language_tag|
59     locale, quality = language_tag.split(/;q=/i)
60     quality = quality ? quality.to_f : 1.0
61     [locale, quality]
62   end.reject do |(locale, quality)|
63     locale == '*' || quality == 0
64   end.sort_by do |(_, quality)|
65     quality
66   end.map(&:first)
67 
68   return if locales.empty?
69 
70   if I18n.enforce_available_locales
71     locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| match?(al, locale) } }
72     if locale
73       I18n.available_locales.find { |al| match?(al, locale) }
74     end
75   else
76     locales.last
77   end
78 end