From 31bab5f54cefbd33fbdbbd51adb8ce183cfbe50a Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sun, 18 Aug 2024 14:43:48 +0200 Subject: [PATCH 1/5] Deprecate "use Gettext" for backends and improve docs --- lib/gettext.ex | 221 ++++++++++++------------ lib/gettext/plural.ex | 4 +- mix.exs | 12 +- test/gettext/extractor_test.exs | 8 +- test/gettext_test.exs | 45 +++-- test/mix/tasks/gettext.extract_test.exs | 10 +- 6 files changed, 168 insertions(+), 132 deletions(-) diff --git a/lib/gettext.ex b/lib/gettext.ex index e6f021b..89a5109 100644 --- a/lib/gettext.ex +++ b/lib/gettext.ex @@ -20,7 +20,7 @@ defmodule Gettext do language). This means that, at the very least, switching from a hardcoded string to a Gettext call is harmless. - 2. It serves as the message ID to which translations will be mapped. + 2. It serves as the **message ID** to which translations will be mapped. An example translation workflow is as follows. @@ -78,51 +78,55 @@ defmodule Gettext do ## Gettext API - To use `Gettext`, a module that calls `use Gettext.Backend` (referred to below as a - "backend") has to be defined: + To use Gettext, you will need a **backend module** which stores and retrieves + translations from PO files. You can create such a module by using `Gettext.Backend`: defmodule MyApp.Gettext do use Gettext.Backend, otp_app: :my_app end - This automatically defines some macros in the `MyApp.Gettext` backend module. - Here are some examples: + Now, you can import all the necessary translation macros (defined in `Gettext.Macros`) + into any module by using `Gettext`: - import MyApp.Gettext + defmodule MyApp.SomeModule do + use Gettext, backend: MyApp.Gettext - # Simple message - gettext("Here is the string to translate") + def showcase_gettext do + # Simple message + gettext("Hello world") - # Plural message - ngettext( - "Here is the string to translate", - "Here are the strings to translate", - 3 - ) + # Plural message + ngettext( + "Here is the string to translate", + "Here are the strings to translate", + 3 + ) - # Domain-based message - dgettext("errors", "Here is the error message to translate") + # Domain-based message + dgettext("errors", "Here is the error message to translate") - # Context-based message - pgettext("email", "Email text to translate") - - # All of the above - dpngettext( - "errors", - "context", - "Here is the string to translate", - "Here are the strings to translate", - 3 - ) + # Context-based message + pgettext("email", "Email text to translate") + end + end The arguments for the Gettext macros and their order can be derived from - their names. For `dpgettext/4` the arguments are: `domain`, `context`, - `msgid`, `bindings` (default to `%{}`). + their names. For example, for [`dpgettext/4`](`Gettext.Macros.dpgettext/4`) + the arguments are: `domain`, `context`, `msgid`, `bindings` (default to `%{}`). Messages are looked up from `.po` files. In the following sections we will explore exactly what are those files before we explore the "Gettext API" in detail. + > #### Recent Updates {: .info} + > + > Before v0.26.0 of this library, the workflow described in this section + > was slightly different. Check out [the + > changelog](https://github.com/elixir-gettext/gettext/blob/main/CHANGELOG.md) for more + > details, but the gist is that `use Gettext` used to define macros in the calling module. + > This created heavy compile-time dependencies which would cause slow recompilation + > in larger applications. + ## Messages Messages are stored inside PO (Portable Object) files, with a `.po` @@ -165,7 +169,9 @@ defmodule Gettext do # Look for messages in my_app/priv/messages instead of # my_app/priv/gettext - use Gettext, otp_app: :my_app, priv: "priv/messages" + use Gettext.Backend, + otp_app: :my_app, + priv: "priv/messages" The messages directory specified by the `:priv` option should be a directory inside `priv/`, otherwise some things won't work as expected. @@ -236,55 +242,14 @@ defmodule Gettext do ### Using macros - Each module that calls `use Gettext` is usually referred to as a "Gettext - backend", as it implements the `Gettext.Backend` behaviour. When a module - calls `use Gettext`, the following macros are automatically - defined inside it: - - * `gettext/2` - * `dgettext/3` - * `pgettext/3` - * `dpgettext/4` - * `ngettext/4` - * `dngettext/6` - * `pngettext/6` - * `dpngettext/6` - * all macros above with a `_noop` suffix (and without accepting bindings), for - example `pgettext_noop/2` - - Supposing the caller module is `MyApp.Gettext`, the macros mentioned above - behave as follows: - - * `gettext(msgid, bindings \\ %{})` - - like `Gettext.gettext(MyApp.Gettext, msgid, bindings)` - - * `dgettext(domain, msgid, bindings \\ %{})` - - like `Gettext.dgettext(MyApp.Gettext, domain, msgid, bindings)` + Each module that calls `use Gettext.Backend` is usually referred to as a "Gettext + backend", as it implements the `Gettext.Backend` behaviour. When a module then calls + `use Gettext, backend: MyApp.Gettext`, all the macros defined in `Gettext.Macros` + are imported into that module, such as: - * `pgettext(msgctxt, msgid, bindings \\ %{})` - - like `Gettext.pgettext(MyApp.Gettext, msgctxt, msgid, bindings)` - - * `dpgettext(domain, msgctxt, msgid, bindings \\ %{})` - - like `Gettext.dpgettext(MyApp.Gettext, domain, msgctxt, msgid, bindings)` - - * `ngettext(msgid, msgid_plural, n, bindings \\ %{})` - - like `Gettext.ngettext(MyApp.Gettext, msgid, msgid_plural, n, bindings)` - - * `dngettext(domain, msgid, msgid_plural, n, bindings \\ %{})` - - like `Gettext.dngettext(MyApp.Gettext, domain, msgid, msgid_plural, n, bindings)` - - * `pngettext(msgctxt, msgid, msgid_plural, n, bindings \\ %{})` - - like `Gettext.pngettext(MyApp.Gettext, msgctxt, msgid, msgid_plural, n, bindings)` - - * `dpngettext(domain, msgctxt, msgid, msgid_plural, n, bindings \\ %{})` - - like `Gettext.dpngettext(MyApp.Gettext, domain, msgctxt, msgid, msgid_plural, n, bindings)` - - * `*_noop` family of functions - used to mark messages for extraction - without translating them. See the documentation for these macros in - `Gettext.Backend` - - See also the `Gettext.Backend` behaviour for more detailed documentation about - these macros. + * [`gettext/2`](`Gettext.Macros.gettext/2`) + * [`dgettext/3`](`Gettext.Macros.dgettext/3`) + * [`pgettext/3`](`Gettext.Macros.pgettext/3`) Using macros is preferred as Gettext is able to automatically sync the messages in your code with PO files. This, however, imposes a constraint: @@ -297,11 +262,13 @@ defmodule Gettext do Gettext.put_locale(MyApp.Gettext, "it") - MyApp.Gettext.gettext("Hello world") + use Gettext, backend: MyApp.Gettext + + gettext("Hello world") #=> "Ciao mondo" @msgid "Hello world" - MyApp.Gettext.gettext(@msgid) + gettext(@msgid) #=> "Ciao mondo" The `*gettext` macros raise an `ArgumentError` exception if they receive a @@ -319,11 +286,11 @@ defmodule Gettext do If compile-time strings cannot be used, the solution is to use the functions in the `Gettext` module instead of the macros described above. These functions - perfectly mirror the macro API, but they all expect a module name as the first - argument. This module has to be a module which calls `use Gettext`. For example: + perfectly mirror the macro API, but they all expect a Gettext backend module + as the first argument. defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app end Gettext.put_locale(MyApp.Gettext, "pt_BR") @@ -338,13 +305,14 @@ defmodule Gettext do ## Domains - The `dgettext` and `dngettext` functions/macros also accept a *domain* as one + The [`dgettext`](`Gettext.Macros.dgettext/3`) and [`dngettext`](`Gettext.Macros.dngettext/5`) + macros (and their function counterparts) also accept a *domain* as one of the arguments. The domain of a message is determined by the name of the PO file that contains that message. For example, the domain of messages in the `it/LC_MESSAGES/errors.po` file is `"errors"`, so those messages would need to be retrieved with `dgettext` or `dngettext`: - MyApp.Gettext.dgettext("errors", "Error!") + dgettext("errors", "Error!") #=> "Errore!" When backend `gettext`, `ngettext`, or `pgettext` are used, the backend's @@ -359,7 +327,9 @@ defmodule Gettext do for that backend. defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app, default_domain: "messages" + use Gettext.Backend, + otp_app: :my_app, + default_domain: "messages" end config :my_app, MyApp.Gettext, default_domain: "messages" @@ -393,13 +363,13 @@ defmodule Gettext do interpolation can be done like follows: Gettext.put_locale(MyApp.Gettext, "it") - MyApp.Gettext.gettext("Hello, %{name}!", name: "Meg") + gettext("Hello, %{name}!", name: "Meg") #=> "Ciao, Meg!" Interpolation keys that are in a string but not in the provided bindings result in an exception: - MyApp.Gettext.gettext("Hello, %{name}!") + gettext("Hello, %{name}!") #=> ** (Gettext.MissingBindingsError) ... Keys that are in the interpolation bindings but that don't occur in the string @@ -410,7 +380,7 @@ defmodule Gettext do Pluralization in Gettext for Elixir works very similar to how pluralization works in GNU Gettext. The `*ngettext` functions/macros accept a `msgid`, a - `msgid_plural` and a count of elements; the right message is chosen based + `msgid_plural`, and a count of elements; the right message is chosen based on the **pluralization rule** for the given locale. For example, given the following snippet of PO file for the `"it"` locale: @@ -423,7 +393,7 @@ defmodule Gettext do the `ngettext` macro can be used like this: Gettext.put_locale(MyApp.Gettext, "it") - MyApp.Gettext.ngettext("One error", "%{count} errors", 3) + ngettext("One error", "%{count} errors", 3) #=> "3 errori" The `%{count}` interpolation key is a special key since it gets replaced by @@ -432,14 +402,16 @@ defmodule Gettext do `count` key in the bindings: # `count: 4` is ignored here - MyApp.Gettext.ngettext("One error", "%{count} errors", 3, count: 4) + ngettext("One error", "%{count} errors", 3, count: 4) #=> "3 errori" You can specify a "pluralizer" module via the `:plural_forms` option in the configuration for each Gettext backend. defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app, plural_forms: MyApp.PluralForms + use Gettext.Backend, + otp_app: :my_app, + plural_forms: MyApp.PluralForms end To learn more about pluralization rules, plural forms and what they mean to @@ -447,8 +419,8 @@ defmodule Gettext do ## Missing messages - When a message is missing in the specified locale (both with functions as - well as with macros), the argument is returned: + When a message is missing in the specified locale (both with functions and + with macros), the argument is returned: * in case of calls to `gettext`/`dgettext`/`pgettext`/`dpgettext`, the `msgid` argument is returned as is; @@ -459,9 +431,9 @@ defmodule Gettext do For example: Gettext.put_locale(MyApp.Gettext, "foo") - MyApp.Gettext.gettext("Hey there") + gettext("Hey there") #=> "Hey there" - MyApp.Gettext.ngettext("One error", "%{count} errors", 3) + ngettext("One error", "%{count} errors", 3) #=> "3 errors" ### Empty messages @@ -477,10 +449,10 @@ defmodule Gettext do Gettext to operate on those messages *at compile-time*. This can be used to extract messages from the source code into POT (Portable Object Template) files automatically (instead of having to manually add messages to POT files - when they're added to the source code). The `gettext.extract` does exactly + when they're added to the source code). `mix gettext.extract` does exactly this: whenever there are new messages in the source code, running - `gettext.extract` syncs the existing POT files with the changed code base. - Read the documentation for `Mix.Tasks.Gettext.Extract` for more information + this task syncs the existing POT files with the changed code base. + Read the documentation for `mix gettext.extract` for more information on the extraction process. POT files are just *template* files and the messages in them do not @@ -498,7 +470,7 @@ defmodule Gettext do will update all the PO files in `priv/gettext/pt_BR/LC_MESSAGES` with the new version of the POT files in `priv/gettext`. Read more about the merging - process in the documentation for `Mix.Tasks.Gettext.Merge`. + process in the documentation for `mix gettext.merge`. ## Configuration @@ -517,7 +489,7 @@ defmodule Gettext do at compile time): defmodule MyApp.Gettext do - use Gettext, options + use Gettext.Backend, options end or by using Mix configuration, configuring the key corresponding to the @@ -526,7 +498,7 @@ defmodule Gettext do # For example, in config/config.exs config :my_app, MyApp.Gettext, options - Note that the `:otp_app` option (an atom representing an OTP application) has + The `:otp_app` option (an atom representing an OTP application) has to always be present and has to be passed to `use Gettext` because it's used to determine the application to read the configuration of (`:my_app` in the example above); for this reason, `:otp_app` can't be configured via the Mix @@ -619,6 +591,7 @@ defmodule Gettext do """ + require Gettext.Macros alias Gettext.MissingBindingsError @type locale :: binary @@ -642,8 +615,23 @@ defmodule Gettext do end _other -> + # TODO: remove this once we stop supporting the old way of defining backends. + IO.warn(""" + defining a Gettext backend by calling + + use Gettext, otp_app: ..., ... + + is deprecated. To define a backend, call: + + use Gettext.Backend, otp_app: :my_app + + Then, to use the backend, call this in your module: + + use Gettext, backend: MyApp.Gettext + + """) + quote do - # TODO: Deprecate this branch use Gettext.Backend, unquote(opts) end end @@ -673,6 +661,7 @@ defmodule Gettext do #=> "en" """ + @doc section: :locale @spec get_locale() :: locale def get_locale() do with nil <- Process.get(Gettext) do @@ -698,6 +687,7 @@ defmodule Gettext do #=> "pt_BR" """ + @doc section: :locale @spec put_locale(locale) :: locale | nil def put_locale(locale) when is_binary(locale), do: Process.put(Gettext, locale) @@ -719,6 +709,7 @@ defmodule Gettext do #=> "en" """ + @doc section: :locale @spec get_locale(backend) :: locale def get_locale(backend) do with nil <- Process.get(backend), @@ -744,6 +735,7 @@ defmodule Gettext do #=> "pt_BR" """ + @doc section: :locale @spec put_locale(backend, locale) :: locale | nil def put_locale(backend, locale) when is_binary(locale), do: Process.put(backend, locale) @@ -765,7 +757,7 @@ defmodule Gettext do ## Examples defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app end Gettext.put_locale(MyApp.Gettext, "it") @@ -777,6 +769,7 @@ defmodule Gettext do #=> "Meg non รจ un nome valido" """ + @doc section: :translation @spec dpgettext(module, binary, binary | nil, binary, bindings) :: binary def dpgettext(backend, domain, msgctxt, msgid, bindings \\ %{}) @@ -806,7 +799,7 @@ defmodule Gettext do ## Examples defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app end Gettext.put_locale(MyApp.Gettext, "it") @@ -821,6 +814,7 @@ defmodule Gettext do #=> "nonexisting" """ + @doc section: :translation @spec dgettext(module, binary, binary, bindings) :: binary def dgettext(backend, domain, msgid, bindings \\ %{}) do dpgettext(backend, domain, nil, msgid, bindings) @@ -841,7 +835,7 @@ defmodule Gettext do ## Examples defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app end Gettext.put_locale(MyApp.Gettext, "it") @@ -855,6 +849,7 @@ defmodule Gettext do Gettext.pgettext(MyApp.Gettext, "alerts-users", "nonexisting") #=> "nonexisting" """ + @doc section: :translation @spec pgettext(module, binary, binary, bindings) :: binary def pgettext(backend, msgctxt, msgid, bindings \\ %{}) do dpgettext(backend, "default", msgctxt, msgid, bindings) @@ -868,6 +863,7 @@ defmodule Gettext do Gettext.dgettext(backend, "default", msgid, bindings) """ + @doc section: :translation @spec gettext(module, binary, bindings) :: binary def gettext(backend, msgid, bindings \\ %{}) do dgettext(backend, "default", msgid, bindings) @@ -889,7 +885,7 @@ defmodule Gettext do ## Examples defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app end Gettext.dpngettext(MyApp.Gettext, "errors", "user error", "Error", "%{count} errors", 3) @@ -898,6 +894,7 @@ defmodule Gettext do #=> "Errore" """ + @doc section: :translation @spec dpngettext(module, binary, binary | nil, binary, binary, non_neg_integer, bindings) :: binary def dpngettext(backend, domain, msgctxt, msgid, msgid_plural, n, bindings \\ %{}) @@ -931,7 +928,7 @@ defmodule Gettext do ## Examples defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app end Gettext.dngettext(MyApp.Gettext, "errors", "Error", "%{count} errors", 3) @@ -940,6 +937,7 @@ defmodule Gettext do #=> "Errore" """ + @doc section: :translation @spec dngettext(module, binary, binary, binary, non_neg_integer, bindings) :: binary def dngettext(backend, domain, msgid, msgid_plural, n, bindings \\ %{}), do: dpngettext(backend, domain, nil, msgid, msgid_plural, n, bindings) @@ -953,6 +951,7 @@ defmodule Gettext do Gettext.dpngettext(backend, "default", context, msgid, msgid_plural, n, bindings) """ + @doc section: :translation @spec pngettext(module, binary, binary, binary, non_neg_integer, bindings) :: binary def pngettext(backend, msgctxt, msgid, msgid_plural, n, bindings), do: dpngettext(backend, "default", msgctxt, msgid, msgid_plural, n, bindings) @@ -966,6 +965,7 @@ defmodule Gettext do Gettext.dngettext(backend, "default", msgid, msgid_plural, n, bindings) """ + @doc section: :translation @spec ngettext(module, binary, binary, non_neg_integer, bindings) :: binary def ngettext(backend, msgid, msgid_plural, n, bindings \\ %{}) do dngettext(backend, "default", msgid, msgid_plural, n, bindings) @@ -998,6 +998,7 @@ defmodule Gettext do #=> "Bonjour monde" """ + @doc section: :locale @spec with_locale(locale, (-> result)) :: result when result: var def with_locale(locale, fun) when is_binary(locale) and is_function(fun) do previous_locale = Process.get(Gettext) @@ -1041,6 +1042,7 @@ defmodule Gettext do #=> "Bonjour monde" """ + @doc section: :locale @spec with_locale(backend(), locale(), (-> result)) :: result when result: var def with_locale(backend, locale, fun) when is_atom(backend) and is_binary(locale) and is_function(fun) do @@ -1069,7 +1071,7 @@ defmodule Gettext do With the following backend: defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app end and the following messages directory: @@ -1085,6 +1087,7 @@ defmodule Gettext do #=> ["en", "it", "pt_BR"] """ + @doc section: :locale @spec known_locales(backend()) :: [locale()] def known_locales(backend) when is_atom(backend) do backend.__gettext__(:known_locales) diff --git a/lib/gettext/plural.ex b/lib/gettext/plural.ex index 18be8d6..ae4c63d 100644 --- a/lib/gettext/plural.ex +++ b/lib/gettext/plural.ex @@ -71,7 +71,9 @@ defmodule Gettext.Plural do or you can set it for each specific backend when you call `use Gettext`: defmodule MyApp.Gettext do - use Gettext, otp_app: :my_app, plural_forms: MyApp.Plural + use Gettext.Backend, + otp_app: :my_app, + plural_forms: MyApp.Plural end > #### Compile-time Configuration {: .warning} diff --git a/mix.exs b/mix.exs index 393883f..260aeea 100644 --- a/mix.exs +++ b/mix.exs @@ -26,7 +26,17 @@ defmodule Gettext.Mixfile do docs: [ source_ref: "v#{@version}", main: "Gettext", - source_url: @repo_url + source_url: @repo_url, + groups_for_docs: [ + # Gettext + "Translation Functions": &(&1[:section] == :translation), + "Locale Functions": &(&1[:section] == :locale), + + # Gettext.Macros + "Comment Macros": &(&1[:module] == Gettext.Macros and &1[:name] == :gettext_comment), + "No-op Macros": &(&1[:module] == Gettext.Macros and to_string(&1[:name]) =~ ~r/_noop$/), + "Translation Macros": &(&1[:module] == Gettext.Macros) + ] ] ] end diff --git a/test/gettext/extractor_test.exs b/test/gettext/extractor_test.exs index d3009d1..9c9311a 100644 --- a/test/gettext/extractor_test.exs +++ b/test/gettext/extractor_test.exs @@ -306,11 +306,11 @@ defmodule Gettext.ExtractorTest do code = """ defmodule Gettext.ExtractorTest.MyGettext do - use Gettext, otp_app: :test_application + use Gettext.Backend, otp_app: :test_application end defmodule Gettext.ExtractorTest.MyOtherGettext do - use Gettext, otp_app: :test_application, priv: "messages" + use Gettext.Backend, otp_app: :test_application, priv: "messages" end defmodule Foo do @@ -410,11 +410,11 @@ defmodule Gettext.ExtractorTest do code = """ defmodule Gettext.ExtractorConflictTest.MyGettext do - use Gettext, otp_app: :test_application + use Gettext.Backend, otp_app: :test_application end defmodule Gettext.ExtractorConflictTest.MyOtherGettext do - use Gettext, otp_app: :test_application + use Gettext.Backend, otp_app: :test_application end defmodule FooConflict do diff --git a/test/gettext_test.exs b/test/gettext_test.exs index 9f46dde..3cbae84 100644 --- a/test/gettext_test.exs +++ b/test/gettext_test.exs @@ -1,44 +1,48 @@ defmodule GettextTest.Translator do - use Gettext, otp_app: :test_application, priv: "test/fixtures/single_messages" + use Gettext.Backend, + otp_app: :test_application, + priv: "test/fixtures/single_messages" end defmodule GettextTest.TranslatorWithAllowedLocalesString do - use Gettext, + use Gettext.Backend, otp_app: :test_application, priv: "test/fixtures/multi_messages", allowed_locales: ["es"] end defmodule GettextTest.TranslatorWithAllowedLocalesAtom do - use Gettext, + use Gettext.Backend, otp_app: :test_application, priv: "test/fixtures/multi_messages", allowed_locales: [:es] end defmodule GettextTest.TranslatorWithCustomPluralForms do - use Gettext, + use Gettext.Backend, otp_app: :test_application, priv: "test/fixtures/single_messages", plural_forms: GettextTest.CustomPlural end defmodule GettextTest.TranslatorWithCustomCompiledPluralForms do - use Gettext, + use Gettext.Backend, otp_app: :test_application, priv: "test/fixtures/single_messages", plural_forms: GettextTest.CustomCompiledPlural end defmodule GettextTest.TranslatorWithDefaultDomain do - use Gettext, + use Gettext.Backend, otp_app: :test_application, priv: "test/fixtures/single_messages", default_domain: "errors" end defmodule GettextTest.HandleMissingMessage do - use Gettext, otp_app: :test_application, priv: "test/fixtures/single_messages" + use Gettext.Backend, + otp_app: :test_application, + priv: "test/fixtures/single_messages" def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do send(self(), {locale, domain, msgctxt, msgid, bindings}) @@ -70,7 +74,7 @@ defmodule GettextTest.TranslatorWithDuckInterpolator.Interpolator do end defmodule GettextTest.TranslatorWithDuckInterpolator do - use Gettext, + use Gettext.Backend, otp_app: :test_application, interpolation: GettextTest.TranslatorWithDuckInterpolator.Interpolator, priv: "test/fixtures/single_messages" @@ -79,6 +83,7 @@ end defmodule GettextTest do use ExUnit.Case + import ExUnit.CaptureIO import ExUnit.CaptureLog alias GettextTest.Translator @@ -223,7 +228,7 @@ defmodule GettextTest do Application.put_env(:gettext, :plural_forms, GettextTest.CustomPlural) defmodule TranslatorWithAppPluralForms do - use Gettext, otp_app: :test_application, priv: "test/fixtures/single_messages" + use Gettext.Backend, otp_app: :test_application, priv: "test/fixtures/single_messages" end alias TranslatorWithAppPluralForms, as: T @@ -296,7 +301,7 @@ defmodule GettextTest do Code.eval_quoted( quote do defmodule BadTranslations do - use Gettext, + use Gettext.Backend, otp_app: :test_application, priv: "test/fixtures/bad_messages" end @@ -859,7 +864,7 @@ defmodule GettextTest do end defmodule TranslatorWithOneModulePerLocale do - use Gettext, + use Gettext.Backend, otp_app: :test_application, split_module_by: [:locale], split_module_compilation: :parallel, @@ -901,7 +906,7 @@ defmodule GettextTest do end defmodule TranslatorWithOneModulePerLocaleDomain do - use Gettext, + use Gettext.Backend, otp_app: :test_application, split_module_by: [:locale, :domain], split_module_compilation: :serial, @@ -947,4 +952,20 @@ defmodule GettextTest do assert "quack foo %{} quack" = gettext("foo") end + + test "use Gettext for defining backends is deprecated" do + {_, stderr} = + with_io(:stderr, fn -> + Code.eval_quoted( + quote do + defmodule DeprecatedWayOfDefiningBackend do + use Gettext, otp_app: :my_app + end + end + ) + end) + + assert stderr =~ "defining a Gettext backend by calling" + assert stderr =~ "is deprecated" + end end diff --git a/test/mix/tasks/gettext.extract_test.exs b/test/mix/tasks/gettext.extract_test.exs index c56fe15..0c0659b 100644 --- a/test/mix/tasks/gettext.extract_test.exs +++ b/test/mix/tasks/gettext.extract_test.exs @@ -17,7 +17,7 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do @@ -73,7 +73,7 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do @@ -112,7 +112,7 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do @@ -138,7 +138,7 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do @@ -160,7 +160,7 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do From dcac95ff541728f12a64e1e1b3871ceb48a349d8 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sun, 18 Aug 2024 14:55:36 +0200 Subject: [PATCH 2/5] Remove with_io/1 --- test/gettext_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/gettext_test.exs b/test/gettext_test.exs index 3cbae84..0bb7860 100644 --- a/test/gettext_test.exs +++ b/test/gettext_test.exs @@ -954,8 +954,8 @@ defmodule GettextTest do end test "use Gettext for defining backends is deprecated" do - {_, stderr} = - with_io(:stderr, fn -> + stderr = + capture_io(:stderr, fn -> Code.eval_quoted( quote do defmodule DeprecatedWayOfDefiningBackend do From 57eac5a92733b9e93ee4d5b53acfc78d846c7c71 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Mon, 19 Aug 2024 08:51:05 +0200 Subject: [PATCH 3/5] Address some review comments --- lib/gettext.ex | 35 +-- lib/gettext/backend.ex | 2 +- lib/gettext/compiler.ex | 35 +-- lib/gettext/macros.ex | 24 ++- test/gettext/macros_test.exs | 258 ++++++++++++++++++++++ test/gettext_test.exs | 271 ++---------------------- test/mix/tasks/gettext.extract_test.exs | 38 ++-- 7 files changed, 350 insertions(+), 313 deletions(-) create mode 100644 test/gettext/macros_test.exs diff --git a/lib/gettext.ex b/lib/gettext.ex index 89a5109..0b0821b 100644 --- a/lib/gettext.ex +++ b/lib/gettext.ex @@ -276,7 +276,7 @@ defmodule Gettext do *at compile time*: msgid = "Hello world" - MyApp.Gettext.gettext(msgid) + gettext(msgid) #=> ** (ArgumentError) msgid must be a string literal Using compile-time strings isn't always possible. For this reason, @@ -610,29 +610,34 @@ defmodule Gettext do case Keyword.keyword?(opts) && Keyword.fetch(opts, :backend) do {:ok, backend} -> quote do + Module.register_attribute(__MODULE__, :__gettext_backend__, persist: true) @__gettext_backend__ unquote(backend) import Gettext.Macros end _other -> # TODO: remove this once we stop supporting the old way of defining backends. - IO.warn(""" - defining a Gettext backend by calling + IO.warn( + """ + defining a Gettext backend by calling - use Gettext, otp_app: ..., ... + use Gettext, otp_app: ..., ... - is deprecated. To define a backend, call: + is deprecated. To define a backend, call: - use Gettext.Backend, otp_app: :my_app + use Gettext.Backend, otp_app: :my_app - Then, to use the backend, call this in your module: + Then, to use the backend, call this in your module: - use Gettext, backend: MyApp.Gettext + use Gettext, backend: MyApp.Gettext - """) + """, + Macro.Env.stacktrace(__CALLER__) + ) quote do use Gettext.Backend, unquote(opts) + @before_compile {Gettext.Compiler, :generate_macros} end end end @@ -986,15 +991,15 @@ defmodule Gettext do Gettext.put_locale("fr") - MyApp.Gettext.gettext("Hello world") + gettext("Hello world") #=> "Bonjour monde" Gettext.with_locale("it", fn -> - MyApp.Gettext.gettext("Hello world") + gettext("Hello world") end) #=> "Ciao mondo" - MyApp.Gettext.gettext("Hello world") + gettext("Hello world") #=> "Bonjour monde" """ @@ -1030,15 +1035,15 @@ defmodule Gettext do Gettext.put_locale(MyApp.Gettext, "fr") - MyApp.Gettext.gettext("Hello world") + gettext("Hello world") #=> "Bonjour monde" Gettext.with_locale(MyApp.Gettext, "it", fn -> - MyApp.Gettext.gettext("Hello world") + gettext("Hello world") end) #=> "Ciao mondo" - MyApp.Gettext.gettext("Hello world") + gettext("Hello world") #=> "Bonjour monde" """ diff --git a/lib/gettext/backend.ex b/lib/gettext/backend.ex index 19b27c2..37302de 100644 --- a/lib/gettext/backend.ex +++ b/lib/gettext/backend.ex @@ -85,7 +85,7 @@ defmodule Gettext.Backend do For example, if something like this is called: - MyApp.Gettext.gettext("Hello %{name}, your favorite color is %{color}", name: "Jane", color: "blue") + gettext("Hello %{name}, your favorite color is %{color}", name: "Jane", color: "blue") and our `it/LC_MESSAGES/default.po` looks like this: diff --git a/lib/gettext/compiler.ex b/lib/gettext/compiler.ex index aa58cda..14a1def 100644 --- a/lib/gettext/compiler.ex +++ b/lib/gettext/compiler.ex @@ -61,8 +61,6 @@ defmodule Gettext.Compiler do Gettext.ExtractorAgent.add_backend(__MODULE__) end - unquote(macros()) - # These are the two functions we generate inside the backend. @impl Gettext.Backend @@ -99,12 +97,12 @@ defmodule Gettext.Compiler do end) end - defp macros() do + def generate_macros(_env) do quote unquote: false do defmacro dpgettext_noop(domain, msgctxt, msgid) do - domain = Gettext.Compiler.expand_to_binary(domain, "domain", __MODULE__, __CALLER__) - msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __MODULE__, __CALLER__) - msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __MODULE__, __CALLER__) + domain = Gettext.Compiler.expand_to_binary(domain, "domain", __CALLER__) + msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __CALLER__) + msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __CALLER__) if Gettext.Extractor.extracting?() do Gettext.Extractor.extract( @@ -143,12 +141,10 @@ defmodule Gettext.Compiler do end defmacro dpngettext_noop(domain, msgctxt, msgid, msgid_plural) do - domain = Gettext.Compiler.expand_to_binary(domain, "domain", __MODULE__, __CALLER__) - msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __MODULE__, __CALLER__) - msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __MODULE__, __CALLER__) - - msgid_plural = - Gettext.Compiler.expand_to_binary(msgid_plural, "msgid_plural", __MODULE__, __CALLER__) + domain = Gettext.Compiler.expand_to_binary(domain, "domain", __CALLER__) + msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __CALLER__) + msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __CALLER__) + msgid_plural = Gettext.Compiler.expand_to_binary(msgid_plural, "msgid_plural", __CALLER__) if Gettext.Extractor.extracting?() do Gettext.Extractor.extract( @@ -309,7 +305,7 @@ defmodule Gettext.Compiler do end defmacro gettext_comment(comment) do - comment = Gettext.Compiler.expand_to_binary(comment, "comment", __MODULE__, __CALLER__) + comment = Gettext.Compiler.expand_to_binary(comment, "comment", __CALLER__) Gettext.Compiler.append_extracted_comment(comment) :ok end @@ -320,9 +316,16 @@ defmodule Gettext.Compiler do Expands the given `msgid` in the given `env`, raising if it doesn't expand to a binary. """ - @spec expand_to_binary(binary, binary, module, Macro.Env.t()) :: binary | no_return - def expand_to_binary(term, what, gettext_module, env) + @spec expand_to_binary(binary, binary, Macro.Env.t()) :: binary | no_return + def expand_to_binary(term, what, env) when what in ~w(domain msgctxt msgid msgid_plural comment) do + gettext_module = + if Module.open?(env.module) do + Module.get_attribute(env.module, :__gettext_backend__) + else + List.first(env.module.__info__(:attributes)[:__gettext_backend__]) + end + raiser = fn term -> raise ArgumentError, """ Gettext macros expect message keys (msgid and msgid_plural), @@ -337,7 +340,7 @@ defmodule Gettext.Compiler do module: string = "hello world" - Gettext.gettext(#{inspect(gettext_module)}, string) + Gettext.gettext(#{if(gettext_module, do: inspect(gettext_module), else: "backend")}, string) """ end diff --git a/lib/gettext/macros.ex b/lib/gettext/macros.ex index 12ad45c..7884dfc 100644 --- a/lib/gettext/macros.ex +++ b/lib/gettext/macros.ex @@ -38,13 +38,15 @@ defmodule Gettext.Macros do """ defmacro dpgettext_noop(domain, msgctxt, msgid) do domain = expand_domain(domain, __CALLER__) - msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __MODULE__, __CALLER__) - msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __MODULE__, __CALLER__) + msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __CALLER__) + msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __CALLER__) if Gettext.Extractor.extracting?() do + backend = Module.get_attribute(__CALLER__.module, :__gettext_backend__) + Gettext.Extractor.extract( __CALLER__, - __MODULE__, + backend, domain, msgctxt, msgid, @@ -127,16 +129,16 @@ defmodule Gettext.Macros do """ defmacro dpngettext_noop(domain, msgctxt, msgid, msgid_plural) do domain = expand_domain(domain, __CALLER__) - msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __MODULE__, __CALLER__) - msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __MODULE__, __CALLER__) - - msgid_plural = - Gettext.Compiler.expand_to_binary(msgid_plural, "msgid_plural", __MODULE__, __CALLER__) + msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __CALLER__) + msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __CALLER__) + msgid_plural = Gettext.Compiler.expand_to_binary(msgid_plural, "msgid_plural", __CALLER__) if Gettext.Extractor.extracting?() do + backend = Module.get_attribute(__CALLER__.module, :__gettext_backend__) + Gettext.Extractor.extract( __CALLER__, - __MODULE__, + backend, domain, msgctxt, {msgid, msgid_plural}, @@ -394,7 +396,7 @@ defmodule Gettext.Macros do """ defmacro gettext_comment(comment) do - comment = Gettext.Compiler.expand_to_binary(comment, "comment", __MODULE__, __CALLER__) + comment = Gettext.Compiler.expand_to_binary(comment, "comment", __CALLER__) Gettext.Compiler.append_extracted_comment(comment) :ok end @@ -404,6 +406,6 @@ defmodule Gettext.Macros do end defp expand_domain(domain, env) do - Gettext.Compiler.expand_to_binary(domain, "domain", __MODULE__, env) + Gettext.Compiler.expand_to_binary(domain, "domain", env) end end diff --git a/test/gettext/macros_test.exs b/test/gettext/macros_test.exs new file mode 100644 index 0000000..bcce13a --- /dev/null +++ b/test/gettext/macros_test.exs @@ -0,0 +1,258 @@ +defmodule Gettext.MacrosTest.Translator do + use Gettext.Backend, + otp_app: :test_application, + priv: "test/fixtures/single_messages" +end + +defmodule Gettext.MacrosTest do + use ExUnit.Case, async: true + use Gettext, backend: Gettext.MacrosTest.Translator + + import ExUnit.CaptureLog + + @backend Gettext.MacrosTest.Translator + @gettext_msgid "Hello world" + + describe "gettext/2" do + test "supports binary-ish msgid at compile-time" do + Gettext.put_locale(@backend, "it") + assert gettext("Hello world") == "Ciao mondo" + assert gettext(@gettext_msgid) == "Ciao mondo" + assert gettext(~s(Hello world)) == "Ciao mondo" + end + end + + describe "dgettext/3" do + test "supports binary-ish msgid at compile-time" do + Gettext.put_locale(@backend, "it") + + assert dgettext("errors", "Invalid email address") == "Indirizzo email non valido" + keys = %{name: "Jim"} + assert dgettext("interpolations", "Hello %{name}", keys) == "Ciao Jim" + + log = + capture_log(fn -> + assert dgettext("interpolations", "Hello %{name}") == "Ciao %{name}" + end) + + assert log =~ ~s/[error] missing Gettext bindings: [:name]/ + end + end + + describe "pgettext/3" do + test "supports test with context based messages" do + Gettext.put_locale(@backend, "it") + assert pgettext("test", @gettext_msgid) == "Ciao mondo" + assert pgettext("test", ~s(Hello world)) == "Ciao mondo" + assert pgettext("test", "Hello world", %{}) == "Ciao mondo" + assert pgettext("test", "Hello %{name}", %{name: "Marco"}) == "Ciao Marco" + + # Missing message + assert pgettext("test", "Hello missing", %{}) == "Hello missing" + end + end + + test "pgettext/3, pngettext/4: dynamic context raises" do + code = + quote do + context = "test" + pgettext(context, "Hello world") + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:context" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + + code = + quote do + context = "test" + pngettext(context, "Hello world", "Hello world", 5) + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:context" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + end + + test "dpgettext/4, dpngettext/5: dynamic context or dynamic domain raises" do + code = + quote do + context = "test" + dpgettext("default", context, "Hello world") + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:context" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + + code = + quote do + domain = "test" + dpgettext(domain, "test", "Hello world") + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:domain" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + + code = + quote do + context = "test" + dpngettext("default", context, "Hello world", "Hello world", n) + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:context" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + + code = + quote do + domain = "test" + dpngettext(domain, "test", "Hello world", "Hello World", n) + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:domain" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + end + + test "dpgettext/4: context and domain based messages" do + Gettext.put_locale(@backend, "it") + assert dpgettext("default", "test", "Hello world", %{}) == "Ciao mondo" + end + + test "dgettext/3 and dngettext/2: non-binary things at compile-time" do + code = + quote do + msgid = "Invalid email address" + dgettext("errors", msgid) + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:msgid" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + + code = + quote do + msgid_plural = ~s(foo #{1 + 1} bar) + dngettext("default", "foo", msgid_plural, 1) + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:msgid_plural" + assert message =~ "Gettext.gettext(Gettext.MacrosTest.Translator, string)" + + code = + quote do + domain = "dynamic_domain" + dgettext(domain, "hello") + end + + error = assert_raise ArgumentError, fn -> Code.eval_quoted(code, [], __ENV__) end + message = ArgumentError.message(error) + assert message =~ "Gettext macros expect message keys" + assert message =~ "{:domain" + end + + describe "dngettext/5" do + test "translates with plural and domain" do + Gettext.put_locale(@backend, "it") + + assert dngettext( + "interpolations", + "You have one message, %{name}", + "You have %{count} messages, %{name}", + 1, + %{name: "James"} + ) == "Hai un messaggio, James" + + assert dngettext( + "interpolations", + "You have one message, %{name}", + "You have %{count} messages, %{name}", + 2, + %{name: "James"} + ) == "Hai 2 messaggi, James" + + assert dngettext( + "interpolations", + "Month", + "%{count} months", + 2 + ) == "2 mesi" + end + end + + @ngettext_msgid "One new email" + @ngettext_msgid_plural "%{count} new emails" + + describe "ngettext/4" do + test "translates with plural" do + Gettext.put_locale(@backend, "it") + assert ngettext("One new email", "%{count} new emails", 1) == "Una nuova email" + assert ngettext("One new email", "%{count} new emails", 2) == "2 nuove email" + + assert ngettext(@ngettext_msgid, @ngettext_msgid_plural, 1) == "Una nuova email" + assert ngettext(@ngettext_msgid, @ngettext_msgid_plural, 2) == "2 nuove email" + end + end + + describe "pngettext/4" do + test "translates with plurals and context" do + Gettext.put_locale(@backend, "it") + + assert pngettext("test", "One new email", "%{count} new emails", 1) == + "Una nuova test email" + + assert pngettext("test", "One new email", "%{count} new emails", 2) == + "2 nuove test email" + + assert pngettext("test", @ngettext_msgid, @ngettext_msgid_plural, 1) == + "Una nuova test email" + + assert pngettext("test", @ngettext_msgid, @ngettext_msgid_plural, 2) == + "2 nuove test email" + end + end + + test "the d?n?gettext macros support a kw list for interpolation" do + Gettext.put_locale(@backend, "it") + assert gettext("%{msg}", msg: "foo") == "foo" + end + + test "(d)(p)gettext_noop" do + assert dpgettext_noop("errors", "test", "Oops") == "Oops" + assert dgettext_noop("errors", "Oops") == "Oops" + assert gettext_noop("Hello %{name}!") == "Hello %{name}!" + end + + test "(d)(p)ngettext_noop" do + assert dpngettext_noop("errors", "test", "One error", "%{count} errors") == + {"One error", "%{count} errors"} + + assert dngettext_noop("errors", "One error", "%{count} errors") == + {"One error", "%{count} errors"} + + assert ngettext_noop("One message", "%{count} messages") == + {"One message", "%{count} messages"} + + assert pngettext_noop("test", "One message", "%{count} messages") == + {"One message", "%{count} messages"} + end +end diff --git a/test/gettext_test.exs b/test/gettext_test.exs index 0bb7860..11cda90 100644 --- a/test/gettext_test.exs +++ b/test/gettext_test.exs @@ -180,10 +180,23 @@ defmodule GettextTest do end test "a custom default_domain can be set for a backend" do - alias TranslatorWithDefaultDomain, as: T + Code.eval_quoted( + quote do + defmodule DefaultDomainTest do + use Gettext, backend: GettextTest.TranslatorWithDefaultDomain + + def test("Invalid email address"), do: gettext("Invalid email address") + def test("Hello world"), do: gettext("Hello world") + end + end + ) + Gettext.put_locale("it") - assert T.gettext("Invalid email address") == "Indirizzo email non valido" - assert T.gettext("Hello world") == "Hello world" + + assert apply(DefaultDomainTest, :test, ["Invalid email address"]) == + "Indirizzo email non valido" + + assert apply(DefaultDomainTest, :test, ["Hello world"]) == "Hello world" end test "allowed_locales ignores other locales as strings" do @@ -393,7 +406,7 @@ defmodule GettextTest do test "MissingBindingsError log messages" do assert capture_log(fn -> - Translator.pgettext("test", "Hello %{name}, missing message!", %{}) + Gettext.pgettext(Translator, "test", "Hello %{name}, missing message!", %{}) end) =~ "missing Gettext bindings: [:name] (backend GettextTest.Translator," <> " locale \"en\", domain \"default\", msgctxt \"test\", msgid \"Hello " <> @@ -468,249 +481,6 @@ defmodule GettextTest do assert_receive {"pl", "foo", ^msgctxt, ^msgid, ^msgid_plural, 4, ^bindings} end - test "dgettext/3: binary msgid at compile-time" do - Gettext.put_locale(Translator, "it") - - assert Translator.dgettext("errors", "Invalid email address") == "Indirizzo email non valido" - keys = %{name: "Jim"} - assert Translator.dgettext("interpolations", "Hello %{name}", keys) == "Ciao Jim" - - log = - capture_log(fn -> - assert Translator.dgettext("interpolations", "Hello %{name}") == "Ciao %{name}" - end) - - assert log =~ ~s/[error] missing Gettext bindings: [:name]/ - end - - # Macros. - - @gettext_msgid "Hello world" - - test "gettext/2: binary-ish msgid at compile-time" do - Gettext.put_locale(Translator, "it") - assert Translator.gettext("Hello world") == "Ciao mondo" - assert Translator.gettext(@gettext_msgid) == "Ciao mondo" - assert Translator.gettext(~s(Hello world)) == "Ciao mondo" - end - - test "pgettext/3: test with context based messages" do - Gettext.put_locale(Translator, "it") - assert Translator.pgettext("test", @gettext_msgid) == "Ciao mondo" - assert Translator.pgettext("test", ~s(Hello world)) == "Ciao mondo" - assert Translator.pgettext("test", "Hello world", %{}) == "Ciao mondo" - assert Translator.pgettext("test", "Hello %{name}", %{name: "Marco"}) == "Ciao Marco" - # Missing message - assert Translator.pgettext("test", "Hello missing", %{}) == "Hello missing" - end - - test "pgettext/3, pngettext/4: dynamic context raises" do - code = - quote do - require Translator - context = "test" - Translator.pgettext(context, "Hello world") - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:context" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - - code = - quote do - require Translator - context = "test" - Translator.pngettext(context, "Hello world", "Hello world", 5) - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:context" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - end - - test "dpgettext/4, dpngettext/5: dynamic context or dynamic domain raises" do - code = - quote do - require Translator - context = "test" - Translator.dpgettext("default", context, "Hello world") - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:context" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - - code = - quote do - require Translator - domain = "test" - Translator.dpgettext(domain, "test", "Hello world") - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:domain" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - - code = - quote do - require Translator - context = "test" - Translator.dpngettext("default", context, "Hello world", "Hello world", n) - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:context" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - - code = - quote do - require Translator - domain = "test" - Translator.dpngettext(domain, "test", "Hello world", "Hello World", n) - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:domain" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - end - - test "dpgettext/4: context and domain based messages" do - Gettext.put_locale(Translator, "it") - assert Translator.dpgettext("default", "test", "Hello world", %{}) == "Ciao mondo" - end - - test "dgettext/3 and dngettext/2: non-binary things at compile-time" do - code = - quote do - require Translator - msgid = "Invalid email address" - Translator.dgettext("errors", msgid) - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:msgid" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - - code = - quote do - require Translator - msgid_plural = ~s(foo #{1 + 1} bar) - Translator.dngettext("default", "foo", msgid_plural, 1) - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:msgid_plural" - assert message =~ "Gettext.gettext(GettextTest.Translator, string)" - - code = - quote do - require Translator - domain = "dynamic_domain" - Translator.dgettext(domain, "hello") - end - - error = assert_raise ArgumentError, fn -> Code.eval_quoted(code) end - message = ArgumentError.message(error) - assert message =~ "Gettext macros expect message keys" - assert message =~ "{:domain" - end - - test "dngettext/5" do - Gettext.put_locale(Translator, "it") - - assert Translator.dngettext( - "interpolations", - "You have one message, %{name}", - "You have %{count} messages, %{name}", - 1, - %{name: "James"} - ) == "Hai un messaggio, James" - - assert Translator.dngettext( - "interpolations", - "You have one message, %{name}", - "You have %{count} messages, %{name}", - 2, - %{name: "James"} - ) == "Hai 2 messaggi, James" - - assert Translator.dngettext( - "interpolations", - "Month", - "%{count} months", - 2 - ) == "2 mesi" - end - - @ngettext_msgid "One new email" - @ngettext_msgid_plural "%{count} new emails" - - test "ngettext/4" do - Gettext.put_locale(Translator, "it") - assert Translator.ngettext("One new email", "%{count} new emails", 1) == "Una nuova email" - assert Translator.ngettext("One new email", "%{count} new emails", 2) == "2 nuove email" - - assert Translator.ngettext(@ngettext_msgid, @ngettext_msgid_plural, 1) == "Una nuova email" - assert Translator.ngettext(@ngettext_msgid, @ngettext_msgid_plural, 2) == "2 nuove email" - end - - test "pngettext/4" do - Gettext.put_locale(Translator, "it") - - assert Translator.pngettext("test", "One new email", "%{count} new emails", 1) == - "Una nuova test email" - - assert Translator.pngettext("test", "One new email", "%{count} new emails", 2) == - "2 nuove test email" - - assert Translator.pngettext("test", @ngettext_msgid, @ngettext_msgid_plural, 1) == - "Una nuova test email" - - assert Translator.pngettext("test", @ngettext_msgid, @ngettext_msgid_plural, 2) == - "2 nuove test email" - end - - test "the d?n?gettext macros support a kw list for interpolation" do - Gettext.put_locale(Translator, "it") - assert Translator.gettext("%{msg}", msg: "foo") == "foo" - end - - test "(d)(p)gettext_noop" do - assert Translator.dpgettext_noop("errors", "test", "Oops") == "Oops" - assert Translator.dgettext_noop("errors", "Oops") == "Oops" - assert Translator.gettext_noop("Hello %{name}!") == "Hello %{name}!" - end - - test "(d)(p)ngettext_noop" do - assert Translator.dpngettext_noop("errors", "test", "One error", "%{count} errors") == - {"One error", "%{count} errors"} - - assert Translator.dngettext_noop("errors", "One error", "%{count} errors") == - {"One error", "%{count} errors"} - - assert Translator.ngettext_noop("One message", "%{count} messages") == - {"One message", "%{count} messages"} - - assert Translator.pngettext_noop("test", "One message", "%{count} messages") == - {"One message", "%{count} messages"} - end - # Actual Gettext functions (not the ones generated in the modules that `use # Gettext`). @@ -857,7 +627,7 @@ defmodule GettextTest do test "a warning is issued in l(n)gettext when the domain contains slashes" do log = capture_log(fn -> - assert Translator.dgettext("sub/dir/domain", "hello") == "hello" + assert Gettext.dgettext(Translator, "sub/dir/domain", "hello") == "hello" end) assert log =~ ~s(Slashes in domains are not supported: "sub/dir/domain") @@ -948,9 +718,8 @@ defmodule GettextTest do end test "uses custom interpolator" do - import GettextTest.TranslatorWithDuckInterpolator, only: [gettext: 1] - - assert "quack foo %{} quack" = gettext("foo") + assert Gettext.gettext(GettextTest.TranslatorWithDuckInterpolator, "foo") == + "quack foo %{} quack" end test "use Gettext for defining backends is deprecated" do diff --git a/test/mix/tasks/gettext.extract_test.exs b/test/mix/tasks/gettext.extract_test.exs index 0c0659b..631d23f 100644 --- a/test/mix/tasks/gettext.extract_test.exs +++ b/test/mix/tasks/gettext.extract_test.exs @@ -21,8 +21,8 @@ defmodule Mix.Tasks.Gettext.ExtractTest do end defmodule MyApp do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.gettext("hello") + use Gettext, backend: MyApp.Gettext + def foo(), do: gettext("hello") end """) @@ -44,8 +44,8 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/other.ex", """ defmodule MyApp.Other do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.dgettext("my_domain", "other") + use Gettext, backend: MyApp.Gettext + def foo(), do: dgettext("my_domain", "other") end """) @@ -73,19 +73,19 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext.Backend, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.gettext("hello") + use Gettext, backend: MyApp.Gettext + def foo(), do: gettext("hello") end """) write_file(context, "lib/other.ex", """ defmodule MyApp.Other do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.dgettext("my_domain", "other") + use Gettext, backend: MyApp.Gettext + def foo(), do: dgettext("my_domain", "other") end """) @@ -116,8 +116,8 @@ defmodule Mix.Tasks.Gettext.ExtractTest do end defmodule MyApp do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.gettext("hello") + use Gettext, backend: MyApp.Gettext + def foo(), do: gettext("hello") end """) @@ -138,19 +138,19 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext.Backend, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.gettext("hello") + use Gettext, backend: MyApp.Gettext + def foo(), do: gettext("hello") end """) write_file(context, "lib/other.ex", """ defmodule MyApp.Other do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.dgettext("my_domain", "other") + use Gettext, backend: MyApp.Gettext + def foo(), do: dgettext("my_domain", "other") end """) @@ -160,12 +160,12 @@ defmodule Mix.Tasks.Gettext.ExtractTest do write_file(context, "lib/my_app.ex", """ defmodule MyApp.Gettext do - use Gettext.Backend, otp_app: #{inspect(test)} + use Gettext.Backend, otp_app: #{inspect(test)} end defmodule MyApp do - require MyApp.Gettext - def foo(), do: MyApp.Gettext.gettext("new text") + use Gettext, backend: MyApp.Gettext + def foo(), do: gettext("hello") end """) From 6167f0a27cbef2685aaac4acbd729ba16b7d3958 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Mon, 19 Aug 2024 10:20:06 +0200 Subject: [PATCH 4/5] More more more --- lib/gettext/compiler.ex | 28 ++ lib/gettext/macros.ex | 335 ++++++++++++++++++++++++ test/gettext/extractor_test.exs | 42 ++- test/mix/tasks/gettext.extract_test.exs | 2 +- 4 files changed, 383 insertions(+), 24 deletions(-) diff --git a/lib/gettext/compiler.ex b/lib/gettext/compiler.ex index 14a1def..1b618ea 100644 --- a/lib/gettext/compiler.ex +++ b/lib/gettext/compiler.ex @@ -358,6 +358,34 @@ defmodule Gettext.Compiler do end end + @doc """ + Expands the given `term` to a compile-time atom in the given `env`. + """ + @spec expand_backend(Macro.t(), Macro.Env.t()) :: module + def expand_backend(term, env) do + case Macro.expand(term, env) do + term when is_atom(term) and term not in [nil, false, true] -> + term + + _other -> + raise ArgumentError, """ + Gettext.Macros macros (that end with "_with_backend") expect the backend argument + to be an atom at compile-time, but the given term doesn't. This is what the macro + received: + + #{inspect(term)} + + Dynamic messages should be avoided as they limit Gettext's + ability to extract messages from your source code. If you are + sure you need dynamic lookup, you can use the functions in the Gettext + module: + + string = "hello world" + Gettext.gettext(backend, string) + """ + end + end + @doc """ Appends the given comment to the list of extracted comments in the process dictionary. """ diff --git a/lib/gettext/macros.ex b/lib/gettext/macros.ex index 7884dfc..f87fe94 100644 --- a/lib/gettext/macros.ex +++ b/lib/gettext/macros.ex @@ -401,6 +401,341 @@ defmodule Gettext.Macros do :ok end + ## Macros that also take a backend. + + @doc """ + Same as `dpgettext_noop/3`, but takes an explicit backend. + """ + defmacro dpgettext_noop_with_backend(backend, domain, msgctxt, msgid) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + domain = expand_domain(domain, __CALLER__) + msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __CALLER__) + msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __CALLER__) + + if Gettext.Extractor.extracting?() do + Gettext.Extractor.extract( + __CALLER__, + backend, + domain, + msgctxt, + msgid, + Gettext.Compiler.get_and_flush_extracted_comments() + ) + end + + msgid + end + + @doc """ + Same as `dgettext_noop/2`, but takes an explicit backend. + """ + defmacro dgettext_noop_with_backend(backend, domain, msgid) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpgettext_noop_with_backend( + unquote(backend), + unquote(domain), + nil, + unquote(msgid) + ) + end + end + + @doc """ + Same as `gettext_noop/1`, but takes an explicit backend. + """ + defmacro gettext_noop_with_backend(backend, msgid) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpgettext_noop_with_backend( + unquote(backend), + :default, + nil, + unquote(msgid) + ) + end + end + + @doc """ + Same as `pgettext_noop/2`, but takes an explicit backend. + """ + defmacro pgettext_noop_with_backend(backend, msgid, context) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpgettext_noop_with_backend( + unquote(backend), + :default, + unquote(context), + unquote(msgid) + ) + end + end + + @doc """ + Same as `dpngettext_noop/4`, but takes an explicit backend. + """ + defmacro dpngettext_noop_with_backend(backend, domain, msgctxt, msgid, msgid_plural) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + domain = expand_domain(domain, __CALLER__) + msgid = Gettext.Compiler.expand_to_binary(msgid, "msgid", __CALLER__) + msgctxt = Gettext.Compiler.expand_to_binary(msgctxt, "msgctxt", __CALLER__) + msgid_plural = Gettext.Compiler.expand_to_binary(msgid_plural, "msgid_plural", __CALLER__) + + if Gettext.Extractor.extracting?() do + Gettext.Extractor.extract( + __CALLER__, + backend, + domain, + msgctxt, + {msgid, msgid_plural}, + Gettext.Compiler.get_and_flush_extracted_comments() + ) + end + + {msgid, msgid_plural} + end + + @doc """ + Same as `dngettext_noop/3`, but takes an explicit backend. + """ + defmacro dngettext_noop_with_backend(backend, domain, msgid, msgid_plural) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpngettext_noop_with_backend( + unquote(backend), + unquote(domain), + nil, + unquote(msgid), + unquote(msgid_plural) + ) + end + end + + @doc """ + Same as `pngettext_noop/3`, but takes an explicit backend. + """ + defmacro pngettext_noop_with_backend(backend, msgctxt, msgid, msgid_plural) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpngettext_noop_with_backend( + unquote(backend), + :default, + unquote(msgctxt), + unquote(msgid), + unquote(msgid_plural) + ) + end + end + + @doc """ + Same as `ngettext_noop/2`, but takes an explicit backend. + """ + defmacro ngettext_noop_with_backend(backend, msgid, msgid_plural) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpngettext_noop_with_backend( + unquote(backend), + :default, + nil, + unquote(msgid), + unquote(msgid_plural) + ) + end + end + + @doc """ + Same as `dpgettext/4`, but takes an explicit backend. + """ + defmacro dpgettext_with_backend(backend, domain, msgctxt, msgid, bindings \\ Macro.escape(%{})) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + domain = expand_domain(domain, __CALLER__) + + quote do + msgid = + unquote(__MODULE__).dpgettext_noop_with_backend( + unquote(backend), + unquote(domain), + unquote(msgctxt), + unquote(msgid) + ) + + Gettext.dpgettext( + unquote(backend), + unquote(__MODULE__).__expand_runtime_domain__(unquote(backend), unquote(domain)), + unquote(msgctxt), + msgid, + unquote(bindings) + ) + end + end + + @doc """ + Same as `dgettext/3`, but takes an explicit backend. + """ + defmacro dgettext_with_backend(backend, domain, msgid, bindings \\ Macro.escape(%{})) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpgettext_with_backend( + unquote(backend), + unquote(domain), + nil, + unquote(msgid), + unquote(bindings) + ) + end + end + + @doc """ + Same as `pgettext/3`, but takes an explicit backend. + """ + defmacro pgettext_with_backend(backend, msgctxt, msgid, bindings \\ Macro.escape(%{})) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpgettext_with_backend( + unquote(backend), + :default, + unquote(msgctxt), + unquote(msgid), + unquote(bindings) + ) + end + end + + @doc """ + Same as `gettext/2`, but takes an explicit backend. + """ + defmacro gettext_with_backend(backend, msgid, bindings \\ Macro.escape(%{})) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpgettext_with_backend( + unquote(backend), + :default, + nil, + unquote(msgid), + unquote(bindings) + ) + end + end + + @doc """ + Same as `dpngettext/6`, but takes an explicit backend. + """ + defmacro dpngettext_with_backend( + backend, + domain, + msgctxt, + msgid, + msgid_plural, + n, + bindings \\ Macro.escape(%{}) + ) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + domain = expand_domain(domain, __CALLER__) + + quote do + {msgid, msgid_plural} = + unquote(__MODULE__).dpngettext_noop_with_backend( + unquote(backend), + unquote(domain), + unquote(msgctxt), + unquote(msgid), + unquote(msgid_plural) + ) + + Gettext.dpngettext( + unquote(backend), + unquote(__MODULE__).__expand_runtime_domain__(unquote(backend), unquote(domain)), + unquote(msgctxt), + msgid, + msgid_plural, + unquote(n), + unquote(bindings) + ) + end + end + + @doc """ + Same as `dngettext/5`, but takes an explicit backend. + """ + defmacro dngettext_with_backend( + backend, + domain, + msgid, + msgid_plural, + n, + bindings \\ Macro.escape(%{}) + ) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpngettext_with_backend( + unquote(backend), + unquote(domain), + nil, + unquote(msgid), + unquote(msgid_plural), + unquote(n), + unquote(bindings) + ) + end + end + + @doc """ + Same as `ngettext/4`, but takes an explicit backend. + """ + defmacro ngettext_with_backend(backend, msgid, msgid_plural, n, bindings \\ Macro.escape(%{})) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpngettext_with_backend( + unquote(backend), + :default, + nil, + unquote(msgid), + unquote(msgid_plural), + unquote(n), + unquote(bindings) + ) + end + end + + @doc """ + Same as `pngettext/5`, but takes an explicit backend. + """ + defmacro pngettext_with_backend( + backend, + msgctxt, + msgid, + msgid_plural, + n, + bindings \\ Macro.escape(%{}) + ) do + backend = Gettext.Compiler.expand_backend(backend, __CALLER__) + + quote do + unquote(__MODULE__).dpngettext_with_backend( + unquote(backend), + :default, + unquote(msgctxt), + unquote(msgid), + unquote(msgid_plural), + unquote(n), + unquote(bindings) + ) + end + end + + ## Helpers + defp expand_domain(:default, _env) do :default end diff --git a/test/gettext/extractor_test.exs b/test/gettext/extractor_test.exs index 9c9311a..1fd9c67 100644 --- a/test/gettext/extractor_test.exs +++ b/test/gettext/extractor_test.exs @@ -314,21 +314,20 @@ defmodule Gettext.ExtractorTest do end defmodule Foo do - import Gettext.ExtractorTest.MyGettext - require Gettext.ExtractorTest.MyOtherGettext + require Gettext.Macros def bar do - gettext_comment("some comment") - gettext_comment("some other comment") - gettext_comment("repeated comment") - gettext("foo") - dngettext("errors", "one error", "%{count} errors", 2) - gettext_comment("one more comment") - gettext_comment("repeated comment") - gettext_comment("repeated comment") - gettext("foo") - Gettext.ExtractorTest.MyOtherGettext.dgettext("greetings", "hi") - pgettext("test", "context based message") + Gettext.Macros.gettext_comment("some comment") + Gettext.Macros.gettext_comment("some other comment") + Gettext.Macros.gettext_comment("repeated comment") + Gettext.Macros.gettext_with_backend(Gettext.ExtractorTest.MyGettext, "foo") + Gettext.Macros.dngettext_with_backend(Gettext.ExtractorTest.MyGettext, "errors", "one error", "%{count} errors", 2) + Gettext.Macros.gettext_comment("one more comment") + Gettext.Macros.gettext_comment("repeated comment") + Gettext.Macros.gettext_comment("repeated comment") + Gettext.Macros.gettext_with_backend(Gettext.ExtractorTest.MyGettext, "foo") + Gettext.Macros.dgettext_with_backend(Gettext.ExtractorTest.MyOtherGettext, "greetings", "hi") + Gettext.Macros.pgettext_with_backend(Gettext.ExtractorTest.MyGettext, "test", "context based message") end end """ @@ -345,13 +344,13 @@ defmodule Gettext.ExtractorTest do #. some other comment #. repeated comment #. one more comment - #: foo.ex:17 - #: foo.ex:22 + #: foo.ex:16 + #: foo.ex:21 #, elixir-autogen, elixir-format msgid "foo" msgstr "" - #: foo.ex:24 + #: foo.ex:23 #, elixir-autogen, elixir-format msgctxt "test" msgid "context based message" @@ -362,7 +361,7 @@ defmodule Gettext.ExtractorTest do msgid "" msgstr "" - #: foo.ex:18 + #: foo.ex:17 #, elixir-autogen, elixir-format msgid "one error" msgid_plural "%{count} errors" @@ -374,7 +373,7 @@ defmodule Gettext.ExtractorTest do msgid "" msgstr "" - #: foo.ex:23 + #: foo.ex:22 #, elixir-autogen, elixir-format msgid "hi" msgstr "" @@ -418,12 +417,9 @@ defmodule Gettext.ExtractorTest do end defmodule FooConflict do - import Gettext.ExtractorConflictTest.MyGettext - require Gettext.ExtractorConflictTest.MyOtherGettext - def bar do - gettext("foo") - Gettext.ExtractorConflictTest.MyOtherGettext.gettext("foo") + Gettext.Macros.gettext_with_backend(Gettext.ExtractorConflictTest.MyGettext, "foo") + Gettext.Macros.gettext_with_backend(Gettext.ExtractorConflictTest.MyOtherGettext, "foo") end end """ diff --git a/test/mix/tasks/gettext.extract_test.exs b/test/mix/tasks/gettext.extract_test.exs index 631d23f..97957c6 100644 --- a/test/mix/tasks/gettext.extract_test.exs +++ b/test/mix/tasks/gettext.extract_test.exs @@ -165,7 +165,7 @@ defmodule Mix.Tasks.Gettext.ExtractTest do defmodule MyApp do use Gettext, backend: MyApp.Gettext - def foo(), do: gettext("hello") + def foo(), do: gettext("hello world") end """) From fdfdf385cf0d6725a34780329e1a04d426f55c27 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Mon, 19 Aug 2024 10:24:30 +0200 Subject: [PATCH 5/5] require --- test/gettext/extractor_test.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/gettext/extractor_test.exs b/test/gettext/extractor_test.exs index 1fd9c67..6285a46 100644 --- a/test/gettext/extractor_test.exs +++ b/test/gettext/extractor_test.exs @@ -417,6 +417,8 @@ defmodule Gettext.ExtractorTest do end defmodule FooConflict do + require Gettext.Macros + def bar do Gettext.Macros.gettext_with_backend(Gettext.ExtractorConflictTest.MyGettext, "foo") Gettext.Macros.gettext_with_backend(Gettext.ExtractorConflictTest.MyOtherGettext, "foo")