howdy/locale
Which language to answer a request in.
A negotiator lists the locales an app supports and where to look for the one a request wants, in order. The first source that names a supported locale wins; when none does, the default is used.
let locales =
locale.negotiator(["en", "de", "fr-CA", "ar"], default: "en")
|> locale.from_param("locale")
|> locale.from_cookie("locale")
|> locale.from_accept_language
fn show(ctx: Context) {
let l = locale.detect(ctx, locales)
controller.html(ctx, page(l))
}
Matching follows the lookup of RFC 4647: de-CH falls back to de.
A request for a bare language also takes a regional locale of it, so
en matches an app that only supports en-GB.
Add middleware to stamp responses with content-language, and with
vary naming the headers the choice came from, so shared caches keep
one copy per language. remember stores a visitor’s choice in the
cookie named by from_cookie, for a language switcher.
Types
A language, optionally with a script and a region, such as en,
pt-BR or zh-Hant-TW. Variants and extensions are dropped when
parsing, since nothing in Howdy uses them.
pub opaque type Locale
The locales an app supports and where to look for a request’s choice.
pub opaque type Negotiator
Values
pub fn accept_language(header: String) -> List(String)
The language ranges in an accept-language header, most wanted first.
Ranges with q=0, and the * wildcard, are left out.
pub fn default(negotiator: Negotiator) -> Locale
pub fn detect(
ctx: context.Context(guarded),
negotiator: Negotiator,
) -> Locale
The locale for a request.
pub fn detect_request(
request: request.Request(context.Body),
params: dict.Dict(String, String),
negotiator: Negotiator,
) -> Locale
The locale for a request outside a handler, such as when a live view
mounts. params are the route parameters, if any.
pub fn fallbacks(locale: Locale) -> List(Locale)
The locale and the less specific ones it falls back to, most specific
first: zh-Hant-TW, zh-Hant, zh.
pub fn from(
negotiator: Negotiator,
find: fn(request.Request(context.Body)) -> option.Option(String),
) -> Negotiator
Look wherever find does, such as in a signed-in user’s saved
preference. None, or a tag that is not supported, moves on to the next
source. Since a custom source may read any header, middleware sets
vary: * to keep its responses out of shared caches.
pub fn from_accept_language(negotiator: Negotiator) -> Negotiator
Look in the accept-language header the browser sends, trying each
language in order of preference.
pub fn from_cookie(
negotiator: Negotiator,
name: String,
) -> Negotiator
Look in the cookie name, which remember sets.
pub fn from_param(
negotiator: Negotiator,
name: String,
) -> Negotiator
Look in the route parameter name, such as locale for routes under
/:locale. Route parameters are not known to app-wide middleware; use
from_path_segment there.
pub fn from_path_segment(negotiator: Negotiator) -> Negotiator
Look at the first segment of the path, so /de/shop asks for de.
pub fn from_query(
negotiator: Negotiator,
name: String,
) -> Negotiator
Look in the query parameter name, such as ?lang=de.
pub fn match(
negotiator: Negotiator,
tag: String,
) -> option.Option(Locale)
The supported locale a tag asks for, if any: the tag itself, then each of its fallbacks, then any supported locale of the same language.
pub fn middleware(
negotiator: Negotiator,
) -> fn(
context.Context(Nil),
fn(context.Context(Nil)) -> response.Response(content.Content),
) -> response.Response(content.Content)
Stamp every response with content-language, unless the handler set
one, and add to vary the request headers the negotiator reads, so a
shared cache keeps a copy per language.
pub fn negotiate(
negotiator: Negotiator,
tags: List(String),
) -> Locale
The supported locale for the first of tags that has one, or the
default.
pub fn negotiator(
supported: List(String),
default default: String,
) -> Negotiator
A negotiator for supported locales, falling back to default, which
is supported even when not listed. With no sources added it always
answers the default. Invalid tags panic, as they are configuration.
pub fn parse(tag: String) -> Result(Locale, Nil)
Parse a BCP 47 language tag. Case is normalised, so EN-us becomes
en-US, and _ is accepted in place of -.
pub fn region(locale: Locale) -> option.Option(String)
pub fn remember(
answer: response.Response(body),
negotiator: Negotiator,
locale: Locale,
) -> response.Response(body)
Store a visitor’s choice in the cookie named by from_cookie, for a
year. Panics if the negotiator reads no cookie, since the choice would
never be read back.
pub fn right_to_left(locale: Locale) -> Bool
Whether text in this locale runs right to left, as Arabic, Hebrew and Persian do. An explicit script decides when there is one.
pub fn script(locale: Locale) -> option.Option(String)
pub fn supported(negotiator: Negotiator) -> List(Locale)