Locale Support Prior to Angular 9

Since Angular 5, the framework has shipped with locale-specific meta data derived from the Unicode Common Locale Data Repository (CLDR). To use a particular locale, developers had to explicitly import and register it prior to application bootstrapping, typically inside 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));

The en-US locale has always been included by default without any additional setup.

This approach inevitably bloats the initial bundle, because all imported locale data is statically linked at build time. The following sections demonstrate how Angular 9 provides a more flexible alternative.

Understanding Global Locales

Starting with version 9, Angular ships dedicated bundles that assign locale meta data to the global.ng.common.locales object. For instance, German locale information would be stored under global.ng.common.locales['de']. Hence, the term "global locales."

You can find these bundles in the package directory node_modules/@angular/common/locales/global:

global locales

Global locales are closely tied to the new code>@angular/localize package, which significantly upgrades the built-in I18N pipeline. Its core concept is a single compilation of the app: you build the application exactly once, replicate the output for each target language, and then apply language-specific customizations to each copy.

These customizations include both text replacement and the injection of locale-specific formatting meta data. To accomplish the latter post-build, a straightforward mechanism for injecting this data is essential — which is exactly what global locales provide.

This strategy proves substantially more efficient than the previous method, which required recompiling the entire application once for each language.

In typical scenarios, you won't interact with global locales directly, as the Angular CLI handles them automatically behind the scenes. Those interested in exploring code>@angular/localize further can check out this excellent blog post.

That said, if your project relies on an alternative translation library like ngx-translate rather than code>@angular/localize, or if you use code>@angular/localize with runtime-loaded translation texts (fetched from a custom backend), then handling global locales manually becomes quite relevant.

In such scenarios, fetching only the meta data actually needed, rather than preloading everything, can markedly enhance initial load performance.

Implementing Lazy Loading

To illustrate manual lazy loading of global locales, I put together a compact sample app that starts in en-US:

example

Clicking the German Version button triggers the on-demand loading and activation of the German locale. To achieve this, I implemented a minimal script loader that dynamically constructs a script element:

//
// 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);
    });
  }

}

Within the demo's AppComponent, there's a date (coincidentally the author's birthday) and a property that tracks the currently active locale:

@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 renders this date accordingly:

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

The toGerman method, wired to the button, employs the SimpleLoaderService to fetch the German locale and subsequently updates the lang property.

Verifying the Behavior

To confirm lazy loading is functioning, open Chrome's developer tools, navigate to the Network tab, and click the German Version button. You'll observe that assets/de.js is fetched at that moment, and the displayed date switches to a German-formatted version.

Lazy Loading

Final Thoughts

When fully embracing code>@angular/localize with compile-time translation, global locales require no attention whatsoever. However, for those adopting other translation mechanisms, such as ngx-translate (which supports runtime switching), manually loading locale data on demand proves highly beneficial.

Additional Resources

Further Learning

To dive deeper into these advanced concepts, consider our Advanced Angular Workshop, focusing on enterprise-level architectures and sustainable practices.

Advanced Angular Workshop