209 lines
7.1 KiB
Elixir
209 lines
7.1 KiB
Elixir
defmodule Gettext.Backend do
|
|
@moduledoc """
|
|
Defines a Gettext backend.
|
|
|
|
## Usage
|
|
|
|
A Gettext **backend** must `use` this module.
|
|
|
|
defmodule MyApp.Gettext do
|
|
use Gettext.Backend, otp_app: :my_app
|
|
end
|
|
|
|
Using this module generates all the callbacks required by the `Gettext.Backend`
|
|
behaviour into the module that uses it. For more options and information,
|
|
see `Gettext`.
|
|
|
|
> #### `use Gettext.Backend` Is a Recent Feature {: .info}
|
|
>
|
|
> Before version v0.26.0, you could only `use Gettext` to generate a backend.
|
|
>
|
|
> Version v0.26.0 changes the way backends work so that now a Gettext backend
|
|
> must `use Gettext.Backend`, while to use the functions in the backend you
|
|
> will do `use Gettext, backend: MyApp.Gettext`.
|
|
"""
|
|
|
|
defmacro __using__(opts) do
|
|
# TODO: From Elixir v1.13 onwards, use compile_env and remove this if.
|
|
env_fun = if function_exported?(Module, :attributes_in, 1), do: :compile_env, else: :get_env
|
|
|
|
quote do
|
|
require Logger
|
|
|
|
opts = unquote(opts)
|
|
otp_app = Keyword.fetch!(opts, :otp_app)
|
|
|
|
@gettext_opts opts
|
|
|> Keyword.merge(Application.unquote(env_fun)(otp_app, __MODULE__, []))
|
|
|> Keyword.put_new(:interpolation, Gettext.Interpolation.Default)
|
|
|
|
@interpolation Keyword.fetch!(@gettext_opts, :interpolation)
|
|
|
|
@before_compile Gettext.Compiler
|
|
|
|
def handle_missing_bindings(exception, incomplete) do
|
|
_ = Logger.error(Exception.message(exception))
|
|
incomplete
|
|
end
|
|
|
|
defoverridable handle_missing_bindings: 2
|
|
|
|
def handle_missing_translation(_locale, domain, _msgctxt, msgid, bindings) do
|
|
Gettext.Compiler.warn_if_domain_contains_slashes(domain)
|
|
|
|
with {:ok, interpolated} <- @interpolation.runtime_interpolate(msgid, bindings),
|
|
do: {:default, interpolated}
|
|
end
|
|
|
|
def handle_missing_plural_translation(
|
|
_locale,
|
|
domain,
|
|
_msgctxt,
|
|
msgid,
|
|
msgid_plural,
|
|
n,
|
|
bindings
|
|
) do
|
|
Gettext.Compiler.warn_if_domain_contains_slashes(domain)
|
|
string = if n == 1, do: msgid, else: msgid_plural
|
|
bindings = Map.put(bindings, :count, n)
|
|
|
|
with {:ok, interpolated} <- @interpolation.runtime_interpolate(string, bindings),
|
|
do: {:default, interpolated}
|
|
end
|
|
|
|
defoverridable handle_missing_translation: 5, handle_missing_plural_translation: 7
|
|
end
|
|
end
|
|
|
|
@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:
|
|
|
|
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 a message.
|
|
|
|
See `Gettext.gettext/3` for more information.
|
|
"""
|
|
@doc since: "0.26.0"
|
|
@callback lgettext(
|
|
Gettext.locale(),
|
|
domain :: String.t(),
|
|
msgctxt :: String.t() | nil,
|
|
msgid :: String.t(),
|
|
bindings :: map()
|
|
) ::
|
|
{:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}
|
|
|
|
@doc """
|
|
Translates a plural message.
|
|
|
|
See `Gettext.ngettext/5` for more information.
|
|
"""
|
|
@doc since: "0.26.0"
|
|
@callback lngettext(
|
|
Gettext.locale(),
|
|
domain :: String.t(),
|
|
msgctxt :: String.t() | nil,
|
|
msgid :: String.t(),
|
|
msgid_plural :: String.t(),
|
|
n :: non_neg_integer(),
|
|
bindings :: map()
|
|
) ::
|
|
{:ok, String.t()} | {:default, String.t()} | {:missing_bindings, String.t(), [atom]}
|
|
end
|