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>.
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.
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?
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
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!
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.
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.
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.
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.
This is what I was missing. I didn’t know this is how it worked, thank you for making it clear!!!
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.
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!