lib/cldr/plug/plug_accept_language.ex

defmodule Cldr.Plug.AcceptLanguage do
  @moduledoc """
  Parses the accept-language header if one is available and sets
  `conn.private[:cldr_locale]` accordingly.  The locale can
  be later retrieved by `Cldr.Plug.AcceptLanguage.get_cldr_locale/1`

  ## Options

  * `:cldr_backend` is any backend module. The default
    is `Cldr.default_backend/0`. If no `:cldr_backend`
    option is provided and no default backend is configured
    then an exception will be raised.

  * `:no_match_log_level` determines the logging level for
    the case when no matching locale is configured to meet the users
    request. The default is `:warn`. If set to `nil` then no logging
    is performed.

  ## Example

      # Using a specific backend to validate
      # and match locales
      plug Cldr.Plug.AcceptLanguage,
        cldr_backend: MyApp.Cldr

      # Using the default backend to validate
      # and match locales
      plug Cldr.Plug.AcceptLanguage

  """

  import Plug.Conn
  require Logger

  @language_header "accept-language"
  @default_log_level :warn

  @doc false
  def init(options \\ []) do
    backend = Keyword.get_lazy(options, :cldr_backend, &Cldr.default_backend!/0)
    log_level = Keyword.get(options, :no_match_log_level, @default_log_level)
    %{backend: backend, log_level: log_level}
  end

  @doc false
  def call(conn, options) do
    case get_req_header(conn, @language_header) do
      [accept_language] ->
        put_private(conn, :cldr_locale, best_match(accept_language, options))

      [accept_language | _] ->
        put_private(conn, :cldr_locale, best_match(accept_language, options))

      [] ->
        put_private(conn, :cldr_locale, nil)
    end
  end

  @doc """
  Returns the locale which is the best match for the provided
  accept-language header

  """
  def best_match(nil, _) do
    nil
  end

  def best_match(accept_language, options) do
    case Cldr.AcceptLanguage.best_match(accept_language, options.backend) do
      {:ok, locale} ->
        locale

      {:error, {Cldr.NoMatchingLocale = exception, reason}} ->
        if options.log_level,
          do: Logger.log(options.log_level, "#{inspect(exception)}: #{reason}")

        nil

      {:error, {exception, reason}} ->
        Logger.warn("#{inspect(exception)}: #{reason}")
        nil
    end
  end

  @doc """
  Return the locale set by `Cldr.Plug.AcceptLanguage`

  """
  def get_cldr_locale(conn) do
    conn.private[:cldr_locale]
  end
end