Skip to content

Commit

Permalink
Avoid compile-time dependency to Gettext backend (#391)
Browse files Browse the repository at this point in the history
  • Loading branch information
whatyouhide authored Aug 18, 2024
1 parent c602ea9 commit 57c6249
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 285 deletions.
33 changes: 25 additions & 8 deletions lib/gettext.ex
Original file line number Diff line number Diff line change
Expand Up @@ -627,19 +627,36 @@ defmodule Gettext do

@doc false
defmacro __using__(opts) do
quote do
opts = unquote(opts)

if Keyword.has_key?(opts, :backend) do
raise "not implemented yet"
opts =
if Macro.quoted_literal?(opts) do
Macro.prewalk(opts, &expand_alias(&1, __CALLER__))
else
# TODO: Deprecate this branch
require Gettext.Backend
Gettext.Backend.__using__(opts)
opts
end

case Keyword.keyword?(opts) && Keyword.fetch(opts, :backend) do
{:ok, backend} ->
quote do
@__gettext_backend__ unquote(backend)
import Gettext.Macros
end

_other ->
quote do
# TODO: Deprecate this branch
use Gettext.Backend, unquote(opts)
end
end
end

defp expand_alias({:__aliases__, _, _} = als, env) do
Macro.expand(als, %{env | function: {:__gettext__, 1}})
end

defp expand_alias(other, _env) do
other
end

@doc """
Gets the global Gettext locale for the current process.
Expand Down
269 changes: 24 additions & 245 deletions lib/gettext/backend.ex
Original file line number Diff line number Diff line change
Expand Up @@ -175,255 +175,34 @@ defmodule Gettext.Backend do
{: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`.
Translates a message.
`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`.
See `Gettext.gettext/3` for more information.
"""
@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 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 """
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")
Translates a plural message.
See `Gettext.ngettext/5` for more information.
"""
@macrocallback gettext_comment(comment :: String.t()) :: :ok
@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
6 changes: 5 additions & 1 deletion lib/gettext/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ defmodule Gettext.Compiler do

unquote(macros())

# These are the two functions we generated inside the backend.
# These are the two functions we generate inside the backend.

@impl Gettext.Backend
def lgettext(locale, domain, msgctxt \\ nil, msgid, bindings)

@impl Gettext.Backend
def lngettext(locale, domain, msgctxt \\ nil, msgid, msgid_plural, n, bindings)

unquote(compile_po_files(env, known_po_files, opts))
Expand Down
15 changes: 14 additions & 1 deletion lib/gettext/extractor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,23 @@ defmodule Gettext.Extractor do
Note that this function doesn't perform any operation on the filesystem.
"""
@spec extract(Macro.Env.t(), module, binary, binary, binary | {binary, binary}, [binary]) :: :ok
@spec extract(
Macro.Env.t(),
backend :: module,
domain :: binary | :default,
msgctxt :: binary,
id :: binary | {binary, binary},
extracted_comments :: [binary]
) :: :ok
def extract(%Macro.Env{} = caller, backend, domain, msgctxt, id, extracted_comments) do
format_flag = backend.__gettext__(:interpolation).message_format()

domain =
case domain do
:default -> backend.__gettext__(:default_domain)
string when is_binary(string) -> string
end

message =
create_message_struct(
id,
Expand Down
Loading

0 comments on commit 57c6249

Please sign in to comment.