Packages
localize
0.2.0
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.41.3
0.41.2
0.41.1
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.1
0.30.0
retired
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
0.1.0-alpha.1
Localization (parsing, formatting) of numbers, dates/time/calendar, units of measure, messages and lists. Includes localized collation.
Current section
Files
Jump to
Current section
Files
lib/localize/validity/u.ex
defmodule Localize.Validity.U do
@moduledoc false
# Map BCP47 string keys to their atom equivalents.
# The struct fields use the original BCP47 key names.
@field_mapping %{
"ca" => :ca,
"cf" => :cf,
"co" => :co,
"cu" => :cu,
"dx" => :dx,
"em" => :em,
"fw" => :fw,
"hc" => :hc,
"ka" => :ka,
"kb" => :kb,
"kc" => :kc,
"kf" => :kf,
"kh" => :kh,
"kk" => :kk,
"kn" => :kn,
"kr" => :kr,
"ks" => :ks,
"kv" => :kv,
"lb" => :lb,
"lw" => :lw,
"ms" => :ms,
"mu" => :mu,
"nu" => :nu,
"rg" => :rg,
"sd" => :sd,
"ss" => :ss,
"tz" => :tz,
"va" => :va,
"vt" => :vt
}
@fields Map.values(@field_mapping) |> Enum.sort()
@validity_data Localize.SupplementalData.validity(:u)
@dont_process_keys ["vt", "rg", "sd", "dx", "kr"]
@valid_keys Map.keys(@validity_data)
@process_keys @valid_keys -- @dont_process_keys
@region_subtag_filler "zzzz"
@doc false
def fields do
@fields
end
def field_mapping do
@field_mapping
end
@doc """
Decodes and validates that a given value is valid
for a given key.
Returns both the canonical key
and the canonical value or an error.
"""
@dont_atomize ["tz", "rg", "sd", "kr"]
def decode(key, value) when key in @dont_atomize do
with {:ok, value} <- valid(key, value) do
{:ok, {map(key), value}}
else
{:error, _value} ->
{:error, invalid_value_error(key, value)}
end
end
def decode(key, value) do
with {:ok, value} <- valid(key, value) do
{:ok, {map(key), atomize(value)}}
else
{:error, _value} ->
{:error, invalid_value_error(key, value)}
end
end
@doc """
Encodes a key and value into the
form required for a string version
of a language tag.
"""
# Calendar names may be compound like
# islamic-rgsa
def encode(:ca = key, value) do
unmapped_key = unmap(key)
value =
unmapped_key
|> encode_key(value)
|> String.replace("_", "-")
{unmapped_key, value}
end
def encode(key, value) do
unmapped_key = unmap(key)
{unmapped_key, encode_key(unmapped_key, value)}
end
# Encode key functions take the form that is
# in the language tag locale struct and encodes it
# back to what is required in a textual form of
# the locale.
for {key, values} <- @validity_data, key in @process_keys do
inverted_values =
Enum.map(values, fn
{k, v} when is_list(v) -> Enum.map(v, fn tz -> {tz, k} end)
{k, nil} -> {String.to_atom(k), k}
{k, v} -> {String.to_atom(v), k}
end)
|> Elixir.List.flatten()
|> Map.new()
defp encode_key(unquote(key), value) when value in unquote(Map.keys(inverted_values)) do
unquote(Macro.escape(inverted_values))
|> Map.fetch!(value)
end
end
defp encode_key("cu", value) do
value
|> Atom.to_string()
|> String.downcase()
end
defp encode_key("rg", value) when is_atom(value) do
value
|> Atom.to_string()
|> String.downcase()
|> Kernel.<>(@region_subtag_filler)
end
defp encode_key("rg", value) do
value
end
defp encode_key("sd", value) do
value
end
defp encode_key("vt", value) do
value
|> String.downcase()
end
defp encode_key("kr", values) when is_list(values) do
values
|> Enum.map(&to_string/1)
|> Enum.map(&String.downcase/1)
|> Enum.join("-")
end
defp encode_key("dx", value) when is_atom(value) do
value
|> Atom.to_string()
|> String.downcase()
end
defp encode_key("dx", values) when is_list(values) do
values
|> Enum.map(&to_string/1)
|> Enum.map(&String.downcase/1)
|> Enum.join("-")
end
# valid function check that the value provided
# is acceptable for the given key.
for {key, values} <- @validity_data, key in (@process_keys -- ["tz"]) do
defp valid(unquote(key), value) when value in unquote(Map.keys(values)) do
unquote(Macro.escape(values))
|> get_value(unquote(key), value)
|> maybe_get_list_head()
|> wrap(:ok)
end
end
# For timezones a nil value means its an acceptable
# time zone name but it needs to be resolved to its
# canonical version. For example, est5edt becomes usnyc
@tz_values @validity_data["tz"]
defp valid("tz", value) do
case Map.fetch(@tz_values, value) do
{:ok, nil} ->
{:ok, preferred} = preferred_timezone(value)
{:ok, %{aliases: aliases}} = Map.fetch(Localize.SupplementalData.timezones(), preferred)
{:ok, hd(aliases)}
{:ok, values} when is_list(values) ->
{:ok, hd(values)}
end
end
# Calendar names may be compound like
# islamic-rgsa
defp valid("ca", values) when is_list(values) do
valid("ca", Enum.join(values, "_"))
end
# Codepoints?
defp valid("vt", value) do
{:ok, value}
end
defp valid("rg", <<value::binary-size(2), "#{@region_subtag_filler}">>) do
case Localize.Validity.Territory.validate(value) do
{:ok, territory, _status} -> {:ok, territory}
other -> other
end
end
defp valid("rg", value) do
case Localize.Validity.Subdivision.validate(value) do
{:ok, subdivision, _status} -> {:ok, subdivision}
other -> other
end
end
defp valid("sd", value) do
case Localize.Validity.Subdivision.validate(value) do
{:ok, subdivision, _status} -> {:ok, subdivision}
other -> other
end
end
# dx can be more than one script
defp valid("dx", value) when is_binary(value) do
case Localize.Validity.Script.validate(value) do
{:ok, script, _status} -> {:ok, script}
other -> other
end
end
defp valid("dx" = key, values) when is_list(values) do
Enum.reduce_while(values, {:ok, []}, fn value, {:ok, acc} ->
case Localize.Validity.Script.validate(value) do
{:ok, script, _status} -> {:cont, {:ok, [script | acc]}}
{:error, _script} -> {:halt, {:error, invalid_value_error(key, value)}}
end
end)
end
@kr_valid_values @validity_data["kr"] |> Map.delete("REORDER_CODE")
defp valid("kr", value) when is_binary(value) do
cond do
Map.has_key?(@kr_valid_values, value) ->
{:ok, [atomize(value)]}
true ->
case Localize.Validity.Script.validate(value) do
{:ok, script, _status} -> {:ok, [script]}
other -> other
end
end
end
defp valid("kr" = key, values) when is_list(values) do
Enum.reduce_while(values, {:ok, []}, fn value, {:ok, acc} ->
case valid("kr", value) do
{:ok, [reorder_code]} -> {:cont, {:ok, [reorder_code | acc]}}
{:error, _reorder_code} -> {:halt, {:error, invalid_value_error(key, value)}}
end
end)
|> case do
{:ok, list} -> {:ok, Enum.reverse(list)}
other -> other
end
end
defp valid(key, value) when key in @valid_keys do
{:error, invalid_value_error(key, value)}
end
defp valid(key, _value) do
{:error, invalid_key_error(key)}
end
defp preferred_timezone(timezone) do
Localize.SupplementalData.timezones()
|> Map.fetch!(timezone)
|> Map.fetch(:preferred)
end
defp get_value(values_map, "cu", value) do
(Map.get(values_map, value) || value)
|> String.upcase()
end
defp get_value(map, _key, value) do
Map.get(map, value) || value
end
defp map(key) do
Map.fetch!(@field_mapping, key)
end
defp unmap(key) when is_atom(key) do
Atom.to_string(key)
end
defp wrap(term, atom) do
{atom, term}
end
defp maybe_get_list_head([head | _rest]) do
head
end
defp maybe_get_list_head(other) do
other
end
defp atomize(value) when is_atom(value) do
value
end
defp atomize(value) when is_binary(value) do
String.to_atom(value)
end
defp atomize(other) do
other
end
@doc false
def invalid_value_error(key, value) do
Localize.InvalidSubtagError.exception(key: key, value: value, reason: nil)
end
@doc false
def invalid_key_error(key) do
Localize.InvalidSubtagError.exception(
key: key,
value: nil,
reason: :invalid_key
)
end
end