Flo0807

Flo0807

Hello everyone!

I am developing a library that contains translations. Users of this library can define a gettext backend in the config.exs script used by my library. Gettext provides functions that take a backend as a parameter. I want to use these functions to translate text in my library, but the translations are not extracted by the mix gettext.extract task.

Suppose I have a module DemoWeb.Gettext with use Gettext in it:

If I have this function in my code, everything works as expected and the translations are extracted when the mix task runs:

DemoWeb.Gettext.dgettext("domain", "translation")

However, when I pass the backend as a parameter, the translation is not extracted:

Gettext.dgettext(DemoWeb.Gettext, "domain", "translation")

Is this because in this function, which accepts a dynamic backend, gettext does not know which backend is used at compile time, because it could be dynamic, and therefore cannot extract the translations?

Is there a better way to integrate gettext into a library or automatically extract translations with a backend defined in the config.exs script?

Thanks in advance. I appreciate any help!

Showing Posts 1 to 6

kip

kip

ex_cldr Core Team

The MyApp.Gettext.*_noop set of macros are designed to help in this situation. They mark the messages for extraction but do not otherwise include any code in your module.

I think the right approach is:

  • Define a Gettext backend in your library in your own namespace
  • Call MyLib.Gettext.dgettext_noop("domain, "message") for each message you want to be extracted.
  • Call the Gettext.dgettext(DemoWeb.Gettext, "domain", "translation") function as you normally would.

This ends up with two calls per message but one is a macro invoked at compile time (to extract the messages only) and the other is a function invoked at runtime to perform the translation.

PJUllrich

PJUllrich

Author of Building Table Views with Phoenix LiveView

Hey @kip, may I ask you to elaborate where you’d put each function in your application, please?

I’m currently trying to define messages for multiple domains, but to fetch the translation with a dynamic domain.

I came up with something like this, but I don’t like that I have to repeat the dgettext_noop_with_backend/3 call for every domain plus the default domain:

defmodule DemoWeb.Domains do
  require Gettext.Macros

  defmacro dtext(domain, message) do
    quote do
      Gettext.Macros.dgettext_noop_with_backend(DemoWeb.Gettext, "default", unquote(message))
      Gettext.Macros.dgettext_noop_with_backend(DemoWeb.Gettext, "domain1", unquote(message))
      Gettext.Macros.dgettext_noop_with_backend(DemoWeb.Gettext, "domain2", unquote(message))

      Gettext.dgettext(DemoWeb.Gettext, unquote(domain), unquote(message))
    end
  end
end

# In demo_web.ex
  defp html_helpers do
    quote do
      use Gettext, backend: DemoWeb.Gettext

      import DemoWeb.Domains
    end
  end

# home.html.heex
{dtext(Enum.random(["default", "domain1", "domain2"]), "hello world")}

If I refactor the three calls into a loop, I sadly receive the following error:

defmodule DemoWeb.Domains do
  require Gettext.Macros

  defmacro dtext(domain, message) do
    quote do
      for d <- ["default", "domain1", "domain2"] do
        Gettext.Macros.dgettext_noop_with_backend(DemoWeb.Gettext, d, unquote(message))
      end

      Gettext.dgettext(DemoWeb.Gettext, unquote(domain), unquote(message))
    end
  end
end
(ArgumentError) Gettext macros expect message keys (msgid and msgid_plural),
domains, and comments to expand to strings at compile-time, but the given domain
doesn't. This is what the macro received:

{:d, [line: 1, counter: {DemoWeb.PageHTML, 23}], DemoWeb.Domains}
LostKobrakai

LostKobrakai

That for loop needs to go outside the quote.

PJUllrich

PJUllrich

Author of Building Table Views with Phoenix LiveView

@LostKobrakai thank you! I tried that but then it executes the Gettext.dgettext/3 three times and I receive a triple string back!

defmodule DemoWeb.Domains do
  require Gettext.Macros

  defmacro dtext(domain, message) do
    for d <- ["default", "domain1", "domain2"] do
      quote do
        Gettext.Macros.dgettext_noop_with_backend(DemoWeb.Gettext, unquote(d), unquote(message))

        Gettext.dgettext(DemoWeb.Gettext, unquote(domain), unquote(message))
      end
    end
  end
end

# page.html.heex
{dtext(Enum.random(["default", "domain1", "domain2"]), "Hello World!")}
=> "Hello World!Hello World!Hello World!"
LostKobrakai

LostKobrakai

noops = 
    for d <- ["default", "domain1", "domain2"] do
      quote do
        Gettext.Macros.dgettext_noop_with_backend(DemoWeb.Gettext, unquote(d), unquote(message))
      end
    end 

quote do
  unquote(noops)
  Gettext.dgettext(DemoWeb.Gettext, unquote(domain), unquote(message))
end

I’d always suggest writing the code you want to generate first, then try to write the macro that is meant to generate that code.

PJUllrich

PJUllrich

Author of Building Table Views with Phoenix LiveView

Heck yeah! That worked! Thank you so much! :heart:

Just for completeness sake, here’s my solution:

defmodule DemoWeb.Domains do
  require Gettext.Macros

  defmacro dtext(domain, message) do
    noops =
      for d <- ["default", "domain1", "domain2"] do
        quote do
          Gettext.Macros.dgettext_noop_with_backend(DemoWeb.Gettext, unquote(d), unquote(message))
        end
      end

    quote do
      unquote(noops)
      Gettext.dgettext(DemoWeb.Gettext, unquote(domain), unquote(message))
    end
  end
end

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews