core icon indicating copy to clipboard operation
core copied to clipboard

How to load separate translation files per module and per component in angular 5 ?

Open Vishnu0522 opened this issue 7 years ago • 13 comments

Expected/desired behavior I am new in angular , Our application is large and we don't want to load whole translation in single call . We want to load translation in per module as well as per component wise.

My environment is : Angular CLI: 6.0.8 Node: 8.10.0 typescript 2.7.2

Vishnu0522 avatar Jul 07 '18 06:07 Vishnu0522

if you search around there are some solutions but those do not work, I am also having this problem and so far i can not find any way around it.

ratidzidziguri avatar Jul 07 '18 22:07 ratidzidziguri

Hi @Vishnu0522 as you see in the official doc of ngx-translate, there is this paragraph...if you are using lazy loading and you have a different modules, you can configure different services by using isolate: true. With this approach, the service is a completely isolated instance (for translations, current lang, events, ...).

adelloste avatar Jul 19 '18 18:07 adelloste

@adelloste Using this approach, I run into the issue described here https://github.com/ngx-translate/core/issues/876

Do you know a way around this issue?

BrianCerasuolo avatar Aug 08 '18 15:08 BrianCerasuolo

How to load translations per module is described in the documentation. If you want to load on a component by component basis you can try something like this:

import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http';

import { TranslateService } from '@ngx-translate/core';

export const EN_TRANSLATIONS = { 'found': 'found', 'not found': 'not found' };

@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit {

translationsUrl = 'assets/i18n';

constructor(private translate: TranslateService, private http: HttpClient) { this.translate.setDefaultLang('en'); this.translate.use('de'); this.translate.setTranslation('en', EN_TRANSLATIONS); }

ngOnInit() { this.loadTranslations('de'); }

loadTranslations(locale: string) { return this.http.get(${this.translationsUrl}/${this.constructor.name}-${locale}.json).subscribe((data: any) => { this.translate.setTranslation(locale, data); }); } }

Tested w/ Angular 6

Here is well explained. https://medium.com/@TuiZ/how-to-split-your-i18n-file-per-lazy-loaded-module-with-ngx-translate-3caef57a738f

However, I didn't get it working in my project. Seems that even with the flag isolate: true i'm still getting the same TranslateService with root translations.

david-dlc-cerezo avatar Sep 03 '19 15:09 david-dlc-cerezo

Experiencing the same as @david-dlc-cerezo, the xhr call is only made for the file specified in the forRoot factory, the lazy-loaded child modules factories are called, but XHR request does not fire.

As of, translation service only has the root translations.

shane-arthur avatar Sep 05 '19 19:09 shane-arthur

@shane-arthur I finally kind of get it working but I'm not sure how/why 🤣

One thing I did it was to add TranslateService to the providers array on the module where I use Translate.forChild

@NgModule({
  imports: [
    ...,
    TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: MyImporterFactory
      },
      isolate: true
    }),
    ...
  ],
  providers: [
    ...,
    TranslateService
  ]
})
export class MyModule {}

But I can ensure this was what finally isolated the TranslateService in my Module.

Angular documentation about providers says:

When the router creates a component within the lazy-loaded context, Angular prefers service instances created from these providers to the service instances of the application root injector.

The original @Vishnu0522 question asked also about loading a different set of translations for each module... Well if for a module you should provide a TranslateLoader with your customized public getTranslation(lang: string): Observable<any> method, my guess is that in a component you should do the same.

david-dlc-cerezo avatar Sep 06 '19 07:09 david-dlc-cerezo

I just tested a PoC about that... and worked! 🎉

To have a different TranslateService with its TranslateLoader on each Component:

class MyTranslateLoader implements TranslateLoader {
  constructor() {}

  public getTranslation(lang: string): Observable<any> {
    const translations = ...// Obtain your translations as you wish
    return of(translations);
  }
}

// AoT requires an exported function for factories
export function MyTranslateLoaderFactory() {
  return new MyTranslateLoader();
}

