Updated the Negotiate method in the Localization package to add explicit language requests.
This commit is contained in:
@@ -5,7 +5,7 @@ The server-side localization toolkit the platform's services build on: locale-ex
|
|||||||
| Role | Types |
|
| Role | Types |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Lookup | `Localize`, a bundle-bound localizer that resolves a catalog key for an explicit locale |
|
| Lookup | `Localize`, a bundle-bound localizer that resolves a catalog key for an explicit locale |
|
||||||
| Negotiation | `Negotiate`, which picks the best supported language from an `Accept-Language` header per RFC 9110 |
|
| Negotiation | `Negotiate`, which picks the best supported language from an explicit request or an `Accept-Language` header per RFC 9110 |
|
||||||
| Languages | `LanguageList`, the supported and default languages a bundle's String Catalog defines |
|
| Languages | `LanguageList`, the supported and default languages a bundle's String Catalog defines |
|
||||||
| Diagnostics | `CatalogState`, the outcome of reading the catalog (`loaded`, `missing`, or `undecodable`) |
|
| Diagnostics | `CatalogState`, the outcome of reading the catalog (`loaded`, `missing`, or `undecodable`) |
|
||||||
|
|
||||||
|
|||||||
@@ -24,18 +24,32 @@ public struct Negotiate: Sendable {
|
|||||||
|
|
||||||
// MARK: Methods
|
// MARK: Methods
|
||||||
|
|
||||||
/// Picks the best supported language for the given `Accept-Language` header value.
|
/// Picks the best supported language for the given explicit request and `Accept-Language` header value.
|
||||||
///
|
///
|
||||||
/// Invoked by calling the instance directly, for example `negotiate(acceptLanguage: header)`.
|
/// Invoked by calling the instance directly, for example `negotiate(acceptLanguage: header)`.
|
||||||
|
/// A `requested` language — a deliberate choice, such as a language switcher's query parameter — wins over the header when it matches a
|
||||||
|
/// supported language, on the same terms a header tag matches; an unsupported value is ignored, so it cannot select a language the catalog
|
||||||
|
/// does not serve.
|
||||||
|
///
|
||||||
/// The header is parsed into its language ranges, which are ordered by descending `q` weight as RFC 9110 prescribes: an entry without a weight
|
/// The header is parsed into its language ranges, which are ordered by descending `q` weight as RFC 9110 prescribes: an entry without a weight
|
||||||
/// counts as 1, entries weighted 0 are "not acceptable" and dropped, and equal weights keep the header order. Each tag is then matched against the
|
/// counts as 1, entries weighted 0 are "not acceptable" and dropped, and equal weights keep the header order. Each tag is then matched against the
|
||||||
/// supported languages in turn — first by an exact match, then by its primary language subtag, so `de-AT` resolves to a supported `de` — while the
|
/// supported languages in turn — first by an exact match, then by its primary language subtag, so `de-AT` resolves to a supported `de` — while the
|
||||||
/// `*` wildcard accepts the default language. When the header is absent or matches nothing, the default language is returned.
|
/// `*` wildcard accepts the default language. When the header is absent or matches nothing, the default language is returned.
|
||||||
/// - Parameter acceptLanguage: the raw `Accept-Language` header value, if any.
|
/// - Parameters:
|
||||||
|
/// - requested: an explicitly requested language identifier, if any; it overrides the header when supported.
|
||||||
|
/// - acceptLanguage: the raw `Accept-Language` header value, if any.
|
||||||
/// - Returns: the identifier of the supported language to serve.
|
/// - Returns: the identifier of the supported language to serve.
|
||||||
public func callAsFunction(
|
public func callAsFunction(
|
||||||
|
requested: String? = nil,
|
||||||
acceptLanguage language: String?
|
acceptLanguage language: String?
|
||||||
) -> String {
|
) -> String {
|
||||||
|
if
|
||||||
|
let requested,
|
||||||
|
let match = match(requested, in: list.all)
|
||||||
|
{
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
guard let language else {
|
guard let language else {
|
||||||
return list.default
|
return list.default
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,4 +127,44 @@ struct NegotiateTests {
|
|||||||
#expect(language == "en")
|
#expect(language == "en")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `honours a supported requested language over the header`() {
|
||||||
|
let language = negotiate(
|
||||||
|
requested: "de",
|
||||||
|
acceptLanguage: "en"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(language == "de")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `matches a regional requested language by its primary subtag`() {
|
||||||
|
let language = negotiate(
|
||||||
|
requested: "de-AT",
|
||||||
|
acceptLanguage: "en"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(language == "de")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `ignores an unsupported requested language`() {
|
||||||
|
let language = negotiate(
|
||||||
|
requested: "fr",
|
||||||
|
acceptLanguage: "de"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(language == "de")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `ignores an empty requested language`() {
|
||||||
|
let language = negotiate(
|
||||||
|
requested: "",
|
||||||
|
acceptLanguage: "de"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(language == "de")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user