This PR contains the latest updates from the generic Website template, which have been added while working on #loud-amsterdam. Reviewed-on: #1 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
80 lines
3.1 KiB
Swift
80 lines
3.1 KiB
Swift
import Foundation
|
|
import HTTPTypes
|
|
import Hummingbird
|
|
import Localization
|
|
|
|
/// Resolves the visitor's preferred language and records it on the request context.
|
|
///
|
|
/// Placed ahead of the localized responders in the middleware chain, it reads the request's `lang` query parameter, its path, and its
|
|
/// `Accept-Language` header, negotiates the best supported match (falling back to the default language), and stores it on the context's
|
|
/// ``LocalizedRequestContext/language``.
|
|
///
|
|
/// The query parameter is a deliberate override; failing that, a leading path segment naming a supported language pins it, so an unrouted path under
|
|
/// a language's prefix — its not-found page — answers in that language. Values naming no supported language are ignored, leaving the header. The
|
|
/// request is passed through untouched — the path and routing are unaffected.
|
|
///
|
|
/// A site whose page routes pin their language by URL consults this only for responses that belong to no URL — in practice, its not-found page.
|
|
public struct LocalizationMiddleware<Context: LocalizedRequestContext> {
|
|
|
|
// MARK: Properties
|
|
|
|
/// Negotiates the request's language from its `Accept-Language` header.
|
|
private let negotiate: Negotiate
|
|
|
|
// MARK: Initializers
|
|
|
|
/// Creates a localization middleware that negotiates against the given bundle's String Catalog languages.
|
|
/// - Parameter bundle: the bundle whose String Catalog names the supported languages.
|
|
public init(
|
|
bundle: Bundle
|
|
) {
|
|
self.negotiate = .init(bundle: bundle)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - RouterMiddleware
|
|
|
|
extension LocalizationMiddleware: RouterMiddleware {
|
|
|
|
// MARK: Functions
|
|
|
|
/// Negotiates the request's language and records it on the context before passing it down.
|
|
/// - Parameters:
|
|
/// - request: the incoming request.
|
|
/// - context: the context the request is resolved against.
|
|
/// - next: the next responder in the middleware chain.
|
|
/// - Returns: the downstream response.
|
|
/// - Throws: any error thrown downstream.
|
|
public func handle(
|
|
_ request: Request,
|
|
context: Context,
|
|
next: (Request, Context) async throws -> Response
|
|
) async throws -> Response {
|
|
var context = context
|
|
|
|
// The deliberate query override first; failing that, the leading path segment, so a language's whole URL
|
|
// prefix — routed or not — answers in its language.
|
|
let requested = request.uri.queryParameters[.Parameter.language].map(String.init)
|
|
?? request.uri.path.split(separator: "/").first.map(String.init)
|
|
|
|
context.language = negotiate(
|
|
requested: requested,
|
|
acceptLanguage: request.headers[.acceptLanguage]
|
|
)
|
|
|
|
return try await next(request, context)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Constants
|
|
|
|
private extension Substring {
|
|
/// A namespace for the query parameters the middleware reads.
|
|
enum Parameter {
|
|
/// The query parameter carrying an explicit language choice; the site's language switcher appends it to the current path.
|
|
static let language: Substring = "lang"
|
|
}
|
|
}
|