Angular multi translation files
Tags: angular, ngxtranslate, multitranslations
Published: 2023-06-27 08:54:27

---
title: Angular multi translation files
published: true
date: 2023-06-23 09:44:10 UTC
tags: angular,ngxtranslate,multitranslations
canonical_url: https://medium.com/@redin.gaetan/angular-multi-translation-files-7116239d915b
---

Done with ngx-translate

I’ve been using [_ngx-translate_](https://github.com/ngx-translate/core) for as long as I can remember. I find it easier than the native Angular i18n solution.

Translations is often complicated to maintain because the translation file grows as the application grows.

My use cases:

- I don’t want to have all my translations in one single file
- I want to know where to write my translations
- I want to keep a common file because there are still common wordings
- Bonus, I want to make that more readable, reviewable

### Project Architecture

This is how my assets are architectured to let the solution works. this can be done in an another way but I don’t variabilize the path to the common files (which can be done if you want to handle it).

```
- assets
  - ...
  - i18n
    - common
      - fr.json
      - en.json
      - ...
    - feature-1
      - fr.json
      - en.json
      - ...
    - feature-2
      - fr.json
      - en.json
      - ...
    - ...
```

### Custom loader

[here](https://github.com/ngx-translate/core#write--use-your-own-loader) is the doc about how to create a custom loader.

Let’s read my suggestion for a custom loader:

```
// model for a resource to load
export type Resource = { prefix: string; suffix: string };


export class MultiTranslateHttpLoader implements TranslateLoader {
  resources: Resources[];
  withCommon: boolean;

  constructor(
    private readonly http: HttpClient,
    { resources, withCommon = true }: { resources: Resource[], withCommon?: boolean }
  ) {
    this.resources = resources;
    this.withCommon = withCommon;
  }

  getTranslation(lang: string): Observable<Record<string, unknown>> {
    let resources: Resource[] = [...this.resources];

    if (this.withCommon) {
      // order matters! like this, all translations from common can be overrode with features' translations
      resources = [
        { prefix: './assets/i18n/common/', suffix: '.json' }, 
        ...resources
      ];
    }

    return forkJoin(resources.map((config: Resource) => {
      return this.http.get<Record<string, unknown>>(`${config.prefix}${lang}${config.suffix}`);
    })).pipe(
      map((response: Record<string, unknown>[]) => 
        mergeObjectsRecursively(response)),
    );
  }
}

export const mergeObjectsRecursively = 
    (objects: Record<string, unknown>[]): Record<string, unknown> {
  const mergedObject: Record<string, unknown> = {};

  for (const obj of objects) {
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        if (typeof obj[key] === 'object' && obj[key] !== null) {
          mergedObject[key] = mergeObjectsRecursively([mergedObject[key], obj[key]]);
        } else {
          mergedObject[key] = obj[key];
        }
      }
    }
  }

  return mergedObject;
}
```

### How to use it

With common translations:

```
TranslateModule.(forRoot|forChild)({
  loader: {
    provide: TranslateLoader,
    useFactory: (http: HttpClient): MultiTranslateHttpLoader => {
      return new MultiTranslateLoader(http, {
        resources: [
          { prefix: './assets/i18n/feature-1/', suffix: '.json' },
          { prefix: './assets/i18n/feature-2/', suffix: '.json' },
          ...
        ],
      });
    },
    deps: [HttpClient],
  },
})
```

Without common translations:

```
TranslateModule.(forRoot|forChild)({
  loader: {
    provide: TranslateLoader,
    useFactory: (http: HttpClient): MultiTranslateHttpLoader => {
      return new MultiTranslateLoader(http, {
        withCommon: false,
        resources: [
          { prefix: './assets/i18n/feature-1/', suffix: '.json' },
          { prefix: './assets/i18n/feature-2/', suffix: '.json' },
          ...
        ],
      });
    },
    deps: [HttpClient],
  },
})
```

### Result

```
// assets/i18n/feature-1/en.json

{
  "HELLO": "HELLO",
  "CIVILITIES": {
    "MR": "Mister",
    "MS": "Miss"
  }
}

// assets/i18n/feature-2/en.json
{
  "TITLE": "LONG TITLE",
}

// generated translations
{
  "HELLO": "HELLO",
  "CIVILITIES": {
    "MR": "Mister",
    "MS": "Miss"
  },
  "TITLE": "LONG TITLE",
}
```

### Conclusion

Now you can split your translation files, you will see how it will be easier to read, maintain and review. Don’t forget to use the lazy loading feature (from Angular) and then you will load only translation files required for a specific route.

If you need more details or want to give me your opinion let me a comment, don’t hezitate.

Thanks for reading.