Race Conditions with window.Granite?.I18n

We have created a JavaScript micro-frontend in VueJS to handle a new feature for our website. The JavaScript micro-frontend is set up nicely inside of a maven module. Along with it’s own Node Package Manager configuration, we used the aem-clientlib-generator to generate a client library under /apps/sourcedcode/clientlibs/micro-frontend.

Within our VueJS application, we have a utility named i18n.js, the file looks like this (code below). Throughout application, we will call this util i18n(function) to get values from our i18n AEM dictionary.

1
2
3
4
5
6
7
8
export default {
  i18n(id, params, notes) {
    if (window.Granite?.I18n) {
      return window.Granite.I18n.get(id, params, notes);
    }
    return id;
  }
};

When we test out the micro-frontend, we noticed that sometimes the window.Granite, AEM global object, is intermittently working; sometimes the window object is valid/not, so dictionary values are loaded, and sometimes they are not. Why window.Granite is sometimes undefined? Well, this looks like a race condition issue.

What is a race condition?
In short, a race condition occurs when a device or system attempts to do two or more actions at the same time, but the actions must be performed in the proper sequence to be performed correctly.

How can we ensure window.Granite is loaded first before our application is loaded next? In the next section let’s look at the different ways to see how we can do this.


Solutions

  1. Manually Invoking the i18n.js from /libs/clientlibs/granite/utils/source/I18n.js
  2. Include the granite.utils as a dependency

Solutions

In this section of the blog, you will find two solutions to this problem.

1. Manually Invoking the i18n.js from /libs/clientlibs/granite/utils/source/I18n.js

From the page template that your micro-frontend is embedded, you can just call the Adobe core Granite utils > i18n.js before your custom code, like the code provided below. Viewing the source code of your HTML page should look something like this; where the i18n.js is loaded first, and then your application is loaded secondly. Using the proxy method for accessing client libraries, you can serve /libs/clientlibs/granite/utils/source/I18n.js with /etc.clientlibs/clientlibs/granite/utils.js.

1
2
<script type="text/javascript" src="/etc.clientlibs/clientlibs/granite/utils.js"></script>
<script type="text/javascript" src="/etc.clientlibs/sourcedcode/clientlibs/micro-frontend.min.js"></script>

2. Include the granite.utils as a dependency

.

Simply ensure that your client library has a dependency for the granite.utils.

Please be aware that if you are planning to this client library approach, you are actually going to load unwanted JavaScripts apart of your page load. This includes: Sling.js, Util.js, HTTP.js, I18n.js, TouchIndicator.js, OptOutUtil.js, and the init.js (according to the js.txt in the Adobe AEM core library). If all you want is the i18n.js, then you should try out solution #1.

In line:6, notice how the a dependency have been added.

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
   jcr:primaryType="cq:ClientLibraryFolder"
   allowProxy="{Boolean}true"
   categories="[sourcedcode.micro-frontend]"
   dependencies="[granite.utils]"/>

While in this article, we have mentioned that we are using the aem-clientlib-generator for our micro-frontend, so to install it, it would be as simple as adding a new property in the clientlib.config.js, and would be something that looks like this: line:33

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const path = require('path');

const BUILD_DIR = path.join(__dirname, 'dist');
const CLIENTLIB_DIR = path.join(
  __dirname,
  '..',
  'ui.apps',
  'src',
  'main',
  'content',
  'jcr_root',
  'apps',
  'sourcedcode',
  'clientlibs'
);

const libsBaseConfig = {
  allowProxy: true,
  serializationFormat: 'xml',
  cssProcessor: ['default:none', 'min:none'],
  jsProcessor: ['default:none', 'min:none']
};

// Config for `aem-clientlib-generator`
module.exports = {
  context: BUILD_DIR,
  clientLibRoot: CLIENTLIB_DIR,
  libs: [
    {
      ...libsBaseConfig,
      name: 'micro-frontend',
      categories: ['sourcedcode.micro-frontend'],
      dependencies: ['granite.utils'],
      assets: {
        js: {
          cwd: 'clientlib-site',
          files: ['**/*.js'],
          flatten: false
        },
        css: {
          cwd: 'clientlib-site',
          files: ['**/*.css'],
          flatten: false
        },

        // Copy all other files into the `resources` ClientLib directory
        resources: {
          cwd: 'clientlib-site',
          files: ['**/*.*'],
          flatten: false,
          ignore: ['**/*.js', '**/*.css']
        }
      }
    }
  ]
};
Still! My AEM i18n Not Showing Translations!
Sometimes after deploying new dictionary keys and values in your AEM platform, you will realize, in rare occasions, you’re dictionary values are not loading as expected. Have no fear, I have created an article here for how to solve this problem. Click here to learn how to refresh the Apache Sling I18N translation engine.

Hello, I am an enthusiastic Adobe Community Advisor and a seasoned Lead AEM Developer. I am currently serving as an AEM Technical Lead at MNPDigital.ca, bringing over a decade of extensive web engineering experience and more than eight years of practical AEM experience to the table. My goal is to give back to the AEM Full Stack Development community by sharing my wealth of knowledge with others. You can connect with me on LinkedIn.

Leave a Reply

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


Back To Top