lib/cadastre/language.ex

defmodule Cadastre.Language do
  @moduledoc """
  Language implementation.
  """
  @external_resource Application.app_dir(:cadastre, "priv/data/languages.etf")

  @enforce_keys [:id]
  defstruct [:id]

  @type id :: <<_::16>>
  @type t :: %__MODULE__{id: id}

  external_data = @external_resource |> File.read!() |> :erlang.binary_to_term()
  ids = external_data |> Enum.map(&elem(&1, 0))

  @doc """
  Returns all ids (ISO_639-2).

  ## Examples

      iex> Cadastre.Language.ids() |> Enum.take(10)
      ["aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av"]

  """
  @spec ids :: [id]
  def ids, do: unquote(ids)

  @doc """
  Returns all languages.

  ## Examples

      iex> Cadastre.Language.all() |> Enum.take(3)
      [
        %Cadastre.Language{id: "aa"},
        %Cadastre.Language{id: "ab"},
        %Cadastre.Language{id: "ae"}
      ]

      iex> Cadastre.Language.all() |> Enum.count()
      179

  """
  @spec all :: [t]
  def all, do: ids() |> Enum.map(&%__MODULE__{id: &1})

  @doc """
  Returns `%Cadastre.Language{}` for valid `id` or `nil` for invalid `id`.

  ## Examples

      iex> Cadastre.Language.new("nl")
      %Cadastre.Language{id: "nl"}

      iex> Cadastre.Language.new("NL")
      %Cadastre.Language{id: "nl"}

      iex> Cadastre.Language.new("xx")
      nil

  """
  @spec new(id | any) :: t | nil
  def new(id) when id in unquote(ids), do: %__MODULE__{id: id}

  def new(str) when is_binary(str) do
    id = str |> String.downcase()

    case id in ids() do
      true -> %__MODULE__{id: id}
      _ -> nil
    end
  end

  def new(_), do: nil

  @doc """
  Returns language name translation for `locale`.

  ## Examples

      iex> Cadastre.Language.new("nl") |> Cadastre.Language.name("be")
      "галандская"

      iex> Cadastre.Language.new("nl") |> Cadastre.Language.name(":)")
      "Dutch"

      iex> Cadastre.Language.name("something wrong", "be")
      nil

  """
  @spec name(t, id) :: String.t()
  def name(language, locale)

  external_data
  |> Enum.each(fn {id, translations} ->
    translations = translations |> Map.new()
    en = translations |> Map.fetch!("en")

    def name(%__MODULE__{id: unquote(id)}, locale) do
      unquote(Macro.escape(translations)) |> Map.get(locale, unquote(en))
    end
  end)

  def name(_, _), do: nil

  @doc """
  Returns language native name.

  ## Examples

      iex> Cadastre.Language.new("nl") |> Cadastre.Language.native_name()
      "Nederlands"

      iex> Cadastre.Language.native_name("something wrong")
      nil

  """
  @spec native_name(t) :: String.t()
  def native_name(%__MODULE__{id: id} = language), do: name(language, id)
  def native_name(_), do: nil
end