Core: .forChild() usage with lazy loading routes

Created on 11 Feb 2017  路  49Comments  路  Source: ngx-translate/core

I'm submitting a ... (check one with "x")

[ ] bug report => check the FAQ and search github for a similar issue or PR before submitting
[x] support request => check the FAQ and search github for a similar issue before submitting
[ ] feature request

Current behavior
AppModule

export function createTranslateLoader(http: Http) {
    return new TranslateHttpLoader(http, "/i18n/", "/index.json");
}
imports: [
    TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: createTranslateLoader,
                deps: [Http]
            }
        }),
]

SharedModule

imports: [
    //TranslateModule
],
exports: [
    //TranslateModule,
]

AdminModule

export function createTranslateLoader(http: Http) {
    return new TranslateHttpLoader(http, "/i18n/", "/admin.json");
}
imports: [
    TranslateModule.forChild({
            loader: {
                provide: TranslateLoader,
                useFactory: createTranslateLoader,
                deps: [Http]
            }
        }),
]

Expected/desired behavior
I would like to load additional JSON files per component / module, i.e. an admin.json for the administration panel of our app.
I would like to import one TranslateModule inside of SharedModule and configure it allow all modules that don't explicite call .forChild().

Both doesn't work, it simply doesn't load the admin.json and hence doesn't translate strings defined in that file.

Please tell us about your environment:

  • ngx-translate version: 6.0.0-beta.1

  • Angular version: 2.4.7

  • Browser: [Firefox 51.0.1]

  • Language: [TypeScript 2.0]

critical bug

Most helpful comment

appModule.ts

image

Create one shared module translateSharedModule.ts

image

appComponent.ts
import {TranslateService} from '@ngx-translate/core';
constructor(public translate: TranslateService) {
translate.setDefaultLang('en');

}

homeModule.ts //or any other submodules
import { TranslateSharedLazyModule } from '../../Common/translateSharedLazyModule';
imports : [
TranslateSharedLazyModule
], ..

All 49 comments

Since I didn't found any plunker working with ngx-translate librairy and I also had some difficulties to manage to make it work with LoadChildren,
I've setup a way that work pretty well for me:

_I've created two SharedModules, (one for lazyLoading and one for the other part of my application)_

SharedLazyModule for lazy loading content:

@NgModule({
  imports: [
    HttpModule,
    CommonModule,
    TranslateModule.forChild({}),
  ],
  exports: [
    CommonModule,
    TranslateModule
  ]
})
export class SharedLazyModule {}

SahredModule for App

