class Middleware < Protocol::HTTP::Middleware
- Inherits from
Protocol::HTTP::Middleware
Computes localization preferences and rewrites locale-prefixed 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: [])
super(app)
@all_locales = 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
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(request, path_locale = nil)
Compute the preferred locales for a request.
Signature
-
parameter
requestUtopia::Request The application request.
-
parameter
path_localeString | Nil The locale extracted from the request path.
-
returns
Array(String | Nil) The unique locales in preference order.
Implementation
def preferred_locales(request, path_locale = nil)
# Keep track of what locales have been tried:
locales = Set.new
if path_locale
locales.add(path_locale)
end
host_preferred_locales(request) do |locale|
locales.add(locale)
end
browser_preferred_locales(request).each do |locale|
locales.add(locale)
end
@default_locales.each do |locale|
locales.add(locale)
end
return locales.to_a
end
def host_preferred_locales(request)
Infer preferred locales from the request authority.
Signature
-
parameter
requestUtopia::Request The application request.
-
yields
{|locale| ...} Each locale whose host pattern matches the request authority.
-
returns
Hash The configured host mappings.
Implementation
def host_preferred_locales(request)
authority = request.authority.to_s
# Yield all hosts which match the incoming authority:
@hosts.each do |pattern, locale|
if authority[pattern]
yield locale
end
end
end
def extract_path_locale(request)
Extract a locale prefix from the request path.
Signature
-
parameter
requestUtopia::Request The application request.
-
returns
Array(Utopia::Request, String | Nil) The request and extracted locale.
Implementation
def extract_path_locale(request)
path = request.url.path
# Localization prefixes only apply to absolute application paths:
unless path.absolute?
return request, nil
end
if segment = path.segments[1]
# Decode only the component which may contain the locale:
component = Protocol::URL::Encoding::System.unescape(segment)
if request_locale = @all_locales.patterns[component]
# Remove the locale while preserving all other encoded segments:
segments = path.segments.dup
segments.delete_at(1)
# Preserve the absolute root when the locale was the only component:
if segments == [""]
segments << ""
end
path = Protocol::URL::Path.new(nil, segments)
return request.with(path: path), request_locale
end
end
return request, nil
end
def browser_preferred_locales(request)
Parse the locales preferred by the browser.
Signature
-
parameter
requestUtopia::Request The application request.
-
returns
Array(String) Supported locales accepted by the browser, in preference order.
Implementation
def browser_preferred_locales(request)
accept_languages = request.headers["accept-language"]
# No user prefered languages:
return [] unless accept_languages
# Extract the ordered list of languages:
languages = accept_languages.preferred_languages
# Returns available languages based on the order languages:
return @all_locales.match(languages)
rescue Protocol::HTTP::Header::AcceptLanguage::ParseError
# If we fail to parse the browser Accept-Language header, we ignore it (silently).
return []
end
def localized?(request)
Check whether the request path includes a locale.
Signature
-
parameter
requestUtopia::Request The application request.
-
returns
Boolean Whether the path is eligible for localization.
Implementation
def localized?(request)
# Ignore requests which match the ignored paths:
path = request.url.path.encoded
return false if @ignore.any?{|pattern| path[pattern] != nil}
return true
end
def vary(response)
Mark the response as varying by language.
Signature
-
parameter
responseProtocol::HTTP::Response The response.
-
returns
Protocol::HTTP::Response The response with localization headers.
Implementation
def vary(response)
response = Response.wrap(response)
headers = response.headers
# This response was based on the Accept-Language header:
headers.add("vary", "Accept-Language")
return response
end
def call(request)
Attach localization preferences and invoke the application once.
Signature
-
parameter
requestUtopia::Request The request.
-
returns
Protocol::HTTP::Response The response with cache-variation headers.
Implementation
def call(request)
# Pass the request through if it shouldn't be localized:
return @delegate.call(request) unless localized?(request)
request, path_locale = extract_path_locale(request)
locales = preferred_locales(request, path_locale)
request.localization = Preferences.new(
all_locales: @all_locales.names,
preferred_locales: locales,
default_locale: @default_locale,
)
return vary(@delegate.call(request))
end