ngx-translate / core

The internationalization (i18n) library for Angular
MIT License
4.51k stars 575 forks source link

Using TranslateModule.forChild() in an Angular lib #883

Open matheuscaldasrj opened 6 years ago

matheuscaldasrj commented 6 years ago

Current behavior

I am trying to use ngx-translate inside an Angular 5 lib.

I have read from docs that "The forRoot static method is a convention that provides and configures services at the same time. Make sure you only call this method in the root module of your application, most of the time called AppModule"

So I thought that I should use Translate.forRoot() in my application and Translate.forChild() in my lib module

The problem is that when I use forRoot in my App and forChild in my lib I always get the following error:

ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[TranslateService -> TranslateStore]: 
  StaticInjectorError(Platform: core)[TranslateService -> TranslateStore]: 
    NullInjectorError: No provider for TranslateStore!
Error: StaticInjectorError(AppModule)[TranslateService -> TranslateStore]: 
  StaticInjectorError(Platform: core)[TranslateService -> TranslateStore]: 
    NullInjectorError: No provider for TranslateStore!

I have tested and it works when I use forRoot in application and lib, but I have to use this.translate.use("myLanguage") because seems I have two instances of TranslateService and this doesn't seems to be the proper way.

Expected behavior

My lib should work using Translate.forChild({}) and my application using Translate.forRoot({})

How do you think that we should fix this?

Minimal reproduction of the problem with instructions

Application module

export function createTranslateLoader(http: HttpClient) { 
  return new TranslateHttpLoader(http, './assets/i18n/', '.json'); 
} 

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient],
      }
    })  
  ],
  providers: [MyService],
  bootstrap: [AppComponent]
})

Lib module

export function createTranslateLoader(http: HttpClient) { 
  return new TranslateHttpLoader(http, './assets/my-other-path/i18n/', '.json'); 
} 

@NgModule({
  imports: [
    NoopAnimationsModule,
    CommonModule
    TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient],

      }
    })
  ],
  declarations: [
    MessageButtonComponent,
  ],
  exports: [
    MessageButtonComponent
  ],
  providers: [
    TranslateService
  ],
  entryComponents: [

  ]
})

Environment


ngx-translate version: 9.1.1
Angular version:  core 5.2.0 


Browser:
- [x] Chrome (desktop) version 67
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX

For Tooling issues:
- Node version: v8.11.2 
- Platform:  Windows 10

Others:

UrsBeeli commented 6 years ago

We have the exact same problem.

Yogu commented 6 years ago

I think you should just import TranslateModule directly to get access to the directives and pipes, and leave it up to the app to provide the services. Having multiple loaders is not really supported anyway: https://github.com/ngx-translate/core/issues/763

See the angular documentation on what to import:

What should I import?

Import NgModules whose public (exported) declarable classes you need to reference in this module's component templates.

Note: nothing mentioned of services

How do I restrict service scope to a module?

[...] As a general rule, import modules with providers exactly once, preferably in the application's root module. That's also usually the best place to configure, wrap, and override them.

They also distinguish between Widget modules and Service modules.

If I understand the usage section of the README of ngx-translate correctly, they also recommend importing TranslateModule in shared modules:

Note: Never call a forRoot static method in the SharedModule. You might end up with different instances of the service in your injector tree. But you can use forChild if necessary.

Note that it says use forChild if necessary, not as a general rule for shared modules.

Maybe we should document this more explicitly in the README.

kshitij-tf-zz commented 6 years ago

@matheuscaldasrj - Could you find any approach to achieve this behaviour? I am facing similar issue where language change event on application translate service is not propagating to the library.

tlaskowski commented 5 years ago

Hi I have similar error. I'm using:

"@ngx-translate/core": "11.0.0", "@ngx-translate/http-loader": "4.0.0",

App.module.ts:

TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: TranslationProviders,
        deps: [HttpClient],
      },
    }),

My LazyLoaded module Imports SharedModule which imports and exports TranslateModule. I tried to import TranslateModule with forChild but didn't help.

Any idea?

segux commented 5 years ago

Hi, i wrote this ugly solution invoking setTranslation with TranslateService and appending, all translations are come to be loaded and run everything fine:

This is my finally solution

Library code

availableLanguages.ts

import { en } from './en.ts';
import { es } from './es.ts';
export const languages = { en, es };

translations.service.ts

@Injectable()
export class LibTranslationsService {
  private availableLanguages = languages;
  constructor(private translate: TranslateService) {
  }