// AoT requires an exported function for factories
export function HttpLoaderFactory(http: Http) {
   return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

@NgModule({
    imports: [
      HttpModule,
      CommonModule,
      TranslateModule.forRoot({
           provide: TranslateLoader,
           useFactory: HttpLoaderFactory,
           deps: [Http],
         })
    ],
    exports: [
      CommonModule,
      TranslateModule
    ]
})
export class SharedModule {

     constructor(private translate: TranslateService) {

        translate.addLangs(["en", "fr"]);
        translate.setDefaultLang('en');

        let browserLang = translate.getBrowserLang();
        translate.use(browserLang.match(/en|fr/) ? browserLang : 'en');
    }
}

See Plunker:
https://plnkr.co/LVmIuI1Xw9vFn0IuC2jW

@neolanders your answer does not relate to the question, you are posting the solution explained in the README for SharedModule.

I'm trying to create a module on top of the TranslateModule where each module could put in his translations, but so far not successful

I believe ngx-translate is broken in regards to its "Lazy loading feature" and since ocombe is now part of the Angular core team, it will probably never get fixed.

Most likely, people will have to switch to Angular's i18n functionality once it becomes more rich in regards to its features.

I wrote a article about how to have 1 json file per lazy loaded module without having to write a new Custom Loader etc... it's quiet simple, only the documentation is not clear in fact:
https://medium.com/@TuiZ/how-to-split-your-i18n-file-per-lazy-loaded-module-with-ngx-translate-3caef57a738f

@Tuizi your solution is a workaround, since you are basically creating TranslateModule in every lazy loaded module, and you need something in between to keep the selected lang synced. The problem we are talking about is that it should work without this workaround that we all have to do for now

@mebibou I'm curious about what contributors think about that. For me it's not a workaround has I simply follow what the documentation say (paragraph "lazy loaded module")

@Tuizi no I'm saying that's not the point of the issue here. We want to have lazy loading modules being in sync with the master modules, without having to push the changes of lang from master to slave everytime. It should basically work without needing to use isolate: true and adding some code (your this.store.dispatch and all)

any progress on this? is Tuizi's solution is indead a work-around. Anyone found a real solution / fix?

What would be a real solution?

I have time to work on a PR and I would like to try to solve this problem, which seems to impact many people.

What do you think of that?

Core Module

export function createTranslateLoader(http: HttpClient) {
  // We may need a new loader? To define the folder where i18n's files are, and the extension
  return new LazyTranslateHttpLoader(http, './assets/i18n/', '.json');
}

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

Lazy loaded Module: admin

  TranslateModule.forChild({lazy: 'admin'})

When admin module is loaded, the file ./assets/i18n/admin/en.json will be requested.

What do you think of that?

I wouldn't mind having to create different translateLoader per module, just without using isolate: true should work, something along:

// app.module.ts
export function HttpLoaderFactory(http: Http) {
   return new TranslateHttpLoader(http, './assets/i18n/app/', '.json');
}

@NgModule({
    imports: [
      TranslateModule.forChild({
        loader: {
          provide: TranslateLoader,
           useFactory: HttpLoaderFactory,
           deps: [Http]
        }
     })
   ]
})
export class AppModule {}

// in a lazy-loaded page's module
// pages/home/page.module.ts
export function HttpLoaderFactory(http: Http) {
   return new TranslateHttpLoader(http, './assets/i18n/home/', '.json');
}

@NgModule({
    imports: [
      TranslateModule.forChild({
        loader: {
          provide: TranslateLoader,
           useFactory: HttpLoaderFactory,
           deps: [Http]
        }
     })
   ]
})
export class HomePageModule {}

@Tuizi taken from the README:

Otherwise, by default, it will share its data with other instances of the service (but you can still use a different loader/compiler/parser/handler even if you don't isolate the service).

I don't think that is true, when I don't use isolate: true, my custom translate loader (like in example above) does not load the translation at all, nothing happens

Thank you are right. I will check that

If I understand correctly, the OP wants to lazily load translations along with his lazily loaded modules. We did managed to load translations lazily but used a slightly different approach. All our translations are defined in 'namespaces' and every translation key is prefixed with the namespace (for instance 'validations.required'). Our translations are loaded with a MissingTranslationHandler. The MissingTranslationHandler uses the namespace to determine which translation file to load.

So, we're not lazily loading our translations per (lazily loaded) module but per translation namespace. By prefixing your translation keys with your module name you could of course get the required effect.

Gist with our MissingTranslationHandler

Hope this can help someone.

Seems the best thing you can do right now is use namespacing, with the -f namespaced-json flag. Seems to be the only solution for modularity. The problem i would like to solve now is a way to globally load the pipe once in a lazy loading environment. I have to use a sharedmodule to load 'TranslateModule.forRoot()' in all my modules for it to work right now, not very efficient.

@Almar Thanks !! That's a great solution for now.

@ocombe are you planning to fix/support this any time soon?

I tried forchild solution but it is not working. Could we have any plunk working well ?
ngx-translate version: 6.0.0-beta.1

Angular version: 6.0.3

Browser: [Chrome Version 67.0.3396.79 (Official Build) (64-bit)]

Language: [TypeScript 2.7.2]

If you use Angular v6, you have to use ngx translate version >= 10

@ocombe here my configs:

"@ngx-translate/core": "^10.0.2",
"@ngx-translate/http-loader": "^3.0.1",

Same here

Same here (angular 6.1.3, ngx-translate 10.0.2)

don't get this... do we need to keep .forChild and .forRoot together? I get an error if I don't add the second one:

Could not find IonicModule.forRoot call in "imports"

Personnaly I still have a issue for getting all my translations... my scenario is :
a main application calling
a lazy loaded module
where is a container module
where the translation is used.
Everything works expect the translation :-/

If I dont lazy load I get my translations.

There is a sample:
app.module

// Http loader for ngx-translate.
export function createTranslateLoader(http: HttpClient, settings: GlobalAppSettings) {
  return new TranslationLoader(http, settings);
}

@NgModule({
  declarations: [
....
  ],
  imports: [
....

    // Other external modules.
    TranslateModule.forRoot({
      loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient, GlobalAppSettings] }, isolate: true
....
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

app-routing.module

imports....
export const routes: Routes = [
...
    {
        path: 'unemployment-info',
        canActivate:[OAuthGuardService],
        loadChildren: './unemployment.module#UnemploymentModule',
        data: { id: 4, title:'tabs.eDossier' }

    }
...
];

@NgModule({
    imports: [RouterModule.forRoot(routes, {preloadingStrategy:聽PreloadAllModules, enableTracing:false})],
    exports: [RouterModule]
})
export class AppRoutingModule { }

unemployment.module

export const routes: Routes = [{ path: '', component: UnemploymentInfoComponent }];
// Http loader for ngx-translate.
export function createTranslateLoader(httpClient: HttpClient, settings: GlobalAppSettings) {
  return new TranslationLoader(httpClient, settings);
}

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild(routes),
    EDossierContainerModule,
    TranslateModule.forChild({
      loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient, 
GlobalAppSettings] }
    }),
  ], exports: [],
  declarations: [...]
})
export class UnemploymentModule { }

