Current section

Files

Jump to
localize lib localize validity u.ex
Raw

lib/localize/validity/u.ex

defmodule Localize.Validity.U do
@moduledoc false
alias Localize.Validity.U.Fields
# Map BCP47 string keys to their atom equivalents.
# The struct fields use the original BCP47 key names.
@field_mapping Fields.field_mapping()
@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.fields()
def field_mapping, do: Fields.field_mapping()
@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