Lazy Load and Encapsulate i18n Files in Angular with Transloco
AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!
When building an application that needs to be translated, one of the most important things to consider is how we organize our translation files.
In this peace, we will go over all of the possibilities that Transloco provides us, what are the challenges, and what ways do we have to overcome them.
Using Scopes
The first option we have is to put each of our language translation files under the assets/i18n
folder:
├─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
In this case, Transloco will load the active language’s file, and use it for the translations.
Of course, As our application grows, our translation files become large and hard to maintain, and it’s probably a good idea to separate our files.
To help you with that, Transloco enables to define a “scope” folder for each of our languages, which gives us the option to not only separate our translation files but also load them lazily.
├─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
└─ 📂 admin/
├─ 📜 en.json
├─ 📜 es.json
More information on using scopes can be found here.
That’s a great solution that might suit most of the applications, but sometimes, we may prefer to put our translation files among our components, and under the feature’s folder.
Using Inline Loaders
Inline Loaders allow us to encapsulate our translation files and put them under the feature’s folder, by providing a specific loader to the scope.
Let’s see how we can use it. Say we have the following CLI project:
├─ 📂 app/
├─ 📜 app-routing.module.ts
├─ 📜 app.component.css
├─ 📜 app.component.html
├─ 📜 app.module.ts
├─ 📜 transloco.loader.ts
└─ 📂 feature/
├─ 📜 feature.module.ts
├─ 📜 feature.component.ts
├─ 📜 feature.component.html
├─ 📜 feature.component.css
├─ 📜 feature.module.ts
└─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
├─ 📂 assets/
└─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
In order to load the feature’s translations, we can define the TRANSLOCO_SCOPE
provider and pass an inline loader that leverages Webpack’s import
function to lazy load the local translation files:
Note that when using an inline loader, the
scope
key is used as thealias
.
Now, we can simply translate our content using the scope
we have defined:
Tip, as from Transloco v2.11, we can use the
--inline-Loader
flag to generate our scopes with inline loaders! 😁
Working with Monorepos
There are cases where we want to use Transloco in our npm
libraries (which is common in a monorepo
environment). In these cases, we probably want to have the translation files inside the library's folder and ship them together with it.
Unfortunately, there are two main difficulties:
- We can’t access the application’s
public
directory, therefore we can’t load them via an HTTP request. - Webpack dynamic
imports
don't work with libraries.
Let’s see which options we have to overcome these issues:
Load Translation Files from a Server
The first option we have is to load the translation files from the server.
We could leverage the inline loader, by providing a factory function that uses the HttpClient
provider to get the translation files from the server:
This approach will work fine, but it doesn’t fit for everybody, let’s see another solution.
Loading Translations Eagrly
Another option is to load each one of the translation files eagerly and use the setTranslation
method to add them.
For example, say we have a new CLI project, with the main application, and another library that encapsulates the translation files inside it:
📦projects
└─ 📂core
└─ 📂src
└─ 📂lib
├─ 📜core.component.ts
├─ 📜core.module.ts
├─ 📜public-api.ts
├─ 📜ng-package.json
├─ 📜package.json
└─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
📦src
└─ 📂 app/
├─ 📜 app-routing.module.ts
├─ 📜 app.component.css
├─ 📜 app.component.html
├─ 📜 app.module.ts
├─ 📜 transloco.loader.ts
└─ 📂 assets/
└─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
We could pass each of the languages we use by calling the setTranslation
method:
Transloco will merge the provided translation object with the provided lang.
We can also scope it under a specific namespace, for example:
The drawback of this approach is that we will load each one of the languages eagerly and include them in our bundle. Maybe it can be fine for your use case, but let’s see another approach.
Scoped Libraries
Our final option is to refer our libraries as scopes. To do so, we will have to create a scope folder, for each translation file (just like a usual scope), and copy the translation files from the lib into the assets folder.
But that would be monkey work
Luckily, Transloco provides us another great tool for this task: Scoped Library Extractor
, which will do this work for us.
So let’s go back to our project from before and see how it works:
📦projects
└─ 📂core
└─ 📂src
└─ 📂lib
├─ 📜core.component.ts
├─ 📜core.module.ts
├─ 📜public-api.ts
├─ 📜ng-package.json
├─ 📜package.json
└─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
📦src
└─ 📂 app/
├─ 📜 app-routing.module.ts
├─ 📜 app.component.css
├─ 📜 app.component.html
├─ 📜 app.module.ts
├─ 📜 transloco.loader.ts
└─ 📂 assets/
└─ 📂 i18n/
├─ 📜 en.json
├─ 📜 es.json
First, we will need to declare TRANSLOCO_SCOPE
in the library’s module:
Now, we can use the scope
inside the library:
Next, we need to install transloco-scoped-libs
:
$ npm install @ngneat/transloco-scoped-libs --save-dev
The first thing we need to do is to add i18n
configuration with the path to the translation folder in the library's package.json
:
Next, we need to add the path to the library into transloco.config.js
as following (we can also pass npm
package ):
Finally, we need to add the following script to the main package.json
"scripts": {
"transloco:extract-scoped-libs": "transloco-scoped-libs"
}
Note it also support “watch mode” using
--watch
flag.
Now, if we run the script, the following things will happen:
- The script will extract the translation files from our library and copy them to the main project’s translation root folder (e.g.,
src/assets/i18n
). - It will add the library’s translation files to the
.gitignore
.
The Join Strategy
This tool supports two different strategies. The default option, the one we used above, and join
.
The join
strategy will combine all the translation files into one file under the root translation path for each language (e.g., en.vendor.json
).
We can set the strategy in our library’s package.json
:
Then, we can use it in our application loader:
Using With Webpack Plugin
We can also use the Webpack plugin by using a tool such as ngx-build-plus
, and add the plugin to webpack.config
file:
Eager Modules
“Eager modules are all the modules that load before the application starts.”
Like every other provider, scopes also follow the Angular DI rules. Therefore if we use an eager module that declares a scope, in a module feature that also declares one, the feature module’s scope will run over the eager module’s.
To prevent that from happening, we have a few options:
- The first one is to simply declare the eager module’s translations in the global translation files.
- Another option is to use the technique from the “Loading Translations Eagrly” section.
- The last option is to declare the scope either in the component’s provider:
Or in the component’s HTML template:
🎁 Bonus Section — Split & Join
Dividing our translation between files could sometimes be a burden, especially when we need to send them as one piece for translators.
Transloco solves that trouble for us by providing us with two schematics commands: Split & Join.
Join
The join
command will merge all our translation files into one piece for each language.
$ ng g @ngneat/transloco:join
By default, the build script will go over the root translation file directory and will refer to every sub-directory as scope.
Note if you have more then one entry folder for your translation files, you will have to add a mapping for each folder entry and the scope name. It could be done using
scopePathMap
property in yourtransloco.config.js
file.
Let’s say we have the following translations folder:
├─ 📂 i18n/
├─ 📜 en.json
├─ 📜 fr.json
├─ 📜 es.json
└─ 📂 todos/
├─ 📜 en.json
├─ 📜 fr.json
├─ 📜 es.json
The script will run over all the directory files (minus the default language) and will merge the scope files to the main translation files.
So if our project’s default language is English, if we run the script the expected output would be:
If we have more then one entry folder for a scope we can specify a map between the scope name and the path to the translations using scopePathMap
property in your transloco.config.js
file:
Note that once you specify the
scopePathMap
the script will automatically use it.
Split
The split
command does the opposite of join
, it will take the translated files and update the project’s translation files.
$ ng g @ngneat/transloco:split
📢 An example project containing all of the above can be found here.
If you like the post, please share, and don’t forget to show your appreciation by starring the library on Github!