e-dossier-container.module

// Http loader for ngx-translate.
export function createTranslateLoader(httpClient: HttpClient, settings: GlobalAppSettings) {
  return new TranslationLoader(httpClient, settings);
}

@NgModule({
  imports: [
    ....
    // Translate
    TranslateModule.forChild({
      loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient, GlobalAppSettings] }
    }),
    ....
  ],
  declarations: [
   ....
  ],
  exports: [HomeMenuComponent],
  providers: [
...
  ]
})
export class EDossierContainerModule { }

It is an issue,

@ocombe a couple of days ago i've faced the same issue for another Angular Library (igniteui),

The problem can be solved by removing the forRoot() implementation and using the recently introduced providedInproperty of the @Injectable decorator, in short TranslateModule.forRoot() will be deprecated.

This would be breaking-change so it requires someone to dedicate at least a full day to migrate the implementation, this is what the Angular 6 documentation says:

Beginning with Angular 6.0, the preferred way to create a singleton services is to specify on the service that it should be provided in the application root. This is done by setting providedIn to root on the service's @Injectable decorator:

Same issue here using a lazy load modules.

Same issue with lazy loading, any signs of progress?
@ocombe
Until they fix this, I have a workaround here

any update on this ?

temp solution works for me

in your shared module import and export like so

without using forChild or forRoot

 imports: [
    TranslateModule
  ],
  exports: [
   TranslateModule
  ], 

in you app module import forRoot

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

this is was cool for me .

In case this can help anyone out there-

I've been struggling with a similar issue.

I had a lazy loaded module that didn't show any translations.
After (a lot) of debugging, I saw that the TranslateStore was being created twice.

At first I thought it was a bug, but after digging into it I saw that the Lazy Loaded module imported a module, which imported a module which had a TranslateModule.forRoot() in its imports.

It was hard to trace because it was "hidden" inside of a shared library project.

Anyway, I hope it will save some of you some debugging time, if you're suffering from the same (hidden) cluster fudge 馃槃

appModule.ts

image

Create one shared module translateSharedModule.ts

image

appComponent.ts
import {TranslateService} from '@ngx-translate/core';
constructor(public translate: TranslateService) {
translate.setDefaultLang('en');

}