@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss'],
  providers: [
    TranslateService,
    {
      provide: TranslateLoader,
      useFactory: MyTranslateLoaderFactory
    }
  ]
})
export class SchoolEditionComponent implements OnInit {
constructor(
    private readonly translate: TranslateService,
  ) {}

 ngOnInit() {
   // This will show the loaded translations
   this.translate.getTranslation('en').subscribe(translations => console.log(translations));
 }
}

david-dlc-cerezo avatar Sep 06 '19 08:09 david-dlc-cerezo

@shane-arthur I finally kind of get it working but I'm not sure how/why

One thing I did it was to add TranslateService to the providers array on the module where I use Translate.forChild

@NgModule({
  imports: [
    ...,
    TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: MyImporterFactory
      },
      isolate: true
    }),
    ...
  ],
  providers: [
    ...,
    TranslateService
  ]
})
export class MyModule {}

But I can ensure this was what finally isolated the TranslateService in my Module.

Angular documentation about providers says:

When the router creates a component within the lazy-loaded context, Angular prefers service instances created from these providers to the service instances of the application root injector.

The original @Vishnu0522 question asked also about loading a different set of translations for each module... Well if for a module you should provide a TranslateLoader with your customized public getTranslation(lang: string): Observable<any> method, my guess is that in a component you should do the same.

@david-dlc-cerezo the solution from Medium was working absolutely fine earlier but now it doesn't work. I have Translate service provided in lazy loaded module as well. but unable to split the translate file per module.Isolate: true does not work at all. This is in angular 6 as well as 7. It was working perfectly fine not sure what affected it and where.

prambhan avatar Dec 24 '19 12:12 prambhan

I was struggling with the same issue and got it working when using isolate: true and setting a default language(and current if needed) again.

For app module:

export class AppTranslateLoader implements TranslateLoader {
  getTranslation(lang: string): Observable<any> {
    return from(import(`../assets/i18n/${lang}.json`));
  }
}
TranslateModule.forRoot({
      defaultLanguage: 'ET',
      loader: {
        provide: TranslateLoader,
        useClass: AppTranslateLoader,
      }
}),

For lazy module:

export class LazyTranslateLoader implements TranslateLoader {
  getTranslation(lang: string): Observable<any> {
    return from(import(`../../assets/i18n/lazyModule/${lang}.json`));
  }
}
TranslateModule.forChild({
      defaultLanguage: 'ET',
      isolate: true,
      loader: {provide: TranslateLoader, useClass: LazyTranslateLoader}
    }),

For setting a current langauge I'm using this in a module container component:

  constructor(translate: TranslateService, store: Store<fromCore.State>) {
    store.pipe(select(UserSelectors.selectUserLanguage)).subscribe(
      (lang) => translate.use(lang)
    );
  }

I'm not using http loader due to browser cache issues.

akiik avatar Sep 23 '20 13:09 akiik

@akiik I didn't know what you meant by "a module container component" so I initialize the TranslateService inside the constructor of my lazy-loaded module and it worked!!!! I can't thank you enough!!!

axell9641 avatar Nov 11 '21 15:11 axell9641

In case you use: this.translate.setTranslation(locale, data); There is a third parameter "shouldMerge: boolean". For me that was the central point which solved my Problems: I always call this.translate.setTranslation(locale, data, true);. This prevents that a component replaces all keys from the SPA. and visa versa.

ps.: isolation=true didn't worked for me. Maybe because of lazy-loading sub-modules which contains the components.

brabenetz avatar Nov 12 '21 18:11 brabenetz

In case you use: this.translate.setTranslation(locale, data); There is a third parameter "shouldMerge: boolean". For me that was the central point which solved my Problems: I always call this.translate.setTranslation(locale, data, true);. This prevents that a component replaces all keys from the SPA. and visa versa.

ps.: isolation=true didn't worked for me. Maybe because of lazy-loading sub-modules which contains the components.

It didn't work for me either until I added this config inside the feature module's constructor image

axell9641 avatar Nov 12 '21 22:11 axell9641