Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.forChild() usage with lazy loading routes #425

Closed
dhardtke opened this issue Feb 11, 2017 · 55 comments
Closed

.forChild() usage with lazy loading routes #425

dhardtke opened this issue Feb 11, 2017 · 55 comments

Comments

@dhardtke
Copy link
Contributor

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]

@neolanders
Copy link

neolanders commented Feb 21, 2017

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

@mebibou
Copy link

mebibou commented Sep 20, 2017

@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

@dhardtke
Copy link
Contributor Author

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.

@Tuizi
Copy link

Tuizi commented Sep 23, 2017

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

@mebibou
Copy link

mebibou commented Sep 24, 2017

@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

@Tuizi
Copy link

Tuizi commented Sep 26, 2017

@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")

@mebibou
Copy link

mebibou commented Sep 26, 2017

@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)

@pieterdegraeuwe
Copy link

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

@Tuizi
Copy link

Tuizi commented Oct 6, 2017

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?

@mebibou
Copy link

mebibou commented Oct 6, 2017

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 {}

@mebibou
Copy link

mebibou commented Oct 6, 2017

@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

@Tuizi
Copy link

Tuizi commented Oct 6, 2017

Thank you are right. I will check that

@Almar
Copy link

Almar commented Dec 7, 2017

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.

@gate3
Copy link

gate3 commented Dec 25, 2017

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.

@Mokto
Copy link

Mokto commented Mar 28, 2018

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

@Dunos
Copy link

Dunos commented Apr 3, 2018

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

@emmano3h
Copy link

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]

@ocombe
Copy link
Member

ocombe commented Jun 12, 2018 via email

@emmano3h
Copy link

@ocombe here my configs:

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

@wilker7ribeiro
Copy link

Same here

@YaroslavLyzlov
Copy link

Same here (angular 6.1.3, ngx-translate 10.0.2)

@erperejildo
Copy link

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"

@cdskill
Copy link

cdskill commented Sep 14, 2018

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 { }

@stemda
Copy link
Contributor

stemda commented Apr 25, 2019

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...

@monkeycatdog
Copy link

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 {}

@stemda
Copy link
Contributor

stemda commented May 10, 2019

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.

@monkeycatdog
Copy link

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

@stemda
Copy link
Contributor

stemda commented May 10, 2019

@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.

@Tolitech
Copy link

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.

@stemda
Copy link
Contributor

stemda commented May 20, 2019

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

@Tolitech
Copy link

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

@jansowinski
Copy link

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

@akiik
Copy link

akiik commented Jun 27, 2019

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.

@pvkrijesh

This comment has been minimized.

@Tuizi
Copy link

Tuizi commented Sep 23, 2019

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

@Tolitech
Copy link

@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.

@kristofdegrave
Copy link

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

ocombe pushed a commit to stemda/ngx-translate that referenced this issue Feb 5, 2020
@ocombe ocombe closed this as completed in 6b675d6 Feb 5, 2020
@Eyasserk
Copy link

Eyasserk commented Jul 1, 2020

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

@jlopezjuy
Copy link

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.

@appstore-redphoenix
Copy link

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);
      });
    }
  }

@Totot0
Copy link

Totot0 commented Nov 15, 2020

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

@isamely1990
Copy link

isamely1990 commented Apr 29, 2021

After a few days searching, reading and trying other solutions, I found that in my case, my team and I, we had a bad implementation of lazy loading in the routing, we had the component declared in the route, and in the pages module, causing that we only receive the translation in just 1 module instead of all modules where we used TranslateModule.forChild with isolate: true

So maybe works to check if the lazy load is ok in your project, the component mus be declare only in each anycomponent-module.ts

path: 'clients',
// component: ClientsComponent, *shouldn't be here*
canActivate: [AuthGuard],
loadChildren: () => import('./clients/clients.module').then(m => m.ClientsModule)

@brianmriley
Copy link

@Juusmann's solution works for me as well. To be extremely specific and to add clarity to anyone still wondering how it all fits together, I have the following setup (using abbreviated module definitions):

AppModule

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

...

imports: [
   ...
   // NOTE: Normally we'd stick TranslateModule in `CoreModule` but the ability to lazy load
   // module translations and extend the main one only works if you set it up in the root `AppModule`.
   // Use the TranslateModule's config param "isolate: false" to allow child, lazy loaded modules to 
   // extend the parent or root module's loaded translations.
   TranslateModule.forRoot({
        defaultLanguage: 'en',
        loader: {
            provide: TranslateLoader,
            useFactory: HttpLoaderFactory,
            deps: [HttpClient]
        },
        isolate: false
    }),
]

SharedModule

@NgModule({
    declarations: DECLARATIONS,
    imports: [
        ...MODULES,
        ...TranslateModule
    ],
    exports: [
        ...MODULES,
        ...TranslateModule
        ...DECLARATIONS,
    ]
})
export class SharedModule {
}

LazyLoadedModule

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

...

imports: [
   ...
   // Use the TranslateModule's config param "extend: true" to extend the parent or root module's
   // loaded translations.
   TranslateModule.forChild({
        defaultLanguage: 'en',
        loader: {
            provide: TranslateLoader,
            useFactory: HttpLoaderFactory,
            deps: [HttpClient]
        },
        extend: true
    }),
]
export class LazyLoadedModule {
    constructor(protected translateService: TranslateService) {
        const currentLang = translateService.currentLang;
        translateService.currentLang = '';
        translateService.use(currentLang);
    }
}

Key Points

  • Use the TranslateModule's method forRoot() with config param isolate: false in AppModule to allow child, lazy loaded modules to extend the parent or root module's loaded translations.
  • Use the TranslateModule's method forChild() with config param extend: true in LazyLoadedModule to extend the parent or root module's loaded translations.
  • DO NOT attempt to move the TranslateModule setup and configuration from AppModule to a CoreModule as it won't allow the root translations and only works when setup directly in AppModule.
  • Force TranslateModule to load the LazyLoadedModule's child translations by setting the locale on the TranslateService in its constructor.

@eulersson
Copy link

Still having this issue. I can't understand why this issue is closed. The extend option does not seem to respect the parent module translations. Does anyone have a StackBlitz showing the extend doing what it's supposed to do for lazy loaded modules that load .json files from another subfolder?

@niroshank
Copy link

LazyLoadedModule - isolated : true -> Main translations file is loaded
LazyLoadedModule - isolated : false -> Feature translations file is loaded
I also have added the translateService in its constructor.

@KevinKallin
Copy link

@brianmriley
OMG thank you!

Me and another colleague have worked a couple of days trying to solve this and your solution did work perfectly fine! :)

@sdedieu
Copy link

sdedieu commented May 20, 2024

Hey @brianmriley !
I did your instructions but end up with translateService.currentLang as undefined (instead of the chosen language) in the lazy loaded Module contructor.
Any idea why ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests