Skip to content

Commit

Permalink
Add MyApp.Cldr.AcceptLanguage module to backend collection
Browse files Browse the repository at this point in the history
  • Loading branch information
kipcole9 committed Jun 7, 2022
1 parent 44ae020 commit 394fa42
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 2 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Cldr v2.30.0

This is the changelog for Cldr v2.30.0 released on June 5th, 2022. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr/tags)

### Enhancements

* Adds a backend module for `AcceptLanguage` which defines `MyApp.Cldr.AcceptLanguage.parse/1` and `MyApp.Cldr.AcceptLanguage.best_match/1`

## Cldr v2.29.0

This is the changelog for Cldr v2.29.0 released on May 10th, 2022. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr/tags)
Expand Down
357 changes: 357 additions & 0 deletions lib/cldr/backend/accept_language.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
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
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", 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
wuth 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_langauge` 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
1 change: 1 addition & 0 deletions lib/cldr/backend/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ defmodule Cldr.Backend.Compiler do

unquote(Cldr.Backend.define_backend_functions(config))
unquote(Cldr.Locale.Backend.define_locale_backend(config))
unquote(Cldr.AcceptLanguage.Backend.define_backend_functions(config))
unquote(Cldr.Number.PluralRule.define_ordinal_and_cardinal_modules(config))
unquote(Cldr.Number.PluralRule.define_plural_ranges(config))
unquote(Cldr.Config.define_provider_modules(config))
Expand Down
2 changes: 1 addition & 1 deletion lib/cldr/language_tag.ex
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ defmodule Cldr.LanguageTag do
nu | Number system identifier | arabext, armnlow, roman, tamldec
rg | Region override | The value is a unicode_region_subtag for a regular region (not a macroregion), suffixed by "ZZZZ"
sd | Subdivision identifier | A unicode_subdivision_id, which is a unicode_region_subtagconcatenated with a unicode_subdivision_suffix.
ss | Break suppressions identifier | none, standard
ss | Break suppressions identifier | none, standard
tz | Timezone identifier | Short identifiers defined in terms of a TZ time zone database
va | Common variant type | POSIX style locale variant
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Cldr.Mixfile do
use Mix.Project

@version "2.29.0"
@version "2.30.0"

def project do
[
Expand Down

0 comments on commit 394fa42

Please sign in to comment.