  init(): any {
    Object.keys(this.availableLanguages).forEach((language) => {

     // Timeout because i need to be async calls after init app
      setTimeout(() => this.translate.setTranslation(language, this.availableLanguages[language], true));
    });

  }

  getTranslations() {
    return this.availableLanguages;
  }
}

App

  app.component.ts
  ngOnInit() {
    this.translateUiService.init();
  }
juanjinario commented 5 years ago

I dont have problems with Translate module, I have in my app.module

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

import { SharedModule } from './shared/shared.module';
import { AppRoutingModule } from './app.routing';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    HttpClientModule,
    SharedModule,
    AppRoutingModule,
    TranslateModule.forRoot({
        loader: {
            provide: TranslateLoader,
            useFactory: HttpLoaderFactory,
            deps: [HttpClient]
        }
    })
  ],
  exports: [],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

// required for AOT compilation
export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http);
}

And In my shared.module I have

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MaterialModule } from '../pages/component/material.module';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ToastrModule } from 'ngx-toastr';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

@NgModule({
  declarations: [],
  imports: [
    RouterModule,
    MaterialModule,
    FlexLayoutModule,
    HttpClientModule,
    ToastrModule.forRoot(),
    TranslateModule.forChild({
      loader: {
          provide: TranslateLoader,
          useFactory: HttpLoaderFactory,
          deps: [HttpClient]
      }
    })
  ],
  exports: [
    MaterialModule,
    FlexLayoutModule,
    ToastrModule,
    TranslateModule
  ]
})
export class SharedModule { }

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http);
}

I think that the problem can be that you didnt put the Translate Module in the exports zone of the Shared Module.

animbalk commented 5 years ago

Facing the same issue. I have even tried exporting TranslateModule as well.


import { CoreModule } from './core/core.module'; import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { UpgradeModule } from '@angular/upgrade/static'; import { EventPolicyModule } from './policy/event-policy/event-policy.module'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { HttpClient } from '@angular/common/http'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core';

export function createTranslateLoader1( http: HttpClient){ return new TranslateHttpLoader(http, 'assets/i18n/core/', '.json'); }

@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, UpgradeModule,

CoreModule,
EventPolicyModule,

TranslateModule.forRoot({
  loader : {
    provide : TranslateLoader, 
    useFactory : (createTranslateLoader1),
    deps : [HttpClient]
  }
})

], providers: [], bootstrap: [AppComponent] })

export class AppModule { }

import { AppRoutingModule } from './../app-routing.module'; import { NgModule } from '@angular/core'; import { CommonModule, LocationStrategy, HashLocationStrategy } from '@angular/common'; import { UrlHandlingStrategy, UrlTree } from '@angular/router'; import { HttpClientModule, HttpClient } from '@angular/common/http'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';

export class NgAppUrlHandlingStrategy implements UrlHandlingStrategy { // only process the angular url shouldProcessUrl(url: UrlTree) { return url.toString().startsWith('/configuration/impolicies'); }

extract(url: UrlTree) { return url; }

merge(url: UrlTree, whole: UrlTree) { return url; } }

export function createTranslateLoader2( http: HttpClient){ return new TranslateHttpLoader(http, 'assets/i18n/core/', '.json'); }

@NgModule({ declarations: [], imports: [ CommonModule, HttpClientModule, TranslateModule.forChild({ loader : { provide : TranslateLoader, useFactory : (createTranslateLoader2), deps : [HttpClient] }, isolate : true }) ], exports :[TranslateModule ], providers: [

] })

export class CoreModule { }

kurtiev commented 5 years ago

@juanjinario Thanks. Guys, I also had that issue, but after adding:

TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: createTranslateLoader, deps: [HttpClient] } })

into app.module.ts it started work fine.

and in Shared module: TranslateModule.forChild({ useDefaultLang: true, isolate: false, loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient] } })

greetclock commented 5 years ago

I had a similar problem as described in the first message in this thread. I tried to create an Angular 7 library with ng g lib. Turned out that I had @ngx-translate/core in node_modules and in projects/<my-lib>/node_modules. Removing the inner folder completely (projects/<my-lib>/node_modules) solved my problem.

After that, I use TranslateModule.forRoot() with no parameters in my AppModule of the tester app and TranslateModule in the imports of my library module.

I hope my message will help somebody.

PedroMGMendes commented 5 years ago

Hi, faced this issue just yesterday with version 11.0.1 and Angular & CLI 8, what worked for me was:

Like state in the documentation in my shared module i had:

