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.
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.
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.
{
"$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.
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:
{
"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.
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.
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:
<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.
nx build authentication-web
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).
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.
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.
Top comments (5)
Nice article!
BTW prefer the structural directive over the pipe with using Transloco 😉
After 2 days I couldn't configure Transloco as global config to share config and use in NX monorepo with 3 apps.
Can you please add nx generator for monorepo or a sample stackblitz?
Hello do you have a stzckblitz or github about this tuto ?
Thx
Hello, Good tutorial but do you know how to manage the language changes with the "@workspace/i18n" ? E.g. From the library or Angular App change the language setting "en" or "es"...? Thanks.
The library
@workspace/i18n
should just contain the translation asset files. The logic for handling language changes should be handled from the Transloco library. The library has an option for handling live translation changes (when you change the active language it will re-render pipes/bindings), but it will come with a slight performance hit for all the watchers. Alternatively you can change the language and reload your application to render all text in the correct language.