gampleman/elm-translate-icu/1.0.0

Icu.Locale

Parse and resolve BCP 47 language tags.

This module lets you match a requested locale (or a prioritized list of requested locales, as in an HTTP Accept-Language header) against the set of locales you actually have translations for — without hard-coding which locales exist. New translations can be added as data at runtime and resolution keeps working.

type Locale

A parsed, canonicalized BCP 47 language tag.

Canonical case is applied on parse: language lowercase (en), script in title case (Latn), region uppercase (US), variants lowercase. Extension and private-use subtags (-u-…, -x-…) are not retained — they don't affect translation lookup.

The en locale — a sensible total default when no locale has been negotiated yet, so callers can hold a concrete Locale (and thus a concrete Bundle) without threading a Maybe everywhere.

toString default
--> "en"
fromString : String -> Maybe Locale

Parse a BCP 47 tag such as "en", "en-US", "zh-Hant-TW", or "sr-Latn-RS". Both - and _ are accepted as separators. Returns Nothing if there is no valid primary language subtag.

fromString "en-US" |> Maybe.map toString
--> Just "en-US"


fromString "ZH_hant_tw" |> Maybe.map toString
--> Just "zh-Hant-TW"
toString : Locale -> String

Render a Locale back to its canonical string form.

language : Locale -> String

The primary language subtag, e.g. "en".

script : Locale -> Maybe String

The script subtag, if present, e.g. Just "Hant".

region : Locale -> Maybe String

The region subtag, if present, e.g. Just "US".

variants : Locale -> List String

The variant subtags, if any.

The fallback chain for a locale, most specific first, following RFC 4647 "lookup" truncation: trailing subtags are dropped one group at a time down to the bare language.

fromString "zh-Hant-TW"
    |> Maybe.map (fallbacks >> List.map toString)
--> Just [ "zh-Hant-TW", "zh-Hant", "zh" ]
bestMatch : List Locale -> Locale -> Maybe Locale

Find the best available locale for a single requested locale, using the fallbacks chain. The requested locale is progressively generalized until one of the available locales matches exactly (in canonical form); returns Nothing if nothing matches.

resolve want =
    [ "en", "en-GB", "fr" ]
        |> List.filterMap fromString
        |> (\avail -> fromString want |> Maybe.andThen (bestMatch avail))
        |> Maybe.map toString


resolve "en-US" --> Just "en"
resolve "en-GB" --> Just "en-GB"
resolve "de"    --> Nothing
negotiate : List Locale -> List Locale -> Maybe Locale

Negotiate against a prioritized list of requested locales (highest priority first, as in an Accept-Language header). Returns the bestMatch for the first requested locale that resolves.

let
    avail =
        List.filterMap fromString [ "en", "fr-CA", "fr" ]
    wanted =
        List.filterMap fromString [ "de", "fr-FR", "en" ]
in
negotiate avail wanted |> Maybe.map toString
--> Just "fr"