From d14ab03b74887d99b5e3678192b355fa7bddc389 Mon Sep 17 00:00:00 2001 From: Kip Cole Date: Sat, 18 Nov 2017 06:50:36 +0800 Subject: [PATCH] Continue work on @docs, especially adding ## Returns and ## Options --- lib/cldr.ex | 133 ++++++++++++++--- lib/cldr/language_tag/parser.ex | 3 +- lib/cldr/locale.ex | 244 ++++++++++++++++---------------- test/cldr_test.exs | 53 +++++++ 4 files changed, 294 insertions(+), 139 deletions(-) diff --git a/lib/cldr.ex b/lib/cldr.ex index c6036f4b3..b53cdebf1 100644 --- a/lib/cldr.ex +++ b/lib/cldr.ex @@ -127,6 +127,8 @@ defmodule Cldr do Set the current locale to be used for `Cldr` functions that take an optional locale parameter for which a locale is not supplied. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/1` @@ -293,7 +295,9 @@ defmodule Cldr do underscore replaces by hyphen in order to facilitate comparisons with Cldr locale names. """ - @known_gettext_locale_names Config.gettext_locales() |> Enum.map(&String.replace(&1, "_", "-")) + @known_gettext_locale_names Config.gettext_locales() + |> Enum.map(&Locale.locale_name_from_posix/1) + @spec known_gettext_locale_names :: [Locale.locale_name, ...] | [] def known_gettext_locale_names do @known_gettext_locale_names @@ -303,6 +307,8 @@ defmodule Cldr do Returns a boolean indicating if the specified locale name is configured and available in Cldr. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` ## Examples @@ -324,6 +330,8 @@ defmodule Cldr do name is configured and available in Cldr and supports rules based number formats (RBNF). + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` ## Examples @@ -344,6 +352,8 @@ defmodule Cldr do Returns a boolean indicating if the specified locale name is configured and available in Gettext. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` ## Examples @@ -367,6 +377,8 @@ defmodule Cldr do This is helpful when building a list of `or` expressions to return the first known locale name from a list. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` ## Examples @@ -392,6 +404,8 @@ defmodule Cldr do whether the locale name is configured in `Cldr` and has RBNF rules defined. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` ## Examples @@ -417,6 +431,8 @@ defmodule Cldr do `false` based upon whether the locale name is configured in `GetText`. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` ## Examples @@ -447,6 +463,8 @@ defmodule Cldr do mean the locale is configured for Cldr. See also `Cldr.known_locale?/1`. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/1` @@ -471,10 +489,12 @@ defmodule Cldr do @doc """ Normalise and validate a locale name. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/1` - Returns: + ## Returns * `{:ok, language_tag}` @@ -515,9 +535,10 @@ defmodule Cldr do {:ok, String.t} | {:error, {Exception.t, String.t}} def validate_locale(locale_name) when is_binary(locale_name) do - locale_name - |> Cldr.Locale.new! - |> validate_locale + case Cldr.Locale.new(locale_name) do + {:ok, locale} -> validate_locale(locale) + {:error, reason} -> {:error, reason} + end end def validate_locale(%LanguageTag{cldr_locale_name: nil} = locale) do @@ -532,6 +553,43 @@ defmodule Cldr do {:error, Locale.locale_error(locale)} end + @doc """ + Normalise and validate a gettext locale name. + + ## Options + + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` + or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/1` + + ## Returns + + * `{:ok, language_tag}` + + * `{:error, reason}` + + ## Examples + + + """ + def validate_gettext_locale(locale_name) when is_binary(locale_name) do + case Cldr.Locale.new(locale_name) do + {:ok, locale} -> validate_gettext_locale(locale) + {:error, reason} -> {:error, reason} + end + end + + def validate_gettext_locale(%LanguageTag{gettext_locale_name: nil} = locale) do + {:error, Locale.gettext_locale_error(locale)} + end + + def validate_gettext_locale(%LanguageTag{} = language_tag) do + {:ok, language_tag} + end + + def validate_gettext_locale(locale) do + {:error, Locale.gettext_locale_error(locale)} + end + @doc """ Returns a list of strings representing the calendars known to `Cldr`. @@ -552,13 +610,15 @@ defmodule Cldr do @doc """ Normalise and validate a calendar name. + ## Options + * `calendar` is any calendar name returned by `Cldr.known_calendars/0` - Returns: + ## Returns * `{:ok, normalized_calendar_name}` or - * `{:error, {exception, message}}` + * `{:error, {Cldr.UnknownCalendarError, message}}` ## Examples @@ -597,8 +657,14 @@ defmodule Cldr do @doc """ Returns an error tuple for an invalid calendar. + ## Options + * `calendar` is any calendar name **not** returned by `Cldr.known_calendars/0` + ## Returns + + * `{:error, {Cldr.UnknownCalendarError, message}}` + ## Examples iex> Cldr.unknown_calendar_error "invalid" @@ -653,13 +719,15 @@ defmodule Cldr do @doc """ Normalise and validate a territory code. + ## Options + * `territory` is any territory code returned by `Cldr.known_territories/0` - Returns: + ## Returns: * `{:ok, normalized_territory_code}` or - * `{:error, {exception, message}}` + * `{:error, {Cldr.UnknownTerritoryError, message}}` ## Examples @@ -716,7 +784,13 @@ defmodule Cldr do @doc """ Returns an error tuple for an unknown territory. - * `territory` is any territory code **not** returned by `Cldr.known_territories/0` + ## Options + + * `territory` is any territory code **not** returned by `Cldr.known_territories/0` + + ## Returns + + * `{:error, {Cldr.UnknownTerritoryError, message}}` ## Examples @@ -770,13 +844,15 @@ defmodule Cldr do @doc """ Normalize and validate a currency code. + ## Options + * `currency` is any ISO 4217 currency code as returned by `Cldr.known_currencies/0` - Returns: + ## Returns * `{:ok, normalized_currency_code}` or - * `{:error, {exception, message}}` + * `{:error, {Cldr.UnknownCurrencyError, message}}` ## Examples @@ -818,7 +894,13 @@ defmodule Cldr do @doc """ Returns an error tuple for an invalid currency. - * `currency` is any currency code **not** returned by `Cldr.known_currencies/0` + ## Options + + * `currency` is any currency code **not** returned by `Cldr.known_currencies/0` + + ## Returns + + * `{:error, {Cldr.UnknownCurrencyError, message}} ## Examples @@ -856,10 +938,12 @@ defmodule Cldr do @doc """ Normalize and validate a number system name. + ## Options + * `number_system` is any number system name returned by `Cldr.known_number_systems/0` - Returns: + ## Returns * `{:ok, normalized_number_system_name}` or @@ -906,7 +990,13 @@ defmodule Cldr do @doc """ Returns an error tuple for an unknown number system. - * `number_system` is any number system name **not** returned by `Cldr.known_number_systems/0` + ## Options + + * `number_system` is any number system name **not** returned by `Cldr.known_number_systems/0` + + ## Returns + + * `{:error, {Cldr.UnknownNumberSystemError, message}}` ## Examples @@ -943,12 +1033,15 @@ defmodule Cldr do @doc """ Normalise and validate a number system type. + ## Options + * `number_system_type` is any number system type returned by `Cldr.known_number_system_types/0` - Returns: + ## Returns * `{:ok, normalized_number_system_type}` or + * `{:error, {exception, message}}` ## Examples @@ -992,9 +1085,15 @@ defmodule Cldr do @doc """ Returns an error tuple for an unknown number system type. - * `number_system_type` is any number system type name **not** returned + ## Options + + * `number_system_type` is any number system type name **not** returned by `Cldr.known_number_system_types/0` + ## Returns + + * `{:error, {Cldr.UnknownNumberSystemTypeError, message}}` + ## Examples iex> Cldr.unknown_number_system_type_error "invalid" diff --git a/lib/cldr/language_tag/parser.ex b/lib/cldr/language_tag/parser.ex index a620f9be0..b65c642de 100644 --- a/lib/cldr/language_tag/parser.ex +++ b/lib/cldr/language_tag/parser.ex @@ -10,6 +10,7 @@ defmodule Cldr.LanguageTag.Parser do """ alias Cldr.LanguageTag alias Cldr.Config + alias Cldr.Locale @doc """ Parse a locale name into a `Cldr.LanguageTag.t` @@ -175,7 +176,7 @@ defmodule Cldr.LanguageTag.Parser do defp normalize_locale_name(name) do name |> String.downcase - |> String.replace("_", "-") + |> Locale.locale_name_from_posix end defp normalize_language(nil), do: nil diff --git a/lib/cldr/locale.ex b/lib/cldr/locale.ex index 2eda193e8..9640c5c0c 100644 --- a/lib/cldr/locale.ex +++ b/lib/cldr/locale.ex @@ -121,7 +121,7 @@ defmodule Cldr.Locale do variant: nil }} - Showing that a the likely subtag for the script is "Latn" and the likely + Which shows that a the likely subtag for the script is "Latn" and the likely territory is "US". Using the example for Substitutions above, we can see the @@ -169,16 +169,20 @@ defmodule Cldr.Locale do Parses a locale name and returns a `Cldr.LanguageTag` struct that represents a locale. + ## Options + + * `language_tag` is any language tag returned by `Cldr.Locale.new/1` + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct - Returns: + ## Returns * `{:ok, language_tag}` or * `{:eror, reason}` - Several steps are followed to produce a canonical language tag: + ## Method 1. The language tag is parsed in accordance with [RFC5646](https://tools.ietf.org/html/rfc5646) @@ -250,6 +254,10 @@ defmodule Cldr.Locale do Parses a locale name and returns a `Cldr.LanguageTag` struct that represents a locale or raises on error. + ## Options + + * `language_tag` is any language tag returned by `Cldr.Locale.new/1` + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct @@ -292,63 +300,54 @@ defmodule Cldr.Locale do end @spec cldr_locale_name(LanguageTag.t) :: locale_name | nil - defp cldr_locale_name(%LanguageTag{language: language, script: script, - territory: territory, variant: variant} = language_tag) do - - # Including variant - Cldr.known_locale_name(locale_name_from(language, script, territory, variant)) || - Cldr.known_locale_name(locale_name_from(language, nil, territory, variant)) || - Cldr.known_locale_name(locale_name_from(language, script, nil, variant)) || - Cldr.known_locale_name(locale_name_from(language, nil, nil, variant)) || - - # Not including variant - Cldr.known_locale_name(locale_name_from(language, script, territory, nil)) || - Cldr.known_locale_name(locale_name_from(language, nil, territory, nil)) || - Cldr.known_locale_name(locale_name_from(language, script, nil, nil)) || - Cldr.known_locale_name(locale_name_from(language, nil, nil, nil)) || - - # Finally the requested locale name + defp cldr_locale_name(%LanguageTag{} = language_tag) do + first_match(language_tag, &Cldr.known_locale_name/1) || Cldr.known_locale_name(language_tag.requested_locale_name) || - - # Boom - can't match nil end @spec rbnf_locale_name(LanguageTag.t) :: locale_name | nil - defp rbnf_locale_name(%LanguageTag{language: language, script: script, - territory: territory} = language_tag) do - Cldr.known_rbnf_locale_name(locale_name_from(language, script, territory, nil)) || - Cldr.known_rbnf_locale_name(locale_name_from(language, nil, territory, nil)) || - Cldr.known_rbnf_locale_name(locale_name_from(language, script, nil, nil)) || - Cldr.known_rbnf_locale_name(locale_name_from(language, nil, nil, nil)) || - Cldr.known_rbnf_locale_name(language_tag.requested_locale_name) || - nil + defp rbnf_locale_name(%LanguageTag{} = language_tag) do + first_match(language_tag, &Cldr.known_rbnf_locale_name/1) end @spec gettext_locale_name(LanguageTag.t) :: locale_name | nil - defp gettext_locale_name(%LanguageTag{language: language, script: script, - territory: territory} = language_tag) do - name = - Cldr.known_gettext_locale_name(locale_name_from(language, script, territory, nil)) || - Cldr.known_gettext_locale_name(locale_name_from(language, nil, territory, nil)) || - Cldr.known_gettext_locale_name(locale_name_from(language, script, nil, nil)) || - Cldr.known_gettext_locale_name(locale_name_from(language, nil, nil, nil)) || - Cldr.known_gettext_locale_name(language_tag.requested_locale_name) || - nil + defp gettext_locale_name(%LanguageTag{} = language_tag) do + language_tag + |> first_match(&Cldr.known_gettext_locale_name/1) + |> locale_name_to_posix + end - if name do - String.replace(name, "-", "_") - else - name - end + defp first_match(%LanguageTag{language: language, script: script, + territory: territory, variant: variant}, fun) when is_function(fun) do + # Including variant + fun.(locale_name_from(language, script, territory, variant)) || + fun.(locale_name_from(language, nil, territory, variant)) || + fun.(locale_name_from(language, script, nil, variant)) || + fun.(locale_name_from(language, nil, nil, variant)) || + + # Not including variant + fun.(locale_name_from(language, script, territory, nil)) || + fun.(locale_name_from(language, nil, territory, nil)) || + fun.(locale_name_from(language, script, nil, nil)) || + fun.(locale_name_from(language, nil, nil, nil)) || + nil end @doc """ Normalize the casing of a locale name. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct + ## Returns + + * The normalized locale name as a `String.t` + + ## Method + Locale names are case insensitive but certain common casing is followed in practise: @@ -396,13 +395,15 @@ defmodule Cldr.Locale do [lang] -> String.downcase(lang) _ -> - String.replace(locale_name, "_", "-") + locale_name_from_posix(locale_name) end end @doc """ Return a locale name from a `Cldr.LanguageTag` + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct @@ -423,9 +424,16 @@ defmodule Cldr.Locale do Return a locale name by combining language, script, territory and variant parameters + ## Options + * `language`, `script`, `territory` and `variant` are string representations, or `nil`, of the language subtags + ## Returns + + * The locale name constructed from the non-nil arguments joined + by a "-" + ## Example iex> Cldr.Locale.locale_name_from("en", "Latn", "001", nil) @@ -445,6 +453,12 @@ defmodule Cldr.Locale do Substitute deprectated subtags with a `Cldr.LanguageTag` with their non-deprecated alternatives. + ## Options + + * `language_tag` is any language tag returned by `Cldr.Locale.new/1` + + ## Method + * Replace any deprecated subtags with their canonical values using the alias data. Use the first value in the replacement list, if it exists. Language tag replacements may have multiple parts, such as @@ -457,58 +471,7 @@ defmodule Cldr.Locale do * Get the components of the cleaned-up source tag (languages, scripts, and regions/territories), plus any variants and extensions. - ## Examples - - iex> Cldr.Locale.substitute_aliases Cldr.LanguageTag.Parser.parse!("en-US") - %Cldr.LanguageTag{ - canonical_locale_name: nil, - cldr_locale_name: nil, - extensions: %{}, - gettext_locale_name: nil, - language: "en", - locale: %{}, - private_use: [], - rbnf_locale_name: nil, - requested_locale_name: "en-us", - script: nil, - territory: "US", - transform: %{}, - variant: nil - } - - iex> Cldr.Locale.substitute_aliases Cldr.LanguageTag.Parser.parse!("sh_Arab_AQ") - %Cldr.LanguageTag{ - canonical_locale_name: nil, - cldr_locale_name: nil, - extensions: %{}, - gettext_locale_name: nil, - language: "sr", - locale: %{}, - private_use: [], - rbnf_locale_name: nil, - requested_locale_name: "sh-arab-aq", - script: "Arab", - territory: "AQ", - transform: %{}, - variant: nil - } - - iex> Cldr.Locale.substitute_aliases Cldr.LanguageTag.Parser.parse!("sh_AQ") - %Cldr.LanguageTag{ - canonical_locale_name: nil, - cldr_locale_name: nil, - extensions: %{}, - gettext_locale_name: nil, - language: "sr", - locale: %{}, - private_use: [], - rbnf_locale_name: nil, - requested_locale_name: "sh-aq", - script: "Latn", - territory: "AQ", - transform: %{}, - variant: nil - } + ## Example iex> Cldr.Locale.substitute_aliases Cldr.LanguageTag.Parser.parse!("mo") %Cldr.LanguageTag{ @@ -570,19 +533,21 @@ defmodule Cldr.Locale do defp remove_unknown(%LanguageTag{} = language_tag, :territory), do: language_tag @doc """ - Replace empty subtags within a `Cldr.LanguageTag` with the most likely + Replace empty subtags within a `Cldr.LanguageTag.t` with the most likely subtag. - A subtag is called empty if it is a missing script or region subtag, or it is + ## Options + + * `language_tag` is any language tag returned by `Cldr.Locale.new/1` + + A subtag is called empty if it has a missing script or territory subtag, or it is a base language subtag with the value `und`. In the description below, a subscript on a subtag x indicates which tag it is from: xs is in the source, xm is in a match, and xr is in the final result. - This operation is performed in the following way: - - ### Lookup + ## Lookup - Lookup each of the following in order, and stop on the first match: + Lookup each of the following in order, and stops on the first match: * languages-scripts-regions * languages-regions @@ -590,7 +555,7 @@ defmodule Cldr.Locale do * languages * und-scripts - ### Return + ## Returns * If there is no match,either return * an error value, or @@ -636,8 +601,14 @@ defmodule Cldr.Locale do @doc """ Returns an error tuple for an invalid locale. + ## Options + * `locale_name` is any locale name returned by `Cldr.known_locale_names/0` + ## Returns + + * `{:error, {Cldr.UnknownLocaleError, message}}` + ## Examples iex> Cldr.Locale.locale_error :invalid @@ -654,8 +625,36 @@ defmodule Cldr.Locale do end @doc """ - Returns the map of likely subtags for a subset of available - locale names. + Returns an error tuple for an invalid gettext locale. + + ## Options + + * `locale_name` is any locale name returned by `Cldr.known_gettext_locale_names/0` + + ## Returns + + * `{:error, {Cldr.UnknownLocaleError, message}}` + + ## Examples + + iex> Cldr.Locale.gettext_locale_error :invalid + {Cldr.UnknownLocaleError, "The gettext locale :invalid is not known."} + + """ + @spec gettext_locale_error(Locale.locale_name | LanguageTag.t) :: {Cldr.UnknownLocaleError, String.t} + def gettext_locale_error(%LanguageTag{gettext_locale_name: gettext_locale_name}) do + gettext_locale_error(gettext_locale_name) + end + + def gettext_locale_error(locale_name) do + {Cldr.UnknownLocaleError, "The gettext locale #{inspect locale_name} is not known."} + end + + @doc """ + Returns the map of likely subtags. + + Note that not all locales are guaranteed + to have likely subtags. ## Example @@ -702,6 +701,8 @@ defmodule Cldr.Locale do Returns the likely substags, as a `Cldr.LanguageTag`, for a given locale name. + ## Options + * `locale` is any valid locale name returned by `Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct @@ -723,22 +724,6 @@ defmodule Cldr.Locale do variant: nil } - iex> Cldr.Locale.likely_subtags Cldr.Locale.new!("th") - %Cldr.LanguageTag{ - canonical_locale_name: nil, - cldr_locale_name: nil, - extensions: %{}, - language: "th", - locale: %{}, - private_use: [], - rbnf_locale_name: nil, - requested_locale_name: nil, - script: "Thai", - territory: "TH", - transform: %{}, - variant: nil - } - """ @spec likely_subtags(locale_name) :: LanguageTag.t def likely_subtags(locale_name) when is_binary(locale_name) do @@ -761,6 +746,8 @@ defmodule Cldr.Locale do @doc """ Return a map of the aliases for a given alias key and type + ## Options + * `type` is one of `[:language, :region, :script, :variant, :zone]` * `key` is the substitution key (a language, region, script, variant or zone) @@ -777,6 +764,8 @@ defmodule Cldr.Locale do @doc """ Returns an error tuple for an invalid locale alias. + ## Options + * `locale_name` is any locale name returned by `Cldr.known_locale_names/0` """ @@ -803,4 +792,17 @@ defmodule Cldr.Locale do %{language_tag | territory: territory} end + + @doc """ + Transforms a locale name from the Posix format to the Cldr format + """ + def locale_name_from_posix(nil), do: nil + def locale_name_from_posix(name) when is_binary(name), do: String.replace(name, "_", "-") + + @doc """ + Transforms a locale name from the CLDR format to the Posix format + """ + def locale_name_to_posix(nil), do: nil + def locale_name_to_posix(name) when is_binary(name), do: String.replace(name, "-", "_") + end \ No newline at end of file diff --git a/test/cldr_test.exs b/test/cldr_test.exs index bd528f8c4..69bcc050a 100644 --- a/test/cldr_test.exs +++ b/test/cldr_test.exs @@ -61,4 +61,57 @@ defmodule Cldr.Test do {Cldr.Rbnf.NotAvailable, "The locale name \"zzz\" does not have an RBNF configuration file available"}} end + + test "that locale substitutions are applied" do + assert Cldr.Locale.substitute_aliases(Cldr.LanguageTag.Parser.parse!("en-US")) == + %Cldr.LanguageTag{ + canonical_locale_name: nil, + cldr_locale_name: nil, + extensions: %{}, + gettext_locale_name: nil, + language: "en", + locale: %{}, + private_use: [], + rbnf_locale_name: nil, + requested_locale_name: "en-us", + script: nil, + territory: "US", + transform: %{}, + variant: nil + } + + assert Cldr.Locale.substitute_aliases(Cldr.LanguageTag.Parser.parse!("sh_Arab_AQ")) == + %Cldr.LanguageTag{ + canonical_locale_name: nil, + cldr_locale_name: nil, + extensions: %{}, + gettext_locale_name: nil, + language: "sr", + locale: %{}, + private_use: [], + rbnf_locale_name: nil, + requested_locale_name: "sh-arab-aq", + script: "Arab", + territory: "AQ", + transform: %{}, + variant: nil + } + + assert Cldr.Locale.substitute_aliases(Cldr.LanguageTag.Parser.parse!("sh_AQ")) == + %Cldr.LanguageTag{ + canonical_locale_name: nil, + cldr_locale_name: nil, + extensions: %{}, + gettext_locale_name: nil, + language: "sr", + locale: %{}, + private_use: [], + rbnf_locale_name: nil, + requested_locale_name: "sh-aq", + script: "Latn", + territory: "AQ", + transform: %{}, + variant: nil + } + end end \ No newline at end of file