Packages

ex_cldr

1.6.2
2.47.5 2.47.4 2.47.3 2.47.2 2.47.1 2.47.0 2.46.0 2.45.2 2.45.1 2.45.0 2.44.1 2.44.0 2.43.2 2.43.1 2.43.0 2.42.0 2.41.1 2.41.0 2.40.4 2.40.3 2.40.2 2.40.1 2.40.0 2.39.2 2.39.1 2.39.0 2.38.1 2.38.0 2.37.5 2.37.4 2.37.3 retired 2.37.2 2.37.1 2.37.0 2.36.0 2.35.1 2.35.0 retired 2.34.2 2.34.1 2.34.0 2.33.2 2.33.1 2.33.1-rc.0 2.33.0 2.32.1 2.32.0 2.31.0 2.30.0 2.29.0 2.28.0 2.27.1 2.27.0 retired 2.26.3 2.26.2 2.26.1 2.26.0 2.25.0 2.24.2 2.24.1 2.24.0 2.24.0-rc.6 2.24.0-rc.5 2.24.0-rc.4 2.24.0-rc.3 2.24.0-rc.2 2.24.0-rc.1 2.24.0-rc.0 2.23.2 2.23.1 2.23.0 2.22.1 2.22.0 2.21.0 2.20.0 2.20.0-rc.3 2.20.0-rc.2 2.20.0-rc.1 2.20.0-rc.0 2.19.1 2.19.0 2.18.2 2.18.1 2.18.0 2.18.0-rc.0 2.17.2 2.17.1 2.17.0 2.16.2 2.16.1 2.16.1-rc.0 2.16.0 2.15.0 2.14.1 2.14.0 2.14.0-rc.0 2.13.0 2.12.1 2.12.0 2.11.1 2.11.0 2.10.2 2.10.1 2.10.0 retired 2.9.0 2.8.1 2.8.0 2.7.2 2.7.1 2.7.0 2.6.2 2.6.1 2.6.0 2.5.0 2.4.3 2.4.2 2.4.1 2.4.0 2.3.2 2.3.1 2.3.0 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 2.2.1 2.2.0 2.1.0 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 2.0.0-rc.3 retired 2.0.0-rc.2 retired 2.0.0-rc.1 retired 2.0.0-rc.0 retired 1.8.2 1.8.1 1.8.0 1.7.1 1.7.0 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.2 1.5.1 1.5.0 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 retired 1.4.0-rc.3 retired 1.4.0-rc.2 retired 1.4.0-rc.1 retired 1.4.0-rc.0 retired 1.3.2 1.3.1 1.3.0 1.2.0 1.1.0 1.0.0 1.0.0-rc.3 retired 1.0.0-rc.2 retired 1.0.0-rc.1 retired 1.0.0-rc.0 retired 0.13.1 0.13.0 0.12.2 retired 0.12.1 retired 0.12.0 retired 0.11.0 retired 0.10.0 retired 0.9.0 retired 0.8.3 retired 0.8.2 retired 0.8.1 retired 0.8.0 retired 0.7.0 retired 0.6.2 retired 0.6.1 retired 0.6.0 retired 0.5.2 retired 0.5.1 retired 0.5.0 retired 0.4.2 retired 0.4.1 retired 0.4.0 retired 0.3.0 retired 0.2.1 retired 0.2.0 retired 0.1.3 retired 0.1.2 retired 0.1.1 retired 0.1.0 retired 0.0.20 retired 0.0.19 retired 0.0.18 retired 0.0.17 retired 0.0.16 retired 0.0.15 retired 0.0.14 retired 0.0.13 retired 0.0.12 retired 0.0.11 retired 0.0.10 retired 0.0.8 retired 0.0.7 retired 0.0.6 retired 0.0.5 retired 0.0.4 retired 0.0.3 retired

Common Locale Data Repository (CLDR) functions for Elixir to localize and format numbers, dates, lists, messages, languages, territories and units with support for over 700 locales for internationalized (i18n) and localized (L10N) applications.

