defmodule Cldr.AcceptLanguage.Backend do @moduledoc false def define_backend_functions(config) do quote location: :keep, bind_quoted: [config: Macro.escape(config)] do defmodule AcceptLanguage do @moduledoc false if Cldr.Config.include_module_docs?(config.generate_docs) do @moduledoc """ Parses 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." """ end alias Cldr.{Locale, LanguageTag} @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. ## Arguments * `accept-language` is any string in the format defined by [rfc2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) * `backend` is any module that includes `use Cldr` and therefore is a `Cldr` backend module ## 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 with 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", TestBackend.Cldr) {:ok, [ {1.0, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "da", cldr_locale_name: :da, language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "da", locale: %{}, private_use: [], rbnf_locale_name: :da, requested_locale_name: "da", script: :Latn, territory: :DK, transform: %{}, language_variants: [] }}, {0.3, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "zh-TW", cldr_locale_name: :"zh-Hant", language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "zh", locale: %{}, private_use: [], rbnf_locale_name: :"zh-Hant", requested_locale_name: "zh-TW", script: :Hant, territory: :TW, transform: %{}, language_variants: [] }} ]} iex> #{inspect(__MODULE__)}.parse("invalid_tag") {:error, {Cldr.LanguageTag.ParseError, "Expected a BCP47 language tag. Could not parse the remaining \\"g\\" starting at position 11"}} iex> #{inspect(__MODULE__)}.parse("da,zh-TW;q=0.3,invalid_tag") {:ok, [ {1.0, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "da", cldr_locale_name: :da, language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "da", locale: %{}, private_use: [], rbnf_locale_name: :da, requested_locale_name: "da", script: :Latn, territory: :DK, transform: %{}, language_variants: [] }}, {0.3, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "zh-TW", cldr_locale_name: :"zh-Hant", language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "zh", locale: %{}, private_use: [], rbnf_locale_name: :"zh-Hant", requested_locale_name: "zh-TW", script: :Hant, territory: :TW, transform: %{}, language_variants: [] }}, {:error, {Cldr.LanguageTag.ParseError, "Expected a BCP47 language tag. Could not parse the remaining \\"g\\" starting at position 11"}} ]} """ @doc since: "2.30.0" @spec parse([{float(), String.t()}, ...] | String.t()) :: {:ok, [ {float(), LanguageTag.t()} | {:error, {Cldr.InvalidLanguageTag, String.t()}}, ... ]} | {:error, {Cldr.AcceptLanguageError, String.t()}} def parse(tokens_or_string) do Cldr.AcceptLanguage.parse(tokens_or_string, unquote(config.backend)) 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 descending order. ## Arguments * `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 with an error tuple for each invalid tag added at the end of the list. ## Example iex> #{inspect(__MODULE__)}.parse!("da,zh-TW;q=0.3") [ {1.0, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "da", cldr_locale_name: :da, language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "da", locale: %{}, private_use: [], rbnf_locale_name: :da, requested_locale_name: "da", script: :Latn, territory: :DK, transform: %{}, language_variants: [] }}, {0.3, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "zh-TW", cldr_locale_name: :"zh-Hant", language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "zh", locale: %{}, private_use: [], rbnf_locale_name: :"zh-Hant", requested_locale_name: "zh-TW", script: :Hant, territory: :TW, transform: %{}, language_variants: [] }} ] #{inspect(__MODULE__)}.parse! "invalid_tag" ** (Cldr.AcceptLanguageError) "Expected a BCP47 language tag. Could not parse the remaining "g" starting at position 11 (ex_cldr) lib/cldr/accept_language.ex:304: Cldr.AcceptLanguage.parse!/1 iex> #{inspect(__MODULE__)}.parse!("da,zh-TW;q=0.3,invalid_tag") [ {1.0, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "da", cldr_locale_name: :da, language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "da", locale: %{}, private_use: [], rbnf_locale_name: :da, requested_locale_name: "da", script: :Latn, territory: :DK, transform: %{}, language_variants: [] }}, {0.3, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "zh-TW", cldr_locale_name: :"zh-Hant", language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "zh", locale: %{}, private_use: [], rbnf_locale_name: :"zh-Hant", requested_locale_name: "zh-TW", script: :Hant, territory: :TW, transform: %{}, language_variants: [] }}, {:error, {Cldr.LanguageTag.ParseError, "Expected a BCP47 language tag. Could not parse the remaining \\"g\\" starting at position 11"}} ] """ def parse!(accept_language) do Cldr.AcceptLanguage.parse!(accept_language, unquote(config.backend)) end @doc """ Parse an `Accept-Language` string and return the best match for a configured `Cldr` locale. ## Arguments * `accept_language` is a string representing an accept language header ## Returns * `{:ok, language_tag}` or * `{:error, reason}` ## Examples iex> #{inspect(__MODULE__)}.best_match("da;q=0.1,zh-TW;q=0.3", TestBackend.Cldr) {:ok, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "zh-TW", cldr_locale_name: :"zh-Hant", language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "zh", locale: %{}, private_use: [], rbnf_locale_name: :"zh-Hant", requested_locale_name: "zh-TW", script: :Hant, territory: :TW, transform: %{}, language_variants: [] }} iex> #{inspect(__MODULE__)}.best_match("da;q=0.1,zh-TW;q=0.3", TestBackend.Cldr) {:ok, %Cldr.LanguageTag{ backend: TestBackend.Cldr, canonical_locale_name: "zh-TW", cldr_locale_name: :"zh-Hant", language_subtags: [], extensions: %{}, gettext_locale_name: nil, language: "zh", locale: %{}, private_use: [], rbnf_locale_name: :"zh-Hant", requested_locale_name: "zh-TW", script: :Hant, territory: :TW, transform: %{}, language_variants: [] }} iex> #{inspect(__MODULE__)}.best_match("xx,yy;q=0.3") {:error, {Cldr.NoMatchingLocale, "No configured locale could be matched to \\"xx,yy;q=0.3\\""}} iex> #{inspect(__MODULE__)}.best_match("invalid_tag") {:error, {Cldr.LanguageTag.ParseError, "Expected a BCP47 language tag. Could not parse the remaining \\"g\\" starting at position 11"}} """ @spec best_match(String.t()) :: {:ok, LanguageTag.t()} | {:error, {Cldr.AcceptLanguageError | Cldr.NoMatchingLocale, String.t()}} def best_match(accept_language) when is_binary(accept_language) do Cldr.AcceptLanguage.best_match(accept_language, unquote(config.backend)) end end end end end