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

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New
perzanko
Do we have maintainer of absinthe-graphql/dataloader here? We have a PR that we’d like to merge to the library. Relatively small additive...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
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
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
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews