What is a good way to allow non-technical folks to change copy text in a Phoenix application?

I’m working on a Phoenix 1.7 application that uses LiveView and Gettext, and we ensure that every string in .heex templates is wrapped in a gettext/1 call. Recently we’ve had requests to change the copy text in some of the pages and we’re struggling to make the changes in a timely manner.

I’m looking for a good strategy to allow non-technical people to edit the text that go inside LiveViews so that each change doesn’t have to go through the loop of being committed as a code change and then scheduled for deployment days later. We really need a way of enabling authorized persons to change string text but I haven’t been able to find much in the topic. We need this so badly that we’re willing to drop gettext in favor of whatever else can help us get there.

Internally we’ve discussed ideas like using a CMS or serving the .pot file in a different system that would be somehow consumed by the Phoenix application, but this either seems too complex or requires extensive refactoring of existing LiveViews. There has to be a cleaner way to do this, so I’m wondering if I can get some help getting there?

Some of the changes are as little as the label in a tooltip, and others are changing some lines of text inside of a <div>.

Thanks in advance!

When you already have gettext, why do you need to make changes in the source code? You can use gettext to translate any language you support – even the one that is used in the source code.

1 Like

Well, this may be a lack of experience with gettext, but the .pot file is being checked in to the repo currently. Should it be in the gitignore? :thinking:

I’m now coming to the realization that this may be really dumb, but here’s what we’re doing for copy changes:

<%!-- my_app_web/live/some_view.html.heex --%>
<h1>{gettext("This is a header")</h1>

We’d go in the template file, change the string inside the gettext call and regenerate the pot file with mix gettext.extract, then commit the changes.

Are you suggesting that we change the text in the .pot files directly? Like Ctrl + F "This is a header" and swap it there? The reason I didn’t do that is because there seems to be a reference to the actual file just above the string values, and I was afraid it’d break something if I changed the files directly.

Regardless, changing the pot file directly and pushing it doesn’t really get us to the point of allowing people that are not on Github to do it. Feel like I’m missing something obvious :thinking:

Platforms to crowdsource translations have been around for a long time. I guess you could do what they do? I’m not deeply familiar with how the Gettext package works. Does anyone know if the translations can be swapped out at runtime?

I suppose if nothing else you could write a set of drop-in replacements for the gettext functions. Store the strings in Postgres and cache them in an ETS table (or maybe persistent_term?). Allowing people to edit your UI in real time seems a bit unhinged but at the same time I think it’s kind-of a neat idea. Like turning your app into a wiki! :slight_smile:

1 Like

We aren’t looking for crowdsourced translations, instead allowing select people like Marketing and Product leads to change copy in certain parts of dashboards and tooltips to make things clearer for users.

But yeah, you got the gist: we would like to have these changes either apply at runtime and/or completely bypass our deployment process so that we can change these strings quicker and in simpler manner.

Gettext is a tool we’re using that maybe could do the trick, would be great if it could but not a deal breaker if we had to drop it.

I’m not suggesting to change the .pot. I’m suggesting to have a corresponding .po for english. There a translator can “translate” the source text of “This is a header” to “This is now a slightly differently worded header” without the source code nor the .pot file needing to change. This comes at the expense of the source code no longer holding the latest english content as keys, but you can backport those updates into the source code at regular intervals if you want – could probably even be somewhat automated. You’re at least not blocking the content editing even for english.

4 Likes

Devs write “placeholders”, --extract the POT, which then remains unchanged. Any translation is added by translators to the *.po files…

I do not understand why you want to touch the sources again, after it was settled once…

Though, maybe you see the “placeholder” as the canonical or fallback translation?

In that case, you treat it-at least in my opinion-the wrong way… The “placeholder” should be treated an identifier. It should be written once, and then touched never again. This way there is no dev needed to adjust wording.

Instead, wording gets adjusted in the english PO files when they are necessary, or in the french, or german, or whatever.

Sadly I have not yet found to make a locale a fallback if a translation is missing. So your team should be quick with translations… Or you keep the identifier just meaningfull enough, but not canonical phrase in whatever fallback language you choose.

2 Likes

Yeah, what I was getting at is that you could build a frontend similar to those tools to generate the po files. I think that’s what Benjamin was also implying.

You could bypass the deploy process by simply transferring the po files to the server somehow - if the frontend is within the same app you could do this entirely internally.

What I’m not sure about is whether Gettext reads the po files at compile time or runtime, and whether there is some cache that needs to be busted. I am assuming it doesn’t read from disk every time gettext() is called. I’m trying to read the implementation but it’s several macros deep and I’m getting lost.

1 Like

The gettext() macro inlines translations at compile time where translations are requested. For runtime translations the po file contents are compiled into the gettext backend module.

3 Likes

There’s this library that may interest you as it seems to address some of your concerns.

2 Likes

This is what I was missing. I didn’t know this is how it worked, thank you for making it clear!!! :slight_smile:

So as long as I have a good default value in my .pot file and I build an e.g. english.po file, I can have non-technical people edit the english.po. Awesome, this gets me one step further!

Building in a way for trusted users to edit .po files is doable, although I’d like to have some sort of check on update that verifies if the file wasn’t botched in some way.

This means that it’s not a matter of just copying the files to the right place, I’d have to recompile at least the gettext backend module, right?

Yes. The gettext library doesn’t do runtime .po loading. The underlying expo can do that, but would require changing how you interact with those translations.

1 Like

kanta looks interesting! will definitely take a look, thanks! :eyes:

I wonder if we could host some internal LiveBook that we’d serve to the team with a simple WYSIWYG editor to change the english.po file and a an update button that would:

  • Check if the .po file is okay and not broken in some way
  • Publish a PR that commits the changes to the .po file (for persisting the changes)
  • Generate and deploy a new release with the .po file

In any case, all of you have given me a lot of ideas and directions to work into, thank you so much!

There exist 3rd party services, which can do such workflows.

2 Likes

I have considered Beacon CMS for some pages for this kind of workflow benefit.

Similarly, the trouble comes in where I really just want to have a set of components that can be managed instead of having to have the whole page

1 Like