import Elementary import Foundation import HTTPTypes import Hummingbird import Localization /// A per-language collection of pre-rendered HTML responses. /// /// At initialization it renders the document once for each language the bundle's ``LanguageList`` /// reports and caches the bytes, mirroring ``CachedHTMLResponse``'s render-once model but keyed by /// language. Each cached response carries a `Content-Language` header and `Vary: Accept-Language`, so /// shared caches key on the negotiated language instead of serving one language to everyone. struct LocalizedHTMLCollectionResponse: Sendable { // MARK: Properties /// The supported languages and default language, derived from the module's String Catalog. private let list: LanguageList /// The pre-rendered responses, keyed by language identifier. private let responses: [String: CachedHTMLResponse] // MARK: Initializers /// Renders the document once per supported language. /// - Parameters: /// - status: the status applied to every response. Defaults to `.ok`. /// - document: builds the document to render for a given locale. init( status: HTTPResponse.Status = .ok, document: (Locale) -> Document ) { self.list = .init(bundle: .module) self.responses = list.all .reduce(into: [:]) { responses, language in responses[language] = CachedHTMLResponse( status: status, additionalHeaders: [ .contentLanguage: language, .vary: "Accept-Language", ], document: document(.init(identifier: language)) ) } } // MARK: Methods /// Builds the response for the given language, falling back to the default language. /// - Parameter language: the negotiated language identifier. /// - Returns: the cached response for the language, the default language's response when the /// language is unavailable, or a `500 Internal Server Error` if neither is cached. func response( for language: String ) -> Response { guard let response = responses[language] ?? responses[list.default] else { return .init(status: .internalServerError) } return response.response() } }