class Middleware
Selects a locale for each request and rewrites localized paths.
Definitions
def initialize(app, locales:, default_locale: nil, default_locales: nil, hosts: {}, ignore: [])
Implementation
def initialize(app, locales:, default_locale: nil, default_locales: nil, hosts: {}, ignore: [])
@app = app
@all_locales = HTTP::Accept::Languages::Locales.new(locales)
# Locales here are represented as an array of strings, e.g. ['en', 'ja', 'cn', 'de'] and are used in order if no locale is specified by the user.
unless @default_locales = default_locales
if default_locale
@default_locales = [default_locale, nil]
else
# We append nil, i.e. no localization.
@default_locales = @all_locales.names + [nil]
end
end
@default_locale = default_locale || @default_locales.first
unless @default_locales.include? @default_locale
@default_locales.unshift(@default_locale)
end
# Select a localization based on a request host name:
@hosts = hosts
@ignore = ignore || options[:nonlocalized]
@methods = methods
end
def freeze
Freeze this object and its internal state.
Signature
-
returns
self This object.
Implementation
def freeze
return self if frozen?
@all_locales.freeze
@default_locales.freeze
@default_locale.freeze
@hosts.freeze
@ignore.freeze
super
end
def preferred_locales(env)
Compute the preferred locales for the Rack environment.
Signature
-
parameter
envHash The Rack environment.
-
yields
{|env| ...} Each localized environment in preference order.
-
returns
Enumerator | Array An enumerator when no block is given, otherwise the configured default locales.
Implementation
def preferred_locales(env)
return to_enum(:preferred_locales, env) unless block_given?
# Keep track of what locales have been tried:
locales = Set.new
host_preferred_locales(env) do |locale|
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
end
request_preferred_locale(env) do |locale, path|
# We have extracted a locale from the path, so from this point on we should use the updated path:
env = env.merge(Rack::PATH_INFO => path.to_s)
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
end
browser_preferred_locales(env).each do |locale|
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
end
@default_locales.each do |locale|
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
end
end
def host_preferred_locales(env)
Infer preferred locales from the request host.
Signature
-
parameter
envHash The Rack environment.
-
yields
{|locale| ...} Each locale whose host pattern matches.
-
returns
Hash The configured host mappings.
Implementation
def host_preferred_locales(env)
http_host = env[Rack::HTTP_HOST]
# Yield all hosts which match the incoming http_host:
@hosts.each do |pattern, locale|
yield locale if http_host[pattern]
end
end
def request_preferred_locale(env)
Select the preferred locale from the request path.
Signature
-
parameter
envHash The Rack environment.
-
yields
{|locale, path| ...} The locale and path without its locale prefix.
-
returns
Object | Nil The block result when a locale prefix is present.
Implementation
def request_preferred_locale(env)
path = Path[env[Rack::PATH_INFO]]
if request_locale = @all_locales.patterns[path.first]
# Remove the localization prefix:
path.delete_at(0)
yield request_locale, path
end
end
def browser_preferred_locales(env)
Parse the locales preferred by the browser.
Signature
-
parameter
envHash The Rack environment.
-
returns
Array(String) Supported locales in preference order.
Implementation
def browser_preferred_locales(env)
accept_languages = env[HTTP_ACCEPT_LANGUAGE]
# No user prefered languages:
return [] unless accept_languages
# Extract the ordered list of languages:
languages = HTTP::Accept::Languages.parse(accept_languages)
# Returns available languages based on the order languages:
return @all_locales & languages
rescue HTTP::Accept::ParseError
# If we fail to parse the browser Accept-Language header, we ignore it (silently).
return []
end
def localized?(env)
Check whether the request path is eligible for localization.
Signature
-
parameter
envHash The Rack environment.
-
returns
Boolean Whether the path is eligible for localization.
Implementation
def localized?(env)
# Ignore requests which match the ignored paths:
path_info = env[Rack::PATH_INFO]
return false if @ignore.any?{|pattern| path_info[pattern] != nil}
return true
end
def vary(env, response)
Set the Vary: header on the response to indicate that this response should include the header in the cache key.
Implementation
def vary(env, response)
headers = response[1].to_a
# This response was based on the Accept-Language header:
headers << ["Vary", "Accept-Language"]
# Althought this header is generally not supported, we supply it anyway as it is useful for debugging:
if locale = env[CURRENT_LOCALE_KEY]
# Set the Content-Location to point to the localized URI as requested:
headers["Content-Location"] = "/#{locale}" + env[Rack::PATH_INFO]
end
return response
end
def call(env)
Try the preferred locales until the application returns a successful response.
Signature
-
parameter
envHash The Rack environment.
-
returns
Array The localized Rack response.
Implementation
def call(env)
# Pass the request through if it shouldn't be localized:
return @app.call(env) unless localized?(env)
env[LOCALIZATION_KEY] = self
response = nil
# We have a non-localized request, but there might be a localized resource. We return the best localization possible:
preferred_locales(env) do |localized_env|
# puts "Trying locale: #{localized_env[CURRENT_LOCALE_KEY]}: #{localized_env[Rack::PATH_INFO]}..."
response = @app.call(localized_env)
break unless response[0] >= 400
response[2].close if response[2].respond_to?(:close)
end
return vary(env, response)
end