Skip to main content

Scoped Library Extractor

There are cases where we need to use translations 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, we won't be able to load our translation files from the library for two reasons:

  1. We can't access the application's public directory
  2. Webpack dynamic imports don't work with libraries

The only option we've got is to load the library translation files from our application public folder.

So if we want our translation files to be under the library's folder, we'll need to copy and paste the translation files repeatedly.

Well, this is why we've created the Scoped Library Extractor tool, which will do the work for you.

For example, here we have created a new CLI project, with the main application (app), and another library with translations (core):

📦 projects
┣ 📂 core
┃ ┣ 📂 src
┃ ┃ ┣ 📂 lib
┃ ┃ ┃ ┣ 📜 core.component.ts
┃ ┃ ┃ ┣ 📜 core.module.ts
┃ ┃ ┃ ┣ 📂 i18n
┃ ┃ ┃ ┃ ┣ 📜 en.json
┃ ┃ ┃ ┃ ┗ 📜 es.json
┃ ┃ ┣ 📜 public-api.ts
┣ 📜 ng-package.json
┣ 📜 package.json
📦 src
┣ 📂 app
┃ ┣ 📜 app.component.html
┃ ┣ 📜 app.component.ts
┃ ┣ 📜 app.module.ts
┃ ┗ 📜 transloco.loader.ts
┣ 📂 assets
┃ ┣ 📂 i18n
┃ ┃ ┣ 📜 en.json
┃ ┃ ┗ 📜 es.json

Now we need to declare the scope in the CoreModule:

core.module.ts
import { provideTranslocoScope } from './transloco.providers';
@NgModule({
declarations: [CoreComponent],
providers: [provideTranslocoScope('core')],
imports: [TranslocoModule],
})
export class CoreModule {}

Now, we can use the scope in our component:

lib-core.component.html
@Component({
selector: 'lib-core',
template: `
<ng-container *transloco="let t">
{{ t('core.title') }}
</ng-container>
`,
})
export class CoreComponent {}

Now, let's install transloco-scoped-libs package:

$ npm install @jsverse/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:

projects/core/package.json
{
"name": "@app/core",
"i18n": [
{
"scope": "core",
"path": "src/lib/i18n"
}
]
}

Next, we need to add the library's path into your global config as following (we can also pass npm package):

transloco.config.ts
const config: TranslocoGlobalConfig = {
scopedLibs: ['./projects/core/', '@lib/name']
}

If multiple destination is needed you could also pass scopedLibs as an object:

transloco.config.ts
const config: TranslocoGlobalConfig = {
scopedLibs: [
{
src: './projects/core',
dist: ['./projects/spa/src/assets/i18n', './src/assets/i18n/']
}
]
}

Note that the path should refer to the location of the library's package.json file. Finally, we need to add the following script to the main package.json:

package.json
"scripts": {
"transloco:extract-scoped-libs": "transloco-scoped-libs"
}

It also support "watch mode" by passing --watch flag:

package.json
"scripts": {
"transloco:extract-scoped-libs": "transloco-scoped-libs --watch"
}

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 ( if you don't want to modify the .gitignore use the --skip-gitignore flag).

Join Strategies​

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:

projects/core/package.json
{
"name": "@app/core",
"i18n": [
{
"scope": "core",
"path": "src/lib/i18n",
"strategy": "join"
}
]
}

Then, we can use it in our application loader:

transloco-loader.ts
@Injectable({ providedIn: 'root' })
export class HttpLoader implements TranslocoLoader {
constructor(private http: HttpClient) {}

getTranslation(lang: string, { scope }) {
const base = this.http.get(`/assets/i18n/${lang}.json`);

if (scope) {
return base;
}

return forkJoin([
base,
this.http.get(`/assets/i18n/${lang}.vendor.json`),
]).pipe(
map(([translation, vendor]) => {
return { ...translation, ...vendor };
}),
);
}
}

export const httpLoader = { provide: TRANSLOCO_LOADER, useClass: HttpLoader };

Use the Webpack Plugin​

Add custom Webpack support by using a tool such as ngx-build-plus, and add the plugin to webpack.config file:

webpack.config.js
const TranslocoScopedLibsWebpackPlugin = require('@jsverse/transloco-scoped-libs/webpack');

module.exports = {
plugins: [new TranslocoScopedLibsWebpackPlugin()],
};