homeModule.ts //or any other submodules
import { TranslateSharedLazyModule } from '../../Common/translateSharedLazyModule';
imports : [
TranslateSharedLazyModule
], ..

My case was:

  • Have a global translation file loaded with the main application
  • Lazy load several feature modules, each with their own translation files
  • Extend the core translations only when needed

I solved my problem by adding an option extend , which allows lazy loaded modules to extend the translations for a given language instead of ignoring them, if already present.

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

If you're interested, you can get it here:

https://github.com/stemda/ngx-translate

Probably not good enough for a PR but maybe it helps some of you...

Hi all, I trying to use this example, but its not working? Why for lazy module translation dictionary not merged with module forRoot?

I wouldn't mind having to create different translateLoader per module, just without using isolate: true should work, something along:

// app.module.ts
export function HttpLoaderFactory(http: Http) {
   return new TranslateHttpLoader(http, './assets/i18n/app/', '.json');
}

@NgModule({
    imports: [
      TranslateModule.forChild({
        loader: {
          provide: TranslateLoader,
           useFactory: HttpLoaderFactory,
           deps: [Http]
        }
     })
   ]
})
export class AppModule {}

// in a lazy-loaded page's module
// pages/home/page.module.ts
export function HttpLoaderFactory(http: Http) {
   return new TranslateHttpLoader(http, './assets/i18n/home/', '.json');
}

@NgModule({
    imports: [
      TranslateModule.forChild({
        loader: {
          provide: TranslateLoader,
           useFactory: HttpLoaderFactory,
           deps: [Http]
        }
     })
   ]
})
export class HomePageModule {}

I posted a possible solution for your problem above. If you dont want to check out the fork, just download a patch

https://github.com/foo/bar/commit/${SHA}.patch

https://github.com/stemda/ngx-translate/commit/f54bc5303124ec35a5e2ab4f51e59cf9920f5cd8.patch

and try it out.

@stemda omg, nice! I just few minutes already forked for same patch. :)
Are you can create PR for resolve this issue?

@iamruslanbakirov okay, I just started a pull request.
btw. I decided against overwriting already present translations within the merge since that could lead to very confusing behaviour. the result would depend on the loading sequence of the modules, which isn't really great I think.

Hello.

If I understand correctly, with "extend" it will be possible to use a shared.json and a module.json file (by lazy loading module) and the "extend" will not overwrite but make an append with shared.json?

Would be perfect.
I'm really in need of this to better organize the translations and still make GET just the necessary one used in the module (lazy-loading).

Thanks.

Hey, @Tolitech, sorry for my late response. Yes, that's excactly what the patch does.

@stemda
thank you.
I'll wait for this feature.

@ocombe what we can do for you to speed up merging @stemda changes?

Hey!

I'm facing the same issue here and @stemda changes will fix the problem. I'm really looking forward to this PR being merged.

Thank you.

I wrote a article about how to have 1 json file per lazy loaded module without having to write a new Custom Loader etc... it's quiet simple, only the documentation is not clear in fact:
https://medium.com/@TuiZ/how-to-split-your-i18n-file-per-lazy-loaded-module-with-ngx-translate-3caef57a738f

Hey , Could you please give it a clear documentation. I am using ngrx/store and i need to use ngx-translate in lazy loaded module and other places it should be in module level transalation.
if you have time please upload a sample example in github

@pvkrijesh a working example updated to Angular 8:
https://github.com/Tuizi/i18n-split-example

@pvkrijesh a working example updated to Angular 8:
https://github.com/Tuizi/i18n-split-example

@Tuizi

This example does not work exactly.

The problem is not just detaching each module into a new json file.

But if you have a shared json with 10 translations for example, it cannot be used by others and always has to be copied again, which makes using this way bad.

Based on the solution @Almar provided, I'm also using the MissingTranslationHandler to determine if additional translations need to be fetched. The parts that I've changed compared to @Almar solution is that I make use of the loader configured inside the translation service. Because a new instance of the TranslationService is created when using the forChild method, this is nicely isolated, the same counts for the missinghandler that is configured.

