Lazy Load and Encapsulate i18n Files in Angular with Transloco

Itay Oded
Angular In Depth
Published in
7 min readDec 11, 2019

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 the alias.

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:‌

  1. We can’t access the application’s public directory, therefore we can’t load them via an HTTP request.
  2. 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:

  1. 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).
  2. 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:

  1. The first one is to simply declare the eager module’s translations in the global translation files.
  2. Another option is to use the technique from the “Loading Translations Eagrly” section.
  3. 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 your transloco.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!

⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

--

--

Itay Oded
Angular In Depth

Transloco core member, CTO & Developer@ e-square.io, Angular enthusiast, windsurfer and boulder climber