Lazy Loading Locales with Angular

Instead of loading all supported locales upfront, we can now load them on demand.

Angular 9 introduces a new implementation for its built-in I18N support called code>@angular/localize. One concept it contains is called global locales. These are bundles with meta data for different date and number formats the CLI can add after compiling the application.

Normally, the CLI uses these bundles under the hood to dramatically speed up the creation of different language versions. However, we can divert it from its intended use to load the meta data dynamically on demand.

This article shows how to leverage this. The used example can be found in my GitHub repo.

Big thanks to Angular's Pete Bacon Darwin who helped me to see the whole picture regarding global locals.

Situation Before Angular 9

Beginning with Angular 5, Angular is shipped with meta data for different locales. It is derived from the Unicode Common Locale Data Repository (CLDR). To support some of them, we had to import and register them before the application starts, e. g. within main.ts:

import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
import localeDeAt from '@angular/common/locales/de-AT';
import localeEs from '@angular/common/locales/es';

registerLocaleData(localeDe);     // de-DE
registerLocaleData(localeDeAt);   // de-AT
registerLocaleData(localeEs);     // es-ES

[...]

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

In addition to these locales, en-US is always available.

Obviously, this increases the size of the main bundle as all this meta data is referenced at compile time. As the next chapters show, Angular 9 introduces a way to prevent this situation.

Global Locales

Beginning with version 9, Angular is also providing a special set of bundles registering the locale meta data globally at global.ng.common.locales. For instance, in the case of German, the meta data would be put to global.ng.common.locales['de']. Hence, they are called global locales.

These bundles are located within code>node_modules/@angular/common/locales/global:

global locales

Global Locals go hand in hand with the new code>@angular/localize package which brings the built-in I18N support to the next level. The idea of this package is to only compile the application once. Then, this compiled version is duplicated for each language one wants to support and each of these duplications is modified.

These modifications involve exchanging texts but also adding the right meta data. For the latter one, we need a way to simply add this meta data after the build. This is what global locals enable.

Obviously, this approach is much faster than the original one which compiled the whole application once per language.

Saying this, normally you don't need to deal directly with these global locals because the CLI does the heavy lifting for you. If you want to try it out, you find a wonderful blog post about code>@angular/localize here.

However, if you don't use code>@angular/localize do deal with translation texts but go with something like ngx-translate instead, directly dealing with global locales will come in handy. This is also the case if you do use code>@angular/localize but define the translation texts programatically, e. g. after loading them from a custom service in a custom data format (which is called runtime tranlation).

In these cases, loading just the needed meta data on demand instead of loading the meta data for all languages upfront will improve your startup performance.

Lazy Loading

To demonstrate lazy loading global locals by hand, I've created a simple application which is using en-US by default:

example

After clicking the button German Version, the German locale meta data is lazy loaded and used. For this, I've created a simple script loader which is basically creating a script tag dynamically:

//
// Simplest Possible Script Loader TM
//

@Injectable({
  providedIn: 'root'
})
export class SimpleLoaderService {

  constructor() { }

  // Remeber already loaded files so that we
  // don't load it again.
  private loadedFiles = new Set<string>();

  loadScript(src: string): Promise<void> {

    // If the file is already loaded don't do anything.
    if (this.loadedFiles.has(src)) {
      return Promise.resolve();
    }

    return new Promise<void>((resolve, reject) => {

      // Create a script tag and point to java script file
      const script = document.createElement('script');
      script.src = src;

      // Resolve Promise after loading
      script.onload = () => {
        this.loadedFiles.add(src);
        resolve();
      };

      // Reject Promise on error
      script.onerror = () => {
        reject();
      };

      // Add script tag to page
      document.body.appendChild(script);
    });
  }

}

Our demo's AppComponent holds a date which happens to be the authors birth day (just saying ...) and a property holding the locale to use:

@Component({ [...] })
export class AppComponent {

  date = new Date('2020-01-20T17:00+01:00');
  lang = 'en-US';

  constructor(private loader: SimpleLoaderService) {
  }

  toGerman() {
    this.loader
        .loadScript('assets/de.js')
        .then(_ => this.lang = 'de-DE')
        .catch(err => console.error('Error loading file', err));
  }

}

The template displays this date as shown above:

{{ date | date:'long':'':lang }}

The method toGerman is bound to the shown button and uses our SimpleLoaderService to load the German locale. After that, it updates the lang property.

Testing the Application

To demonstrate that lazy loading really takes happen, you can open Chrome's network tab within the dev tools and click German Version within the page. You should see that this lazy loads the assets/de.js file. Also, you should now see a German date.

Lazy Loading

Conclusion

If you fully go with code>@angular/localize and compile time translation, you don't need to concentrate on gobal locals. But if you use other tools for loading translation texts or runtime translation, loading the meta data on demand will come in handy. This is especially the case if you use solutions like ngx-translate which allow to switch between languages at runtime.

References

More in Our Workshop

If you want to learn more about such advanced topics, check out our Advanced Angular Workshop which focusses enterprise applications and sustainable architectures.

Advanced Angular Workshop