Current section

Files

Jump to
ex_cldr lib cldr accept_language.ex
Raw

lib/cldr/accept_language.ex

defmodule Cldr.AcceptLanguage do
@moduledoc """
Tokenizer and parser for HTTP `Accept-Language` header values as defined in
[rfc2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4).
The Accept-Language request-header field is similar to Accept, but restricts
the set of natural languages that are preferred as a response to the request.
Language tags function are provided in `Cldr.LanguageTag`.
The format of an `Accept-Language` header is as follows in `ABNF` format:
Accept-Language = "Accept-Language" ":"
1#( language-range [ ";" "q" "=" qvalue ] )
language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
Each language-range MAY be given an associated quality value which represents an
estimate of the user's preference for the languages specified by that range. The
quality value defaults to "q=1". For example,
Accept-Language: da, en-gb;q=0.8, en;q=0.7
would mean: "I prefer Danish, but will accept British English and other types of English."
"""
alias Cldr.Locale
alias Cldr.LanguageTag
@default_quality 1.0
@low_quality 0.2
@doc """
Splits the language ranges for an `Accept-Language` header
value into tuples `{quality, language}`.
* `accept-language` is any string in the format defined by [rfc2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
## Example
iex> Cldr.AcceptLanguage.tokenize "da,zh-TW;q=0.3"
[{1.0, "da"}, {0.3, "zh-tw"}]
"""
@spec tokenize(String.t()) :: [{float(), String.t()}, ...]
@language_separator ","
def tokenize(accept_language) do
accept_language
|> String.downcase()
|> remove_whitespace
|> String.split(@language_separator)
|> Enum.reject(&is_nil/1)
|> Enum.reject(&String.starts_with?(&1, "*"))
|> Enum.map(&token_tuple/1)
end
@quality_separator ";q="
defp token_tuple(language) do
case String.split(language, @quality_separator) do
[language, quality] ->
{parse_quality(quality), language}
[language] ->
{@default_quality, language}
[language | _rest] ->
{@low_quality, language}
end
end
@doc """
Parses an `Accept-Language` header value in its string
or tokenized form to return a tuple of the form
`{:ok, [{quality, %Cldr.LanguageTag{}}, ...]}` sorted by quality.
* `accept-language` is any string in the format defined by [rfc2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
Returns:
* `{:ok, [{quality, language_tag}, ...]}` or
* `{:error, {Cldr.AcceptLanguageError, String.t}}`
If at least one valid language tag is found but errors are also
detected on one more more tags, an `{ok, list}` tuple is returned
wuth an error tuple for each invalid tag added at the end of the list.
## Example
iex> Cldr.AcceptLanguage.parse "da,zh-TW;q=0.3"
{:ok,
[{1.0,
%Cldr.LanguageTag{canonical_locale_name: "da-Latn-DK",
cldr_locale_name: "da", extensions: %{}, language: "da",
locale: %{}, private_use: [], rbnf_locale_name: "da",
territory: "DK", requested_locale_name: "da", script: "Latn",
transform: %{}, variant: nil}},
{0.3,
%Cldr.LanguageTag{canonical_locale_name: "zh-Hant-TW",
cldr_locale_name: "zh-Hant", extensions: %{}, language: "zh",
locale: %{}, private_use: [], rbnf_locale_name: "zh-Hant",
territory: "TW", requested_locale_name: "zh-TW", script: "Hant",
transform: %{}, variant: nil}}]}
iex> Cldr.AcceptLanguage.parse "X"
{:error,
{Cldr.AcceptLanguageError,
"Could not parse language tag. Error was detected at 'x'"}}
iex> Cldr.AcceptLanguage.parse "da,zh-TW;q=0.3,X"
{:ok,
[{1.0,
%Cldr.LanguageTag{canonical_locale_name: "da-Latn-DK",
cldr_locale_name: "da", extensions: %{}, language: "da",
locale: %{}, private_use: [], rbnf_locale_name: "da",
territory: "DK", requested_locale_name: "da", script: "Latn",
transform: %{}, variant: nil}},
{0.3,
%Cldr.LanguageTag{canonical_locale_name: "zh-Hant-TW",
cldr_locale_name: "zh-Hant", extensions: %{}, language: "zh",
locale: %{}, private_use: [], rbnf_locale_name: "zh-Hant",
territory: "TW", requested_locale_name: "zh-TW", script: "Hant",
transform: %{}, variant: nil}},
{:error,
{Cldr.InvalidLanguageTag,
"Could not parse language tag. Error was detected at 'x'"},
"x"}]}
"""
@spec parse([{float(), String.t()}, ...] | String.t()) ::
{:ok,
[
{float(), LanguageTag.t()} | {:error, {Cldr.InvalidLanguageTag, String.t()}},
...
]}
def parse(tokens) when is_list(tokens) do
accept_language =
tokens
|> parse_language_tags
|> sort_by_quality
case accept_language do
[{:error, reason, language_tag}] ->
{:error, accept_language_error(reason, language_tag)}
_ ->
{:ok, accept_language}
end
end
def parse(string) when is_binary(string) do
string
|> tokenize
|> parse
end
@doc """
Parses an `Accept-Language` header value in its string
or tokenized form to produce a list of tuples of the form
`[{quality, %Cldr.LanguageTag{}}, ...]` sorted by quality
in decending order.
* `accept-language` is any string in the format defined by [rfc2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
Returns:
* `{:ok, [{quality, language_tag}, ...]}` or
* raises a `Cldr.AcceptLanguageError` exception
If at least one valid language tag is found but errors are also
detected on one more more tags, an `{ok, list}` tuple is returned
wuth an error tuple for each invalid tag added at the end of the list.
## Example
iex> Cldr.AcceptLanguage.parse! "da,zh-TW;q=0.3"
[{1.0,
%Cldr.LanguageTag{canonical_locale_name: "da-Latn-DK",
cldr_locale_name: "da", extensions: %{}, language: "da",
locale: %{}, private_use: [], rbnf_locale_name: "da", territory: "DK",
requested_locale_name: "da", script: "Latn", transform: %{},
variant: nil}},
{0.3,
%Cldr.LanguageTag{canonical_locale_name: "zh-Hant-TW",
cldr_locale_name: "zh-Hant", extensions: %{}, language: "zh",
locale: %{}, private_use: [], rbnf_locale_name: "zh-Hant",
territory: "TW", requested_locale_name: "zh-TW", script: "Hant",
transform: %{}, variant: nil}}]
Cldr.AcceptLanguage.parse! "X"
** (Cldr.AcceptLanguageError) Could not parse language tag. Error was detected at 'x'
(ex_cldr) lib/cldr/accept_language.ex:168: Cldr.AcceptLanguage.parse!/1
iex> Cldr.AcceptLanguage.parse! "da,zh-TW;q=0.3,X"
[{1.0,
%Cldr.LanguageTag{canonical_locale_name: "da-Latn-DK",
cldr_locale_name: "da", extensions: %{}, language: "da",
locale: %{}, private_use: [], rbnf_locale_name: "da", territory: "DK",
requested_locale_name: "da", script: "Latn", transform: %{},
variant: nil}},
{0.3,
%Cldr.LanguageTag{canonical_locale_name: "zh-Hant-TW",
cldr_locale_name: "zh-Hant", extensions: %{}, language: "zh",
locale: %{}, private_use: [], rbnf_locale_name: "zh-Hant",
territory: "TW", requested_locale_name: "zh-TW", script: "Hant",
transform: %{}, variant: nil}},
{:error,
{Cldr.InvalidLanguageTag,
"Could not parse language tag. Error was detected at 'x'"}, "x"}]
"""
def parse!(accept_language) do
case parse(accept_language) do
{:ok, parse_result} -> parse_result
{:error, {exception, reason}} -> raise exception, reason
end
end
@doc """
Parse an `Accept-Language` string and return the best match for
a configured `Cldr` locale.
* `accept_langauge` is a string representing an accept language header
Returns:
* `{:ok, language_tag}` or
* `{:error, reason}`
## Examples
iex> Cldr.AcceptLanguage.best_match "da;q=0.1,zh-TW;q=0.3"
{
:ok,
%Cldr.LanguageTag{
canonical_locale_name: "zh-Hant-TW",
cldr_locale_name: "zh-Hant",
extensions: %{},
language: "zh",
locale: %{},
private_use: [],
rbnf_locale_name: "zh-Hant",
requested_locale_name: "zh-TW",
script: "Hant",
territory: "TW",
transform: %{},
variant: nil
}
}
iex> Cldr.AcceptLanguage.best_match "en,zh-TW;q=0.3"
{:ok, %Cldr.LanguageTag{
canonical_locale_name: "en-Latn-US",
cldr_locale_name: "en",
extensions: %{},
gettext_locale_name: "en",
language: "en",
locale: %{},
private_use: [],
rbnf_locale_name: "en",
requested_locale_name: "en",
script: "Latn",
territory: "US",
transform: %{},
variant: nil
}}
iex> Cldr.AcceptLanguage.best_match "xx,yy;q=0.3"
{
:error,
{Cldr.NoMatchingLocale,
"No configured locale could be matched to \\"xx,yy;q=0.3\\""}
}
iex> Cldr.AcceptLanguage.best_match "x"
{
:error,
{Cldr.AcceptLanguageError,
"Could not parse language tag. Error was detected at 'x'"}
}
"""
@spec best_match(String.t()) ::
{:ok, LanguageTag.t()} | {:error, {Cldr.AcceptLanguageError, String.t()}}
def best_match(accept_language) when is_binary(accept_language) do
with {:ok, languages} <- parse(accept_language) do
candidates =
Enum.filter(languages, fn
{priority, %LanguageTag{cldr_locale_name: locale_name}}
when is_float(priority) and not is_nil(locale_name) ->
true
_ ->
false
end)
case candidates do
[{_priority, language_tag} | _] ->
{:ok, language_tag}
_ ->
{
:error,
{
Cldr.NoMatchingLocale,
"No configured locale could be matched to #{inspect(accept_language)}"
}
}
end
else
{:error, reason} -> {:error, reason}
end
end
@doc """
Filters the returned results of `parse/1` to return
only the error tuples.
## Example
iex> Cldr.AcceptLanguage.parse!("da,zh-TW;q=0.3,X") |> Cldr.AcceptLanguage.errors
[{:error,
{Cldr.InvalidLanguageTag,
"Could not parse language tag. Error was detected at 'x'"}, "x"}]
"""
@spec errors([tuple(), ...]) :: [{:error, {Cldr.InvalidLanguageTag, String.t()}}, ...]
def errors(parse_result) when is_list(parse_result) do
Enum.filter(parse_result, fn
{:error, _, _} -> true
_ -> false
end)
end
defp parse_quality(quality_string) do
case Float.parse(quality_string) do
:error -> @low_quality
{quality, _} -> quality
end
end
defp parse_language_tags(tokens) do
Enum.map(tokens, fn {quality, language_tag} ->
case Locale.canonical_language_tag(language_tag) do
{:ok, tag} ->
{quality, tag}
{:error, reason} ->
{:error, reason, language_tag}
end
end)
end
defp remove_whitespace(accept_language) do
String.replace(accept_language, " ", "")
end
defp sort_by_quality(tokens) do
Enum.sort(tokens, fn
{:error, _, _}, {_quality_2, _} -> false
{_quality_2, _}, {:error, _, _} -> true
{quality_1, _}, {quality_2, _} -> quality_1 > quality_2
end)
end
defp accept_language_error({_exception, reason}, _language_tag) do
{Cldr.AcceptLanguageError, reason}
end
end