lib/gettext/backend.ex

defmodule Gettext.Backend do
  @moduledoc """
  Behaviour that defines the macros that a Gettext backend has to implement.
  """

  @doc """
  Default handling for missing bindings.

  This function is called when there are missing bindings in a message. It
  takes a `Gettext.MissingBindingsError` struct and the message with the
  wrong bindings left as is with the `%{}` syntax.

  For example, if something like this is called:

      MyApp.Gettext.gettext("Hello %{name}, your favorite color is %{color}", name: "Jane", color: "blue")

  and our `it/LC_MESSAGES/default.po` looks like this:

      msgid "Hello %{name}, your favorite color is %{color}"
      msgstr "Ciao %{name}, il tuo colore preferito è %{colour}" # (typo)

  then Gettext will call:

      MyApp.Gettext.handle_missing_bindings(exception, "Ciao Jane, il tuo colore preferito è %{colour}")

  where `exception` is a struct that looks like this:

      %Gettext.MissingBindingsError{
        backend: MyApp.Gettext,
        domain: "default",
        locale: "it",
        msgid: "Ciao %{name}, il tuo colore preferito è %{colour}",
        bindings: [:colour],
      }

  The return value of the `c:handle_missing_bindings/2` callback is used as the
  translated string that the message macros and functions return.

  The default implementation for this function uses `Logger.error/1` to warn
  about the missing binding and returns the translated message with the
  incomplete bindings.

  This function can be overridden. For example, to raise when there are missing
  bindings:

      def handle_missing_bindings(exception, _incomplete) do
        raise exception
      end

  """
  @callback handle_missing_bindings(Gettext.MissingBindingsError.t(), binary) ::
              binary | no_return

  @doc """
  Default handling for messages with a missing message.

  When a Gettext function/macro is called with a string to translate
  into a locale but that locale doesn't provide a message for that
  string, this callback is invoked. `msgid` is the string that Gettext
  tried to translate.

  This function should return `{:ok, translated}` if a message can be
  fetched or constructed for the given string. If you cannot find a
  message, it should return `{:default, translated}`, where the
  translated string defaults to the interpolated msgid. You can, however,
  customize the default to, for example, pick the message from the
  default locale. The important is to return `:default` instead of `:ok`
  whenever the result does not quite match the requested locale.

  Earlier versions of this library provided a callback without msgctxt.
  Users implementing that callback will still get the same results,
  but they are encouraged to switch to the new 5-argument version.
  """
  @callback handle_missing_translation(
              Gettext.locale(),
              domain :: String.t(),
              msgctxt :: String.t(),
              msgid :: String.t(),
              bindings :: map()
            ) ::
              {:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}

  @doc """
  Default handling for plural messages with a missing message.

  Same as `c:handle_missing_translation/5`, but for plural messages.
  In this case, `n` is the number used for pluralizing the translated string.

  Earlier versions of this library provided a callback without msgctxt.
  Users implementing that callback will still get the same results,
  but they are encouraged to switch to the new 7-argument version.
  """
  @callback handle_missing_plural_translation(
              Gettext.locale(),
              domain :: String.t(),
              msgctxt :: String.t(),
              msgid :: String.t(),
              msgid_plural :: String.t(),
              n :: non_neg_integer(),
              bindings :: map()
            ) ::
              {:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}

  @doc """
  Translates the given `msgid` with a given context (`msgctxt`) in the given `domain`.

  `bindings` is a map of bindings to support interpolation.

  See also `Gettext.dpgettext/5`.
  """
  @macrocallback dpgettext(
                   domain :: Macro.t(),
                   msgctxt :: String.t(),
                   msgid :: String.t(),
                   bindings :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Same as `dpgettext(domain, msgctxt, msgid, %{})`.

  See also `Gettext.dpgettext/5`.
  """
  @macrocallback dpgettext(domain :: Macro.t(), msgctxt :: String.t(), msgid :: String.t()) ::
                   Macro.t()

  @doc """
  Translates the given `msgid` in the given `domain`.

  `bindings` is a map of bindings to support interpolation.

  See also `Gettext.dgettext/4`.
  """
  @macrocallback dgettext(domain :: Macro.t(), msgid :: String.t(), bindings :: Macro.t()) ::
                   Macro.t()

  @doc """
  Same as `dgettext(domain, msgid, %{})`.

  See also `Gettext.dgettext/4`.
  """
  @macrocallback dgettext(domain :: Macro.t(), msgid :: String.t()) :: Macro.t()

  @doc """
  Translates the given `msgid` with the given context (`msgctxt`).

  `bindings` is a map of bindings to support interpolation.

  See also `Gettext.pgettext/4`.
  """
  @macrocallback pgettext(msgctxt :: String.t(), msgid :: String.t(), bindings :: Macro.t()) ::
                   Macro.t()

  @doc """
  Same as `pgettext(msgctxt, msgid, %{})`.

  See also `Gettext.pgettext/4`.
  """
  @macrocallback pgettext(msgctxt :: String.t(), msgid :: String.t()) :: Macro.t()

  @doc """
  Same as `dgettext("default", msgid, %{})`, but will use a per-backend
  configured default domain if provided.

  See also `Gettext.gettext/3`.
  """
  @macrocallback gettext(msgid :: String.t(), bindings :: Macro.t()) :: Macro.t()

  @doc """
  Same as `gettext(msgid, %{})`.

  See also `Gettext.gettext/3`.
  """
  @macrocallback gettext(msgid :: String.t()) :: Macro.t()

  @doc """
  Translates the given plural message (`msgid` + `msgid_plural`) with the given context (`msgctxt`)
  in the given `domain`.

  `n` is an integer used to determine how to pluralize the
  message. `bindings` is a map of bindings to support interpolation.

  See also `Gettext.dpngettext/7`.
  """
  @macrocallback dpngettext(
                   domain :: Macro.t(),
                   msgctxt :: String.t(),
                   msgid :: String.t(),
                   msgid_plural :: String.t(),
                   n :: Macro.t(),
                   bindings :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Same as `dpngettext(domain, msgctxt, msgid, msgid_plural, n, %{})`.

  See also `Gettext.dpngettext/7`.
  """
  @macrocallback dpngettext(
                   domain :: Macro.t(),
                   msgctxt :: String.t(),
                   msgid :: String.t(),
                   msgid_plural :: String.t(),
                   n :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Translates the given plural message (`msgid` + `msgid_plural`) in the
  given `domain`.

  `n` is an integer used to determine how to pluralize the
  message. `bindings` is a map of bindings to support interpolation.

  See also `Gettext.dngettext/6`.
  """
  @macrocallback dngettext(
                   domain :: Macro.t(),
                   msgid :: String.t(),
                   msgid_plural :: String.t(),
                   n :: Macro.t(),
                   bindings :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Same as `dngettext(domain, msgid, msgid_plural, n, %{})`.

  See also `Gettext.dngettext/6`.
  """
  @macrocallback dngettext(
                   domain :: Macro.t(),
                   msgid :: String.t(),
                   msgid_plural :: String.t(),
                   n :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Translates the given plural message (`msgid` + `msgid_plural`) with the given context (`msgctxt`).

  `n` is an integer used to determine how to pluralize the
  message. `bindings` is a map of bindings to support interpolation.

  See also `Gettext.pngettext/6`.
  """
  @macrocallback pngettext(
                   msgctxt :: String.t(),
                   msgid :: String.t(),
                   msgid_plural :: String.t(),
                   n :: Macro.t(),
                   bindings :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Same as `pngettext(msgctxt, msgid, msgid_plural, n, %{})`.

  See also `Gettext.pngettext/6`.
  """
  @macrocallback pngettext(
                   msgctxt :: String.t(),
                   msgid :: String.t(),
                   msgid_plural :: String.t(),
                   n :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Same as `dngettext("default", msgid, msgid_plural, n, bindings)`, but will
  use a per-backend configured default domain if provided.

  See also `Gettext.ngettext/5`.
  """
  @macrocallback ngettext(
                   msgid :: String.t(),
                   msgid_plural :: String.t(),
                   n :: Macro.t(),
                   bindings :: Macro.t()
                 ) :: Macro.t()

  @doc """
  Same as `ngettext(msgid, msgid_plural, n, %{})`.

  See also `Gettext.ngettext/5`.
  """
  @macrocallback ngettext(msgid :: String.t(), msgid_plural :: String.t(), n :: Macro.t()) ::
                   Macro.t()

  @doc """
  Marks the given message for extraction and returns it unchanged.

  This macro can be used to mark a message for extraction when `mix
  gettext.extract` is run. The return value is the given string, so that this
  macro can be used seamlessly in place of the string to extract.

  ## Examples

      MyApp.Gettext.dgettext_noop("errors", "Error found!")
      #=> "Error found!"

  """
  @macrocallback dgettext_noop(domain :: String.t(), msgid :: String.t()) :: Macro.t()

  @doc """
  Same as `dgettext_noop("default", msgid)`.
  """
  @macrocallback gettext_noop(msgid :: String.t()) :: Macro.t()

  @doc """
  Marks the given message for extraction and returns
  `{msgid, msgid_plural}`.

  This macro can be used to mark a message for extraction when `mix
  gettext.extract` is run. The return value of this macro is `{msgid,
  msgid_plural}`.

  ## Examples

      my_fun = fn {msgid, msgid_plural} ->
        # do something with msgid and msgid_plural
      end

      my_fun.(MyApp.Gettext.dngettext_noop("errors", "One error", "%{count} errors"))

  """
  @macrocallback dngettext_noop(
                   domain :: Macro.t(),
                   msgid :: String.t(),
                   msgid_plural :: String.t()
                 ) :: Macro.t()

  @doc """
  Same as `dngettext_noop("default", msgid, mgsid_plural)`, but will use a
  per-backend configured default domain if provided.
  """
  @macrocallback ngettext_noop(msgid :: String.t(), msgid_plural :: String.t()) :: Macro.t()

  @doc """
  Stores an "extracted comment" for the next message.

  This macro can be used to add comments (Gettext refers to such
  comments as *extracted comments*) to the next message that will
  be extracted. Extracted comments will be prefixed with `#.` in POT
  files.

  Calling this function multiple times will accumulate the comments;
  when another Gettext macro (such as `c:gettext/2`) is called,
  the comments will be extracted and attached to that message, and
  they will be flushed so as to start again.

  This macro always returns `:ok`.

  ## Examples

      MyApp.Gettext.gettext_comment("The next message is awesome")
      MyApp.Gettext.gettext_comment("Another comment for the next message")
      MyApp.Gettext.gettext("The awesome message")

  """
  @macrocallback gettext_comment(comment :: String.t()) :: :ok
end