Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid compile-time dependency to Gettext backend #391

Merged
merged 3 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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