https://gist.github.com/kristofdegrave/64863e249bbc8768fe33fc666dfa8bf5

I隆ve developed a lazy loading per module solution through a factory.

  1. create a folder assets/i18n/modulexxx for every module
  2. add the language json en.json, fr.json..etc in that folder and the shared ones in assets/i18n
  3. Add the TranslateLoder factory (e.g shared/i18n/TranslateLoader.ts)
import { TranslateLoader } from '@ngx-translate/core';
import { Observable, from, merge } from 'rxjs';

const appAvailableLanguages = ['ar', 'en', 'es', 'fr'];
const defaultLanguage = 'en';

export class TranslateLoaderFactory {
    static forModule(module: string): any {
        return class LazyTranslateLoader implements TranslateLoader {
            getTranslation(lang: string): Observable<any> {
                if (!appAvailableLanguages.includes(lang)) {
                    return merge(
                        from(import(`../../../assets/i18n/${defaultLanguage}.json`)),
                        from(import(`../../../assets/i18n/${module}/${defaultLanguage}.json`))
                    );
                }
                return merge(
                    from(import(`../../../assets/i18n/${lang}.json`)),
                    from(import(`../../../assets/i18n/${module}/${lang}.json`))
                );
            }
        }
    }
}
  1. Use in in your root and lazy loaded modules as:

    • root:

      typescript TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateLoaderFactory.forModule('main'), deps: [HttpClient] } }),

    • lazy loaded module:

      typescript TranslateModule.forChild({ loader: { provide: TranslateLoader, useClass: TranslateLoaderFactory.forModule('mymodule'), deps: [HttpClient] } }),

      Hope that helps

I隆ve developed a lazy loading per module solution through a factory.

  1. create a folder assets/i18n/modulexxx for every module
  2. add the language json en.json, fr.json..etc in that folder and the shared ones in assets/i18n
  3. Add the TranslateLoder factory (e.g shared/i18n/TranslateLoader.ts)
import { TranslateLoader } from '@ngx-translate/core';
import { Observable, from, merge } from 'rxjs';

const appAvailableLanguages = ['ar', 'en', 'es', 'fr'];
const defaultLanguage = 'en';

export class TranslateLoaderFactory {
    static forModule(module: string): any {
        return class LazyTranslateLoader implements TranslateLoader {
            getTranslation(lang: string): Observable<any> {
                if (!appAvailableLanguages.includes(lang)) {
                    return merge(
                        from(import(`../../../assets/i18n/${defaultLanguage}.json`)),
                        from(import(`../../../assets/i18n/${module}/${defaultLanguage}.json`))
                    );
                }
                return merge(
                    from(import(`../../../assets/i18n/${lang}.json`)),
                    from(import(`../../../assets/i18n/${module}/${lang}.json`))
                );
            }
        }
    }
}
  1. Use in in your root and lazy loaded modules as:
  • root:
TranslateModule.forRoot({
          loader: {
              provide: TranslateLoader,
              useClass: TranslateLoaderFactory.forModule('main'),
              deps: [HttpClient]
          }
      }),
  • lazy loaded module:
TranslateModule.forChild({
          loader: {
              provide: TranslateLoader,
              useClass: TranslateLoaderFactory.forModule('mymodule'),
              deps: [HttpClient]
          }
      }),

Hope that helps

Hi, could you share a github project working? i have testest but not work.

one more workaround to trigger language change globally including lazy modules

  export class SharedModule {
    constructor(
      private languageService: LanguageService,
      private translateService: TranslateService
    ) {
      this.languageService.language.subscribe((lang) => {
        this.translateService.use(lang);
      });
    }
  }

I am the same problem. Isolet true not working for me. Do you have a solution ?
I use Angular 9

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bjornharvold picture bjornharvold  路  3Comments

dankerk picture dankerk  路  3Comments

briancullinan picture briancullinan  路  3Comments

pndewit picture pndewit  路  3Comments

webprofusion-chrisc picture webprofusion-chrisc  路  4Comments