Skip to content

Localization

Lucien Bertin edited this page Jul 23, 2019 · 2 revisions

tl;dr

  • lucca-front uses LOCALE_ID to know what locale to use
  • you can't dynamically change locale
  • all translations are overridable

Basics

Most components in lucca-front are made as to not have any text in it to avoid having to handle localization. But now some of them have, like the user-select or the modal

each component that has associated translations gets them through token injection. for example the default translations for the lu-user-select are provided as such

user-select-input.module

@NgModule({
	/* ... */
	providers: [
		{ provide: LU_USER_SELECT_INPUT_TRANSLATIONS, useValue: luUserSelectInputTranslations },
	]
})
export class LuUserSelectInputModule {}

each translations so provided contains translations in different languages, then lucca-front uses the one corresponding to the LOCALE_ID, with a fallback to english.

Prerequisites

as the translated labels are provided and use the provided value LOCALE_ID, the translations cannot change dynamically. as a result the first LOCALE_ID you provide is going to be the locale used.

it is not a huge problem as our solutions don't allow for a dynamic change of language ; but when working with ngx-translate it is easy to make a SPA that initializes in english, then calls an api to know what language the current user prefers, then switch to this language. With the current implementation, this behaviour is not supported. If you need a http call to know what language to use, there are 2 ways you could do it.

App initializer

angular allows for some providers to be resolved before the app really initialises. you can find documentation about it here, a basic example would look like that

//app.module.ts
@NgModule({
	declarations: [AppComponent],
	imports: [ /* ... */ ],
	providers: [
		{
			provide: APP_INITIALIZER,
			useFactory: localeInit, // this factory calls the api endpoint to know what `LOCALE_ID` to use
			multi: true,
		},
	],
	bootstrap: [AppComponent],
})
export class AppModule {}

the problem with this approach is that the initialize step of your application could become a bottleneck. the SPA will start initializing after the call resolves. If your api endpoint is slow, then you are delaying the Time to interactive, or even the First contentful paint on your SPA by this much.

Lazy loading + guard

this solution, which i am not a fan of, consists of

  1. having a robust lazyloading, each root route should load a module
  2. add a language guard at the root, that calls the api to know whats the current language is and sets it
  3. each lazyloaded module should reprovide LOCALE_ID with the right value
  4. the appModule should be as empty as possible

as a result, when you navigate to /lol for example, theses thing happen in this order

  1. app.module is loaded with LOCALE_ID = en
  2. the language guard on / is called, it calls the language api, gets the current language, sets it somewhere, and resolve allowing the navigation to continue
  3. the LolModule is lazyloaded
  4. it provides for its context LOCALE_ID with the right value

what might happen with this implementation is that all services that are provided in root may use the LOCALE_ID provided in root, so en

if you forget the guard and/or to provide LOCALE_ID in one of the lazy loaded modules, you might have a part of your SPA that is not in the same language as the rest

also it uses the notion of RouterGuard for something it is not made for: resolving something

and it will be called with all navigationbetween root routes, from /lol to /rofl, from /rofl to /lol

Override

if you want to override translations for a given components, you just have to provide your own set of translations

const myUserSelectInputTranslations = {
	en: { /* ... */ },
	fr: { /* ... */ },
};
@NgModule({
	imports: [
		LuUserSelectInputModule,
	],
	providers: [
		{ provide: LU_USER_SELECT_INPUT_TRANSLATIONS, useValue: myUserSelectInputTranslations },
	]
})
export class MyModule {}

and voila, your translations are used instead of the default ones

Clone this wiki locally