Implementing GC Web (government of Canada) theme. Part 3: adding lookup localizations

By | August 5, 2024

It’s the third part of these series, and I’m assuming you’ve at least looked at the first two, since this is where I talked about how to enable GC Web for Power Pages, and, also, about how to configure some of the language-specific features once the theme has been “enabled”.

Let’s talk about lookup localizations in this post.

To being with, lookup localizations don’t work that well on the model-driven application side either. Somehow, the platform has always lacked this functionality. So, if you’ve ever worked on the government of Canada D365/Power Platform projects, you probably know the drill:

  • For any lookup that requires translations, you’d create two fields to keep English and French labels
  • You’d use a plugin (on “Update” and on “Create”) to put a combined value into the name field, and that value would use a special separator between English and French labels (for example, |~)
  • And you’d use yet another plugin (on “Retrieve” and on “RetrieveMultiple”) to intercept what’s being displayed to the user, since you’d need to parse the values stored in the “Name” field and only leave language-specific part in the output. You’d do it based on the preferred language of the user making the request, and you’d have required user id in the plugin context
  • There are some other considerations to keep in mind: there may need to be an additional plugin to handle translations in the timelines, yet sorting on the “name” field won’t work properly in the “second” language, since your name field will always have the following format: <First Language>|~<Second Language>, etc

I’ll show you how to implement such plugin in one of the other posts, but, for the purpose of this post, the important thing is that “Retrieve” and “RetrieveMultiple” parts of it have to be turned off for all requests from the portal.

Why is that?

The plugin described above needs to be able to see preferred language of the user making the request, and it works fine for the system users, but it breaks for the portal “users” for a few reasons:

  • Portal calls are usually done under the same system user account. Potentially, you can use IPluginExecutionContext2 interface to figure out portal contact id, since there is PortalContactsId property in that interface. I had certain issues with that, though. It seems that property can be null even for some of the the portal calls
  • What’s worse, though, is that a lot of portal calls will be cached, the same cache can be used for different users, so you either need to add something unique to each request to essentially break the cache, or this is not going to work
  • But let’s say you’ve figured out the caching problem (by making each request unique), you still won’t have access to the “onpage” language (the one your power pages users would choose for their “current session”) in the plugin, you’d only have access to the preferred language in the contact record. Which is not what you need, though, since a user may decide to switch to English while having French as their preferred language in the profile

Hence, all those translations have to be applied on the browser side (just keep in mind we are talking about lookup translations, not about the option sets and/or page content in general).

So, let’s ignore the plugin for now (since we can’t rely on the retrieve/retrievemultiple when it comes to Power Pages), and let’s just manually create required data in the lookup table in exactly the same way.

There will be two lookup records in the ITA Lookup table (it’s a custom table I just created for this post):

There is, also, ITA Data table that has a Name field and a Lookup field. The lookup field is pointing to the ITA Lookup table. So, potentially, we could have created 3 different views. One of those would be showing EN label from the lookup table, another one would be showing FR label from the lookup table, and yet another one would be showing Name field from the lookup table

With that in place, let’s create a List in Power Pages for ITA Data table, add Table Permissions… or just disable table permissions for that list (I assigned them to the anonymous web role, since I did not want to have to deal with the sign in for this post), and add a web page that’s using that list.

Note: For the list, choose the last view (the one that’s displaying both combined English and French label in the lookup column.

As a side note: make sure the content pages have that list assigned to them. If you create a page first, then add a list, then use it for the web page, content page won’t just start using it automatically. Either way, just double check if you have that list selected for the content pages:

Sync, refresh, open the page, and you will likely won’t see the list yet.

Instead, you’ll see an area on the page where that list was supposed to show up, but it will be empty:

This is because wet-boew uses DOMPurify to sanitize HTML, so you need to open your GC Web Footer template and disable DOMPurify by adding this code just in front of the closing “</body>” tag in that template:

<script type="text/javascript">
DOMPurify.isSupported = false; 
</script>

Without this, DOMPurify will keep removing thead tags from the rendered lists, and, ultimately, those lists wont’ work.

With that in place, though, sync->refresh, and you should finally see the list:

So, finally, you can see the problem – that lookup column is not translated at all.

You could have chosen another view for your list, or you could have added one more view to the list (so you’d have one view with English labels and another one with French), but it’s not going to work quite well:

As you can see above, you’d be getting view selector rendered just above the list, but you actually need to render a view based on the user language.

One option might be to update the part of your GC Web Template to use the code below:

{% if page_entity_list %}
{% include 'entity_list' key: page_entity_list %}
{% endif %}

You’d need to create two copies of each lists, then, and use language-specific views for each. Then you could set page_entity_list variable in the content pages like this:

{% assign page_entity_list='<LIST ID’ %}

Then use “french” list id in the French content page, and use “english” list id in the English content page. That would somewhat take care of it.

BUT, we can probably do better. Let’s fall back to the “combined” label first:

Open GC Web Footer template again, and add the following script just before the closing “</body>” tag:

<script type="text/javascript">
//Localizations script

let lang_separator = "|~";
let selected_languge = $('html').attr('data-lang');

function translateText(text) {
  let result = text;
  if (text && text.includes(lang_separator ))
    result = text.split(lang_separator )[selected_languge == "en" ? 0 : 1];
  return result;
}

function translateElements()
{
  try{
    for (var i=0; i < document.all.length; i++) {
      let element = document.all[i];
      if(element.childNodes.length == 1 && element.childNodes[0].childNodes.length == 0 && element.innerText.contains(lang_separator))
      {   
          element.innerText = translateText(element.innerText);
      }
    else if(element.childNodes.length == 2 && element.childNodes[0].childNodes.length == 0 && element.childNodes[0].textContent.contains(lang_separator))
      {   
          element.childNodes[0].textContent = translateText(element.childNodes[0].textContent);
      }
    }
  }
  catch(e)
  {
    console.log("Error in translateElements: " + e.message)
  }
}

function startTranslationsObserver()
{
  const config = { childList: true, subtree: true };
  const callback = (mutationList, observer) => {
    for (const mutation of mutationList) {
      if (mutation.target && mutation.target.innerText.contains(lang_separator)) {
        translateElements();
      }
     
    }
  };
  const observer = new MutationObserver(callback);
  observer.observe(document.body, config);
}



document.addEventListener("readystatechange", (event) => {
        translateElements();
        startTranslationsObserver();

});
</script>

This is going to create a mutation observer, so whenever an html element is added to the document, observer callback function will kick in, it will look for all occurrences of “|~” (remember that language separator I mentioned at the beginning of the post?), and it’ll only keep part of that label that corresponds to the currently selected language. Initially, that function will be called once the document has been loaded, and, from there, mutation observer will pick it up:

Ok, so far so good, but here comes the contradictory part.

In general, GC Web does not seem to be fully compatible with Power Pages lists and forms. It’s using different scaffolding, it’s supposed to present form validations slightly differently, it occasionally breaks action buttons functionality (since wet-boew attaches a lot of event listeners to the input components), and, in general, the look and feel of Wet-Boew/GCWeb tables and forms is different from how lists and forms look like in Power Pages.

Ultimately, you can’t help but to start thinking if it may be better to do custom list and form rendering instead, since that is going to allow you to stay compliant with the GoC web standards and meet GoC accessibility requirements (since GC Web has sample implementation for most of the components you may need).

So, then, instead of using out of the box Power Pages lists and forms, we may need to figure out how to use GC Web-based tables and forms. That’s a bit of a bummer since we need to give up a great feature of Power Pages, but it’s not necessarily that bad.

Let’s start with a “table” component in Part 4

Leave a Reply

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