Class: Utopia::Localization::Middleware

Inherits:
Protocol::HTTP::Middleware
  • Object
show all
Defined in:
lib/utopia/localization/middleware.rb

Overview

Computes localization preferences and rewrites locale-prefixed paths.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, locales:, default_locale: nil, default_locales: nil, hosts: {}, ignore: []) ⇒ Middleware

Returns a new instance of Middleware.

Parameters:

  • locales (Array<String>)

    An array of all supported locales.

  • default_locale (String) (defaults to: nil)

    The default locale if none is provided.

  • default_locales (Array<String | Nil>) (defaults to: nil)

    The locales to try in order if none is provided.

  • hosts (Hash<Pattern, String>) (defaults to: {})

    Specify a mapping of request hosts to locales.

  • ignore (Array<Pattern>) (defaults to: [])

    A list of patterns matched against request paths which will not be localized.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/utopia/localization/middleware.rb', line 24

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

Instance Attribute Details

#all_localesObject (readonly)

Returns the value of attribute all_locales.



65
66
67
# File 'lib/utopia/localization/middleware.rb', line 65

def all_locales
  @all_locales
end

#default_localeObject (readonly)

Returns the value of attribute default_locale.



66
67
68
# File 'lib/utopia/localization/middleware.rb', line 66

def default_locale
  @default_locale
end

Instance Method Details

#browser_preferred_locales(request) ⇒ Object

Parse the locales preferred by the browser.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/utopia/localization/middleware.rb', line 147

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

#call(request) ⇒ Object

Attach localization preferences and invoke the application once.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/utopia/localization/middleware.rb', line 190

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

#extract_path_locale(request) ⇒ Object

Extract a locale prefix from the request path.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/utopia/localization/middleware.rb', line 113

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

#freezeObject

Freeze this object and its internal state.



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/utopia/localization/middleware.rb', line 53

def freeze
	return self if frozen?
	
	@all_locales.freeze
	@default_locales.freeze
	@default_locale.freeze
	@hosts.freeze
	@ignore.freeze
	
	super
end

#host_preferred_locales(request) ⇒ Object

Infer preferred locales from the request authority.



99
100
101
102
103
104
105
106
107
108
# File 'lib/utopia/localization/middleware.rb', line 99

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

#localized?(request) ⇒ Boolean

Check whether the request path includes a locale.

Returns:

  • (Boolean)


166
167
168
169
170
171
172
# File 'lib/utopia/localization/middleware.rb', line 166

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

#preferred_locales(request, path_locale = nil) ⇒ Object

Compute the preferred locales for a request.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/utopia/localization/middleware.rb', line 72

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

#vary(response) ⇒ Object

Mark the response as varying by language.



177
178
179
180
181
182
183
184
185
# File 'lib/utopia/localization/middleware.rb', line 177

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