Content translations in Power Pages – the custom way

By | September 22, 2025

Over a year ago, I blogged about GC Web implementation in Power Pages:

Just to re-cap, it’s a completely custom power pages web site that’s not using entity forms or lists, and, instead, that’s relying on the Javascript/Liquid to render the content. Most of the things I blogged about back then have not changed much, of course there were some tweaks and adjustments, but there was one particular piece I forgot to clarify.

How can we implement content translations?

Traditionally in Power Pages, you’d probably do it with Content Snippets

Valentin Gasenko just published a good overview of the snippets functionality:

https://www.linkedin.com/pulse/power-pages-content-snippets-fastest-path-clean-copy-valentin-gasenko-htpje/?trackingId=lnFYtofZRuWZ4QBF8cEHDg%3D%3D

And there is another piece of this puzzle that Nick Doelman added to the discussion:

Power Pages: Inserting Content Snippets in Design Studio

Which is great, but there are a few things about content snippets which I don’t quite understand. Mainly, it’s these two:

  • Content Snippets are not that different from Web Templates, so why do we have them?
  • Content Snippets are difficult to manage (for the translators/stakeholders), so we need to simplify translations management somehow either way

The first item is “theoretical”, the second one is more practical, since, if we are supposed to build a dedicated application to make it easier to manage the snippets, then we can also use other options.

Of course the benefit of snippets is that they can be deployed with the portal, but that’s almost irrelevant on a larger project, since you will likely have a way of deploying your “reference data” for all your custom tables and lookups anyways.

Ok, this was all to say that, in our case, we did not use the snippets, and I can this all aside now.

The rest of the blog is not going to end up with a turn-key solution, by the way, but there will be enough explanations and code samples to replicate the solution.

The idea is pretty simple:

There is a custom table which the business users will use to manage content. On the diagram above, it includes two fields for English and French, but this can also be adjusted to have a Label and a lookup to the language table, for instance. Data in that table can be managed directly in the main business MDA together with all the other configuration data.

When rendering the page, a few things needs to be coded at first, but, from there, everything becomes pretty much declarative.

In the header, we need to create a javascript object that will include all the labels. It’ll be cached when header caching is enabled, so beware… if caching starts getting in the way, use “substitution”. The code might look like this, for example:

{% capture labelsQuery %}
<fetch version="1.0" mapping="logical" no-lock="true" distinct="true" >
    <entity name="ita_contenttable" >

        <attribute name="ita_name" />
        <attribute name="ita_labelen" />
        <attribute name="ita_labelfr" />
        <filter type="and" >
            <condition attribute="statecode" operator="eq" value="0" />
        </filter>
    </entity>
</fetch>
{% endcapture %}

{% fetchxml portalLabels %}
{{labelsQuery}}
{% endfetchxml %}

<script>
var lbls={};


{% for record in portalLabels.results.entities %} 
   if(!lbls[`{{record["ita_name"]}}`]) lbls[`{{record["ita_name"]}}`] = {}; 
   lbls[`{{record["ita_name"]}}`]={en: `{{record["ita_labelen"]}}`, fr: `{{record["ita_labelfr"]}}`};
{% endfor %}

</script>

That way, you’d have your javascript “lbls” object populated with the content.

Then you can use this script in the footer to do the replacement:

<script>
$("[content-key]").each(function (i, item) {
   let labelData = lbls($(item).attr('content-key'));
   let labelContent = labelData['{{website.selected_language.code}}'];
   $(item).html(labelContent);
});
</script>

This will do the replacement. There could be some flickering, occasionally, so you might want to, perhaps, display a semi-transparent div on top of the content initially then hide it, for example. It seems this is not such a big issue to really bother, though.

That’s the essence of it, but what are the benefits compared to the snippets?

First of all, our code is a bit more complicated than the code above. We have different security settings and custom roles, so what we actually do is:

  • In the “Content Table”, we also have a field for the “role”
  • We have some additional associations to the declarative filters (another table)
  • Then, in the scripts above, we identify the label by looking not only at the “content-key”, but, also, at the signed in user role and at some of the properties of the page/user to match that label to the declarative filter. For example, say we had a page to display vehicle details. We could have a label which would say “EV Vehicle Details” for an EV vehicle, and “Gas Vehicle Details” for a gas vehicle. Both labels would have the same key. That’s just to give you an example, tough, in our case the business domain is very different from that.

Could you do it with the snippets? Possibly, though you might have to customize the snippets table, you might have to create a custom application to manage them, or you might actually have to hardcode certain things. But you’d be using out of the box snippets… So, pros and cons, but this is just in case you are looking for some alternatives.

Leave a Reply

Your email address will not be published. Required fields are marked *