From c602ea9d1a2ee5869216a653337b3e66f2b70b10 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 17 Aug 2024 09:43:31 +0200 Subject: [PATCH] Introduce "use Gettext.Backend" (#390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/gettext.ex | 55 ++++--------------------- lib/gettext/backend.ex | 75 ++++++++++++++++++++++++++++++++++- test/gettext/backend_test.exs | 18 +++++++++ 3 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 test/gettext/backend_test.exs diff --git a/lib/gettext.ex b/lib/gettext.ex index eb47d931..73eb3a65 100644 --- a/lib/gettext.ex +++ b/lib/gettext.ex @@ -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. @@ -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 diff --git a/lib/gettext/backend.ex b/lib/gettext/backend.ex index 063df18d..aa2cb1f3 100644 --- a/lib/gettext/backend.ex +++ b/lib/gettext/backend.ex @@ -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. diff --git a/test/gettext/backend_test.exs b/test/gettext/backend_test.exs new file mode 100644 index 00000000..534098e9 --- /dev/null +++ b/test/gettext/backend_test.exs @@ -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