Gettext.extract not looking into .eex files (not using Phoenix)?

I am using EEx.eval_string in combination with eex template files as part of an email generation process. This is not a phoenix project. I am introducing gettext to do internationalisation, with calls to gettext inside the eex files. For example:

<b><%= gettext("This is a test for %{name}, %{description} test", name: name, description: description) %></b>

I noticed initially that this wouldn’t work unless I imported the gettext backend directly in the eex files, like this:

<% import Notification.Gettext %>

This works but the mix task to do the extraction into POT files doesn’t look into the EEX files. I see lots of examples of the mix gettext.extract task working inside EEX files but they are all Phoenix based and the import is being done inside one of the view files. In my case, is it possible to do the import in such a way that the extract task will realise it should include the EEX files?

Any help would be appreciated.

eval_string is executed at runtime. For gettext to pick up those values you need to use the EEx.compile_* functions.

1 Like

gettext itself is working fine as is, including the substitutions. It is just the gettext.extract process that isn’t working, presumably because it doesn’t see that eex files are importing the gettext backend. Switching to EEX.compile_string with Code.eval_quoted doesn’t help - the import in the eex is still needed and gettext.extract still doesn’t look into the eex files.

You probably want both. Importing the backend in the module and compiling the template into that module. That’s how it works in phoenix.

Why not simply EEx.function_from_file?

1 Like

Both what though? I am already importing the backend in the module doing the Eval and it isn’t enough, it still needs it in the eex file, even when I switch to compile then eval_quoted. Re why not File, good question! It is legacy code, not sure why they read it in and then eval_string the string, but switching to eval_file doesn’t make any difference.

Anything involving eval_* is runtime (unless you put it in a module body, which I guess you don’t). Extracting gettext strings requires code to be part of some module at compile time.

1 Like

EEx.function_from_file did it! I wasn’t quite understanding what you were saying initially. Some test code for anyone else who lands here later:

defmodule Notification.EmailImpl.WelcomeEmailTemplate do
  @moduledoc false

  alias Notification.EmailImpl.EmailTemplateUtil
  import Notification.Gettext
  require EEx

  @external_resource template = Path.join([__DIR__, "template/welcome_template_email.html.eex"])

  EEx.function_from_file(:defp, :template_as_function, template, [:vars])

  def render(%{starts_on: _starts_on, ends_on: _ends_on} = variables) do
    vars = variables
    |> Map.update!(:starts_on, fn date -> EmailTemplateUtil.to_localised_date_string(date) end)
    |> Map.update!(:ends_on, fn date -> EmailTemplateUtil.to_localised_date_string(date) end)
    |> EmailTemplateUtil.compute_values()

    Gettext.with_locale("es", fn -> template_as_function(vars) end)

and in the template:

<%= gettext("You have been enrolled in the following tutorial, %{name}, starting on %{starts_on}.", name:, starts_on: vars.starts_on) %>

Thanks LostKobrakai!