Lazy translation assets with Angular, Transloco and Nx Buildable Libs
Tags: angular, transloco, nx
Published: 2021-09-13 18:21:59

When dealing with applications that support multiple languages, it becomes increasingly more costly to have single translation asset files to contain all the key-value pairs for your application.

Instead, making use of webpack's inline loaders and Nx's buildable libs; you can fetch smaller translation assets only for a feature's scope, without requesting additional data.

**Requirements**

- Nx workspace (mono-repo) with buildable libs. 
- Angular
- Transloco as the translation library for handling your translations. 
- JSON files to contain your translation key-value pairs (en.json) 

## Tutorial

### Shared i18n Library

#### Available/Supported Locales

In a shared library (i.e.: @workspace/i18n) create a constant of your application's supported locales.

```ts
export const AVAILABLE_LANGS = [
    'en',
    'ja',
    'fr-ca',
    'zh-cn'
];
```

#### Inline Loader Factory


In the same shared library, create a file for simplifying the interaction of using webpack's inline loader with the expected structure to use with Transloco's library.

```ts
import { AVAILABLE_LANGS } from './available-langs.const';

export function InlineLoaderFactory(loader: (lang: string) => Promise<any>) {
    return AVAILABLE_LANGS.reduce((acc: Translation, lang: string) => {
        acc[lang] = () => loader(lang);
        return acc;
    }, {});
}
```

> Make sure to export your inline loader factory file to the root barrel of your shared Nx library.


### Feature Lib

For this tutorial, we are separating out the translation assets for an authentication experience. 

We will have an Nx library for `authentication/web` in `libs/authentication/web` of our Nx mono-repo.

#### Buildable libs

When creating a new library make sure to use the `--buildable` modifier to run the schematic for making your lib buildable. 

If you are adding this to an existing lib that isn't already buildable you will need to:

- Create an `ng-package.json` file in the root of your lib:
```json
{
    "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
    "dest": "../../../dist/libs/authentication/web",
    "lib": {
        "entryFile": "src/index.ts"
    }
}
```
- Create a `tsconfig.lib.prod.json`:
```json
{
    "extends": "./tsconfig.lib.json",
    "compilerOptions": {
        "declarationMap": false
    },
    "angularCompilerOptions": {
        "compilationMode": "partial"
    }
}
``` 
- Update your workspace.json/project.json (depending if you are using standalone configs) to include a build target:
```json
"build": {
    "executor": "@nrwl/angular:ng-packagr-lite",
    "outputs": ["dist/libs/authentication/web"],
    "options": {
        "project": "libs/authentication/web/ng-package.json"
    },
    "configurations": {
        "production": {
            "tsConfig": "libs/authentication/web/tsconfig.lib.prod.json"
        },
        "development": {
            "tsConfig": "libs/authentication/web/tsconfig.lib.json"
        }
    },
    "defaultConfiguration": "production"
},
```
- Create a `package.json` of your library's workspace qualified name:
```json
{
    "name": "@workspace/authentication/web",
    "version": "0.0.1",
    "peerDependencies": {
        "@angular/common": "^12.2.0",
        "@angular/core": "^12.2.0"
    },
    "dependencies": {
        "tslib": "^2.3.0"
    }
}

```

#### Assets

In your lib, create a new directory called `assets` that is located at the root (`/libs/authentication/web/assets/`).

In this folder, add all of the JSON translation files for your feature and supported locales.

```
/assets/
       en.json
       ja.json
       fr-ca.json
       zh-cn.json
```

Next, update your ng-package.json to include your assets in the build process.

```json
{
    "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
    "dest": "../../../dist/libs/client/authentication/web",
    "lib": {
        "entryFile": "src/index.ts"
    },
    "assets": ["./assets"]
}
```

> This step is **extremely** important. Without it, your JSON files will be purged from the generated `/dist` when the library is built. This will cause your webpack inline loaders to fail to load the translation assets only in a built environment.

#### Configuring Transloco for Feature Module

In your feature's main Angular module, configure Transloco to make use of webpack's inline loader and our library's assets.

```ts
import { NgModule } from '@angular/core';
import { TRANSLOCO_SCOPE } from '@ngneat/transloco';

@NgModule({
    imports: [...], // Lazy routing module and feature modules
    providers: [
        {
            provide: TRANSLOCO_SCOPE,
            useValue: {
                scope: 'auth',
                loader: InlineLoaderFactory((lang: string) => import(`../../assets/i18n/${lang}.json`))
       }
    ]
})
export class AuthenticationModule {}
```

The `scope` value, is the key prefix that will be added to all translation key-value pairs that are loaded from your assets directory.

Assuming you have a translation file such as:
```json
{
    "login_form.title": "Enter your email"
}
```

The key will be re-written within the context of your feature module to be accessed as `auth.login_form.title`. You can [learn more about scopes within Transloco here](https://ngneat.github.io/transloco/docs/scope-configuration/).


#### Using your translations

In the component's module making use of Transloco, import the `TranslocoModule` to make use of Transloco's built in pipes and directives.

```ts
import { TranslocoModule } from '@ngneat/transloco';

@NgModule({
    imports: [TranslocoModule]
})
```

Now, remembering the `scope` you defined earlier, you can use your translation in the component's template:

```html
<p>{{ 'auth.login_form.title' | transloco }}</p> 
```

### Verifying and Validating

#### Build output

You should confirm that ng-packgr is configured correctly to generate and output your JSON assets alongside your generated JS bundles.

Using Nx's build command, build your library and validate the JSON assets are in the `assets` directory of the `dist` output.

```bash
nx build authentication-web
```

![Dist directory for built library showing JSON assets in the assets directory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/18cnrner1jpgrmcwsuym.png)

#### Network traffic

After making this change, you should be able to confirm that webpack's inline-loaders are loading the translation assets when your feature module is requested by your application (when making use of lazy loading).

![Network tab in browser showing translation asset loading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxzt4ujj6yf5no73pi1u.png)

#### UI validation

Lastly you should confirm in your application's interface that your translation keys are translated from `auth.login_form.title` to your expected value for the active locale.

![Translated app interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bszikgrmp04argegtyfo.png)

### Final thoughts

Lazy loading translation assets can be super impactful to application performance. With this change in our authentication experience, we are able to request 3.0kb of translation assets for our locale, instead of 166kb for our entire application's translation asset. As our application continues to scale, our impact of translations stay consistent to the growth; instead of all users being impacted for translation assets for features they are not using.

Lastly, by using Nx buildable libs to manage our separated feature and translation assets; our library is never rebuilt for other translation changes in our application, resulting in lower CI build times and cost savings.