TranslateModule.forChild({
      loader: {
        provide: TranslateLoader,
        useFactory: (createtranslateloader),
        deps: [HttpClient]
      },
      isolate: true
    }),

and in my AppModule, wich also imports the shared module , i add to provide the TranslateStore

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    CommonModule,
    SharedModule,
    OAuthModule.forRoot(),
    AppRoutingModule,
    CoreModule.forRoot(),
    UiBlocksModule,
    ThemeModule.forRoot(AppThemes)
  ],
  providers: [
    TranslateStore,
    { provide: OAuthStorage, useValue: localStorage },
    { provide: OAuthLogger, useValue: LoggingService }      
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Not sure if this is supposed to be done this way but this did resolve the original error message "NullInjectorError: No provider for TranslateStore!".

But now everything is translated except the lazy loaded modules. (every component loaded inside the had missing translations). I then notice there were at least two instances of the TranslateService, the one create for components outside of the router outlet, and another one for the lazy loaded modules.

What fixed this was setting the isolate: false when registering the translate module in my SharedModule.

This issue ultimately seems to depend a lot with the way the angular project and module registration is set up. In my case the SharedModule and the AppModule do share the same i18n files, but i do realize that if i want to get different i18n files for the SharedModule and the AppModule it does make sense to have different instances of the TranslateService and would have to register the the TranslateModule.forRoot in the AppModule and keep the flag isolate: true in my Shared Module

Hope this can help..

JMuhire commented 5 years ago

I had the same issue when lazy loading many translated angular 8 modules with ngx-translate/core v.11.0.1 .

In the translation module I have :

@NgModule({
    declarations: [ MyComponent ],
    imports: [
        CommonModule,
        TranslateModule.forChild({
            loader: {
                provide: TranslateLoader,
                useFactory: MyTranslationLoaderFactory,
                deps: [ HttpClient ]
            },
            isolate: false
        }),
        RouterModule.forChild(MY_ROUTES),
        SharedModule
    ]
})
export class MyModule {}

In the container I have to add this line in the ngOnInit to make it work:

export class MyContainer implements OnInit {

    constructor(public translationService: TranslateService) {}

    ngOnInit() {
        this.translate.use(this.translate.store.currentLang);
        this.translate.store.onLangChange.subscribe((lang) => this.translate.use(lang));
    }
}
ahmadmu commented 5 years ago

This worked for me, In tsconfig.ts of the main application add this to paths and restart your application:

     "@ngx-translate/core": [
       "node_modules/@ngx-translate/core"
     ],
     "@ngx-translate/http-loader": [
       "node_modules/@ngx-translate/http-loader"
     ],
jielimanyili commented 4 years ago

Besides adding the standard TranslateModule.forRoot(...) in my AppModule, I simply import and export the TranslateModule (no .forChild()) in my SharedModule worked for me also, surprisingly. Before that, I got translate pipe error.

olofens commented 4 years ago

Besides adding the standard TranslateModule.forRoot(...) in my AppModule, I simply import and export the TranslateModule (no .forChild()) in my SharedModule worked for me also, surprisingly. Before that, I got translate pipe error.

I can confirm that just dropping all .forChild(), and having one .forRoot({...}) in app.module throughout my application + library did the trick. Seems strange though that you can't call .forChild() on what I see as child modules. Oh well.

Sherpard commented 3 years ago

I've been tampering a bit with the library, and found out a solution,

.forChild forgets to provide a TranslateStore, adding

 providers: [{ provide: TranslateStore }]

to the Shared Module seems to solve the injection issues with no .forRoot() call whatsoever

BdDragos commented 3 years ago

I had a similar problem as described in the first message in this thread. I tried to create an Angular 7 library with ng g lib. Turned out that I had @ngx-translate/core in node_modules and in projects/<my-lib>/node_modules. Removing the inner folder completely (projects/<my-lib>/node_modules) solved my problem.

After that, I use TranslateModule.forRoot() with no parameters in my AppModule of the tester app and TranslateModule in the imports of my library module.

I hope my message will help somebody.

Thank you so so much. I was having this issue and I've spent a considerable amount of time figuring out why suddenly the library doesn't translate anymore. This solved everything.

eulersson commented 3 years ago

For lazy-loaded modules with different translation loaders (loading .json from different files) it seems to be either (in the case of the lazy-loaded):

It's like I can't blend the two.

I got pretty close though maybe you could have a look and play within StackBlitz: https://stackblitz.com/edit/translations-and-lazy-loading?file=README.md

kacperczerwinski commented 2 years ago

@ahmadmu You saved my day! Thanks

morbargig commented 1 year ago

with lazy module somehow it's essayer it works fine and for non lazy module such as my FrontBaseClientModule that configure with forChild for exam this one did the job appTranslateService is custom extends for TranslateService + typescript and shit but with basic logic as TranslateService

FrontBaseClientModule it's kinda my shared module so onLangChange just call the service to set the Translation and merge it manually. I stoped to lay on the functionality of the ngx-tranlate and start doing what I can manually Instead of looking for some magic configuration online.

FrontBaseClientModule is a NX angular lib that is the core layout and more off few angular application this is way the separate for modules are so important for project that need to load 200 features (lazy module) it can be excellent to lazy load and import separately translations data by the way FrontBaseClientTranslateLoader is not an http loader it's a lazy junk loader which allows me to force some typescript and validation with the translations data and help with finding bugs on compile time instead of run time

export class FrontBaseClientModule {
  constructor(
    frontBaseClientTranslateLoader: FrontBaseClientTranslateLoader,
    appTranslateService: AppTranslateService
  ) {
    appTranslateService.onLangChange.pipe(take(1)).subscribe((event) => {
      firstValueFrom(
        frontBaseClientTranslateLoader.getTranslation(event.lang as any)
      ).then((res) => {
        appTranslateService.setTranslation(event.lang, res, true);
      });
    });
  }
  }

if any of you find this helpful please let me know :)

