saveman71

saveman71

Hello!

I want to generate HTML that would be similar to:

English:

Hello, Please click here to learn more !

French:

Bonjour, Veuillez cliquez ici pour en apprendre plus !

My best attempt:

<%= raw(
  gettext(
    "Hello! Please %{link} to learn more!",
    link: gettext("click here") |> link(to: path) |> safe_to_string()
  )
) %>

But the usage of raw and safe_to_string, the nested gettext really make me wonder if I’m in the right direction. The translation is also unfortunately split into 2, making the translation job harder.

I also thought about embedding the link inside the translation, like below:

<%= raw(
  gettext(
    "Hello! Please <a href=\"%{link}\">click here</a> to learn more!",
    link: path
  )
) %>

First we still need to use raw and now we have HTML in the translations making the whole thing very brittle, and we have to escape the link, we don’t use the native component…

The naive way of doing (below) feels worse because the translation is now split in 3 (think about a paragraph with 2, 3, links), making the translation job even harder.

<%= gettext("Hello! Please") %> <%= link(gettext("click here"), to: path) %> <%= gettext("to learn more!") %>

How do you guys do it? All three options work, but neither feels just right.

Showing Posts 1 to 10

saveman71

saveman71 OP

For posterity, here’s some insight on Stack Overflow https://stackoverflow.com/q/273847/236784 since this might not be so Elixir specific.

Cochonours

Cochonours

I was curious as I haven’t done i18n with gettext in a looong time, but here are some better options IMHO : Gettext html in translation - #3 by danschultzer

  <%= gettext("Already have an account? %{sign_in} to continue", sign_in: safe_to_string(link(gettext("Sign in"), to: Routes.session_path(@conn, :new)))) |> raw() %>

No html in the strings, and all in one place.

saveman71

saveman71 OP

Thanks for finding that post! I’m a bit ashamed that I wasn’t able to find it myself :frowning:

I thought of that option, and it works quite well if the source language is English (same language as the interpolation key).

In my case, the source language is French (and I am not in a position to change that). So the solution becomes:

<%= gettext("Vous avez déjà un compte ? %{sign_in} pour continuer", sign_in: safe_to_string(link(gettext("Connectez-vous"), to: Routes.session_path(@conn, :new)))) |> raw() %>

It’s less ideal, but works. I’m still not 100% convinced because the two translation strings are not so connected (hard for the translator to make the connection between the two, in the middle of 100s of other strings).

Maybe we can make an exception and use French only for these keys?

<%= gettext("Vous avez déjà un compte ? %{connectez_vous} pour continuer", connectez_vous: safe_to_string(link(gettext("Connectez-vous"), to: Routes.session_path(@conn, :new)))) |> raw() %>

So given all these compromises, I also consider the following solution:

<%= gettext("Vous avez déjà un compte ? %{a_start}Connectez-vous%{a_end} pour continuer", a_start: "<a href='/login'>", a_end: "</a>") |> raw() %>

Unfortunately:

  • it’s very brittle
  • we can’t use link/2 anymore
  • if we have multiple links, it starts to be even more brittle (a1_start, a2_start, etc. no good solution)

Compromises :sweat_smile:

LostKobrakai

LostKobrakai

That’s exactly the issue here. Translation tools aren’t well suited for allowing the translation of segmented text without adding the details about the segmentation into the translatable text. This becomes even more tricky if the translation might reorder links (when multiple ones) in which case you actually require something in the translated text to map to the correct link.

Doing that with less compromises would likely require a new dataformat for doing translations with, which explicitly supports this usecase.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

There are a lot of good answers here, so I’ll just throw my $0.02 in and note that perhaps a different design would make this easier? What you have is basically a “call to action”, and calls to action work better when they are pulled out from the actual text and are visually represented in a more emphatic way like a button. This happens to also make the translation easier since you aren’t doing an inline link.

saveman71

saveman71 OP

TBH I crafted these examples because they were easy to understand, it’s a good point though, when possible taking the link out is good practice!

But for example, there are two good examples on this page only:

image

image

(and that last one can have multiple links too!)

odd

odd

I found this article very useful when dealing with HTML in translations
https://angelika.me/2021/11/23/7-gettext-lessons-after-2-years/

kip

kip

ex_cldr Core Team

@saveman71, As is often the case Unicode has a slightly different take that is, I think more suited to complete language expressions. Its the Unicode Message Format and I have an implementation of it in ex_cldr_messages.

After the great work done by @maennchen, the latest versions of gettext fully supports merging those messages into a .po file just like any other message type (little know fact that .po files are independent of the message type, its just that gettext messages predominate).

Its possible this a better fit for your needs and if so, let me know and I’m happy to help.

LostKobrakai

LostKobrakai

I recently looked into ICU messages for exactly the reasons discussed and it also doesn’t really have an answer for it, even though there’s a lot of powerful stuff in them. It’s still build to handle a string of text – even though with a lot more gramatical/language related options – but it’s not really better in handling formatted text or text interspersed with other types of markup.

kip

kip

ex_cldr Core Team

I smell an opportunity. Very open to thoughts on what an API might look like to make this more ergonomic from a developer point of view.

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
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
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
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
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews