Skip to content

Commit

Permalink
Introduce "use Gettext.Backend" (#390)
Browse files Browse the repository at this point in the history
Co-authored-by: José Valim <jose.valim@dashbit.co>
  • Loading branch information
whatyouhide and josevalim authored Aug 17, 2024
1 parent e6e9fe8 commit c602ea9
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 48 deletions.
55 changes: 8 additions & 47 deletions lib/gettext.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ defmodule Gettext do
## Gettext API
To use `Gettext`, a module that calls `use Gettext` (referred to below as a
To use `Gettext`, a module that calls `use Gettext.Backend` (referred to below as a
"backend") has to be defined:
defmodule MyApp.Gettext do
use Gettext, otp_app: :my_app
use Gettext.Backend, otp_app: :my_app
end
This automatically defines some macros in the `MyApp.Gettext` backend module.
Expand Down Expand Up @@ -627,55 +627,16 @@ defmodule Gettext do

@doc false
defmacro __using__(opts) do
# From Elixir v1.13 onwards, use compile_env
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}
if Keyword.has_key?(opts, :backend) do
raise "not implemented yet"
else
# TODO: Deprecate this branch
require Gettext.Backend
Gettext.Backend.__using__(opts)
end

defoverridable handle_missing_translation: 5, handle_missing_plural_translation: 7
end
end

Expand Down
75 changes: 74 additions & 1 deletion lib/gettext/backend.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,81 @@
defmodule Gettext.Backend do
@moduledoc """
Behaviour that defines the macros that a Gettext backend has to implement.
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.
Expand Down
18 changes: 18 additions & 0 deletions test/gettext/backend_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule Gettext.BackendTest do
use ExUnit.Case, async: true

describe "use Gettext.Backend" do
test "creates a backend" do
body =
quote do
use Gettext.Backend,
otp_app: :test_application
end

{:module, mod, _bytecode, :ok} = Module.create(TestBackend, body, __ENV__)

assert mod.__gettext__(:otp_app) == :test_application
assert mod.__info__(:attributes)[:behaviour] == [Gettext.Backend]
end
end
end

0 comments on commit c602ea9

Please sign in to comment.