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. public struct LocalizedHTMLCollectionResponse: Sendable { // MARK: Properties /// The supported languages and default language, derived from the bundle'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: /// - bundle: the bundle whose String Catalog names the languages the document is rendered for. /// - status: the status applied to every response. Defaults to `.ok`. /// - document: builds the document to render for a given locale. public init( bundle: Bundle, status: HTTPResponse.Status = .ok, document: (Locale) -> Document ) { self.list = .init(bundle: bundle) 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. /// - Parameters: /// - language: the negotiated language identifier. /// - request: the request the response answers, consulted for conditional revalidation. /// - 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. public func response( for language: String, request: Request ) -> Response { guard let response = responses[language] ?? responses[list.default] else { return .init( status: .internalServerError ) } return response.response( for: request ) } }