IJustDev commented 11 months ago

Does this work without lazy loaded but with eager loaded modules?

For example I am currently struggling with a child module that is always imported.

In my child module I do TranslateModule.forChild with isolate false Bildschirmfoto 2023-11-02 um 10 16 11 I also do in the child module export: [TranslateModule]

In the app module.ts I do for root Bildschirmfoto 2023-11-02 um 10 16 44

But somehow nothing is resolved. Any ideas?

IJustDev commented 11 months ago

So I came up with a salutation that might be useful for somebody. If you have non-lazy-loaded modules and use ngx-core, you can create a TranslateLoader yourself, that holds an array of translation loaders. Those will be asked for translations if one translation could not be translated.

import { Injectable } from "@angular/core";
import { TranslateLoader } from "@ngx-translate/core";
import { Observable, zip } from "rxjs";
import { map } from 'rxjs/operators';

@Injectable()
export class MultiTranslationLoader {

  protected readonly translationDefinitions: {usagePrefix: string, translationLoader: TranslateLoader}[] = [];

  getTranslation(lang: string): Observable<Object> {
    const loaders = this.translationDefinitions.map(definition => definition.translationLoader.getTranslation(lang));
    return zip(...loaders).pipe(
      map((translationsArray) => {
        return translationsArray.reduce((prev, translation, index) => {
          const translationDefinition = this.translationDefinitions[index];

          const translationToAppend = translationDefinition.usagePrefix === undefined ? translation : {[translationDefinition.usagePrefix]: translation};

          return {...prev, ...translationToAppend};
        }, {} as any);
      })
    );
  }

  public registerTranslationLoader(usagePrefix: undefined | string, translationLoader: TranslateLoader) {
    this.translationDefinitions.push({
      usagePrefix,
      translationLoader
    });
  }
}

Usage

// app.module.ts
@NgModule({
  imports: [TranslateModule.forRoot({loader: {provide: TranslateLoader, useClass: MultiTranslationLoader})]
})
class AppModule {
  constructor(@Inject(TranslateLoader) translateLoader: MultiTranslationLoader) {
    translateLoader.registerTranslationLoader(undefined, new FakeTranslationLoader());
  }
}
// child.module.ts
@NgModule({
  imports: [TranslateModule]
})
class ChildModule {
  constructor(@Inject(TranslateLoader) translateLoader: MultiTranslationLoader) {
    translateLoader.registerTranslationLoader('child1', new FakeTranslationLoader());
  }
}
// child.component.html
<h1>{{'child1.translationKey' | translate}}</h1> <!-- 'child1', because we provided it as usagePrefix in our child module. -->
<h1>{{'translationKey' | translate}}</h1> <!-- direct access to the translations object with all translations -->
allexgut commented 8 months ago

For anyone facing a similar issue, please notice the note in this section of the documentation. It explicitly says: Note: Never call a forRoot static method in the SharedModule. You might end up with different instances of the service in your injector tree. But you can use forChild if necessary. which I see multiple posters (including me) in this issue doing. Moving the forRoot call to the app.module resolved my issue.