Working with Angular i18n inside NRWL NX workspace

Angular v10 has great support for localization using the i18n attributes. I will not describe it, because that is already described in detail on official Angual site. What I found missing was how to integrate it with NRWL NX approach.

Problems encountered

For starters, the workspace.json has different structure than angular.json, so some parts are not 1:1. Workspace.json extends angular.json schema and adds its own properties to it. In its most basic form it is ok to just rename angular.json to workspace.json and update the version property. But the documentation about how to implement localization in NRWL NX workspace is very scarse (written in March 2021).

Nx serve cant serve multiple localizations

Angular i18n approach enables you to define multiple localization files, and then compile multiple instances of the app for each localization. Each localized version will live in different folder and will use different baseHref attribute.

Example

http://myapp.com/fr for French version of the app, 
and http://myapp.com/en for English version of the app 

Define multiple locales in default build target

You can define mutliple localizations in your workspace.json, and then build them all at one step.

"targets": {
  "build": {
    "executor": "@angular-devkit/build-angular:browser",
    "options": {
        // … options not relevant to i18n removed
        // 3) LOG WARNING ABOUT MISSING TRANSLATIONS
        "i18nMissingTranslation": "warning",
        // 4) LIST OF SUPPORTED LOCALIZATIONS
        // THIS WILL COMPILE DIFFERENT INSTANCES INTO "fr" AND "en" FOLDERS
        // BUT WILL PRODUCE PROBLEM IN "nx serve myapp" command
        "localize": ["fr", "en"]
    },

nx build appname will then produce those different folders.

But nx serve can not work multiple locales. For obvious reason, each language version is a different instance of the application. So you need to instruct nx serve which language to use.

Define named build targets for each locale

Named build target can be invoked when you build/serve your application, so that different configuration is used. The most used example is the --prod flag, which uses build target named “production“. We define named build targets for each locale, like this

"configurations": {
   "production": {
     // ... some prod settings
   },
   "en": {
     "localize": ["en"]
   },
   "fr": {
     "localize": ["fr"]
   }
  },

Update serve configuration to use specific build target

The trick is in the browserTarget option says which named build target to use. If you don’t specify it, then the default is used, but that contains multiple locales, and thus breaks the serve command.

"serve": {
 "executor": "@angular-devkit/build-angular:dev-server",
 "options": {
   "browserTarget": "appname:build:en"
 }
}

And thats it. It looks simple in retrospect, but it took me considerable time to figure it out. I hope this will save someones time. Or maybe time of my future self.

All relevant code together

// 1) NAME OF THE APPLICATION
 "appname": { 
    "projectType": "application",
    "root": "apps/appname",
    "sourceRoot": "apps/appname/src",
    "prefix": "appname",
    // 2) PATH TO LOCALIZATION FILES
    "i18n": {
      "locales": {
          "fr": "apps/appname/src/locale/messages.fr.xlf",
          "en": "apps/appname/src/locale/messages.en.xlf"
      }
    },
    "targets": {
      "build": {
          "executor": "@angular-devkit/build-angular:browser",
          "options": {
            // … options not relevant to i18n removed
            // 3) LOG WARNING ABOUT MISSING TRANSLATIONS
            "i18nMissingTranslation": "warning",
            // 4) LIST OF SUPPORTED LOCALIZATIONS
            // THIS WILL COMPILE DIFFERENT INSTANCES INTO "fr" AND "en" FOLDERS
            // BUT WILL PRODUCE PROBLEM IN "nx serve myapp" command
            "localize": ["fr", "en"]
          },
    "configurations": {
     "production": {
       // 5) HERE GOES PRODUCTION SETTINGS - IGNORED FOR BREWITY
     },
     // 6) DEFINITION OF LOCALE FOR NAMED BUILD TARGETS
     // USE "en" FOR BUILD TARGET "en"
     // AND USE "fr" FOR BUILD TARGET "fr"
     "en": {
       "localize": ["en"]
     },
     "fr": {
       "localize": ["fr"]
     }
    },
   "serve": {
        "executor": "@angular-devkit/build-angular:dev-server",
        "options": {
                "browserTarget": "appname:build:en"
        }
  }
On pull request to cripple them all. We have all been there …

Further reading

Tagged function $localize. This is really cool, you can translate messages directly in your code like this:

$localize`this message will be translated like in the template`