diff --git a/src/app/app.component.html b/src/app/app.component.html index 9e422d63..b791315f 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -10,16 +10,6 @@ > {{ 'app.user-manual' | translate }} - - 🇬🇧 English - 🇩🇪 Deutsch - 🇫🇷 Français - - {{ environmentLabel @@ -35,8 +25,8 @@ ; + protected currentLanguage: string = this.i18nService.language; projectInMenu: Observable; @@ -46,6 +47,7 @@ export class AppComponent { private trainrunSectionService: TrainrunSectionService, private nodeService: NodeService, private labelService: LabelService, + private i18nService: I18nService, ) { if (!this.disableBackend) { this.authenticated = authService.initialized; @@ -58,9 +60,16 @@ export class AppComponent { } } - changeLocale(locale: string) { - localStorage.setItem("locale", locale); - location.reload(); + @Input() + get language() { + return this.currentLanguage; + } + + set language(language: string) { + if (language !== this.currentLanguage) { + this.i18nService.setLanguage(language); + this.currentLanguage = language; + } } @Input() diff --git a/src/app/core/i18n/i18n.module.ts b/src/app/core/i18n/i18n.module.ts index e19914ff..c7929192 100644 --- a/src/app/core/i18n/i18n.module.ts +++ b/src/app/core/i18n/i18n.module.ts @@ -1,23 +1,23 @@ import {NgModule, APP_INITIALIZER, LOCALE_ID} from "@angular/core"; import {CommonModule} from "@angular/common"; import {TranslatePipe} from "./translate.pipe"; -import {I18n} from "./i18n.service"; +import {I18nService} from "./i18n.service"; @NgModule({ declarations: [TranslatePipe], // Declare the pipe imports: [CommonModule], providers: [ - I18n, + I18nService, { // Load locale data at app start-up provide: APP_INITIALIZER, - useFactory: (i18n: I18n) => () => i18n.setLocale(), - deps: [I18n], + useFactory: (i18nService: I18nService) => () => i18nService.setLanguage(), + deps: [I18nService], multi: true, }, { // Set the runtime locale for the app provide: LOCALE_ID, - useFactory: (i18n: I18n) => i18n.locale, - deps: [I18n], + useFactory: (i18nService: I18nService) => i18nService.language, + deps: [I18nService], } ], exports: [TranslatePipe] // Export the pipe diff --git a/src/app/core/i18n/i18n.service.ts b/src/app/core/i18n/i18n.service.ts index 7fdf6394..13eab38e 100644 --- a/src/app/core/i18n/i18n.service.ts +++ b/src/app/core/i18n/i18n.service.ts @@ -5,85 +5,94 @@ import {loadTranslations} from "@angular/localize"; @Injectable({ providedIn: "root", }) - export class I18n { - locale = "en"; - readonly allowedLocales = ["en", "fr", "de", "it"]; - translations: any = {}; - - async setLocale() { - const userLocale = localStorage.getItem("locale"); - - // If the user has a preferred language stored in LocalStorage, use it. - if (userLocale && this.allowedLocales.includes(userLocale)) { - this.locale = userLocale; - } else { - localStorage.setItem("locale", this.locale); - } - - // Use webpack magic string to only include required locale data - const localeModule = await import( - /* webpackInclude: /(en|de|fr|it)\.mjs$/ */ - `/node_modules/@angular/common/locales/${this.locale}.mjs` - ); - registerLocaleData(localeModule.default); - - // Load translation file initially - await this.loadTranslations(); - } - - async loadTranslations() { - const localeTranslationsModule = await import( - `src/assets/i18n/${this.locale}.json` - ); - - // Ensure translations are flattened if necessary - this.translations = this.flattenTranslations(localeTranslationsModule.default); - - // Load translations for the current locale at runtime - loadTranslations(this.translations); +export class I18nService { + readonly allowedLanguages = ["en", "fr", "de", "it"]; + private currentLanguage: string = this.getLanguageFromStorage() || this.detectNavigatorLanguage(); + translations: any = {}; + + get language(): string { + return this.currentLanguage; + } + + async setLanguage(language?: string) { + if (language && this.allowedLanguages.includes(this.language)) { + this.setLanguageToStorage(language); + this.currentLanguage = language; } - - // Helper function to flatten nested translations - // nested JSON : - // { - // "app": { - // "login": "Login", - // "models": {...} - // } - // } - // flattened JSON : - // { - // "app.login": "Login", - // "app.models...": ..., - // "app.models...": ... - // } - private flattenTranslations(translations: any): any { - const flattenedTranslations = {}; - - function flatten(obj, prefix = "") { - for (const key in obj) { - if (typeof obj[key] === "string") { - flattenedTranslations[prefix + key] = obj[key]; - } else if (typeof obj[key] === "object") { - flatten(obj[key], prefix + key + "."); - } + + const languageModule = await import( + /* webpackInclude: /(en|de|fr|it)\.mjs$/ */ + `/node_modules/@angular/common/locales/${this.language}.mjs` + ); + registerLocaleData(languageModule.default); + + await this.loadTranslations(); + } + + private setLanguageToStorage(language: string) { + localStorage.setItem("i18nLng", language); + } + + private getLanguageFromStorage(): string | null { + const lang = localStorage.getItem("i18nLng"); + return this.allowedLanguages.includes(lang) ? lang : null; + } + + private detectNavigatorLanguage(): string { + const navigatorLanguage = navigator.language.slice(0, 2); + return this.allowedLanguages.includes(navigatorLanguage) ? navigatorLanguage : this.allowedLanguages[0]; + } + + async loadTranslations() { + const languageTranslationsModule = await import( + `src/assets/i18n/${this.language}.json` + ); + + this.translations = this.flattenTranslations(languageTranslationsModule.default); + loadTranslations(this.translations); + } + + // Helper function to flatten nested translations + // nested JSON : + // { + // "app": { + // "login": "Login", + // "models": {...} + // } + // } + // flattened JSON : + // { + // "app.login": "Login", + // "app.models...": ..., + // "app.models...": ... + // } + private flattenTranslations(translations: any): any { + const flattenedTranslations = {}; + + function flatten(obj, prefix = "") { + for (const key in obj) { + if (typeof obj[key] === "string") { + flattenedTranslations[prefix + key] = obj[key]; + } else if (typeof obj[key] === "object") { + flatten(obj[key], prefix + key + "."); } } - - flatten(translations); - return flattenedTranslations; } - - // Used for the pipe and allowing parameters - translate(key: string, params?: any): string { - let translation = this.translations[key] || key; - - if (params) { - Object.keys(params).forEach(param => { - translation = translation.replace(`{$${param}}`, params[param]); - }); - } - - return translation; + + flatten(translations); + return flattenedTranslations; + } + + // Used for the pipe and allowing parameters + translate(key: string, params?: any): string { + let translation = this.translations[key] || key; + + if (params) { + Object.keys(params).forEach(param => { + translation = translation.replace(`{$${param}}`, params[param]); + }); } + + return translation; } +} diff --git a/src/app/core/i18n/translate.pipe.ts b/src/app/core/i18n/translate.pipe.ts index bb38f238..e11045a3 100644 --- a/src/app/core/i18n/translate.pipe.ts +++ b/src/app/core/i18n/translate.pipe.ts @@ -1,14 +1,14 @@ import {Pipe, PipeTransform} from "@angular/core"; -import {I18n} from "./i18n.service"; +import {I18nService} from "./i18n.service"; @Pipe({ name: "translate", pure: false }) export class TranslatePipe implements PipeTransform { - constructor(private i18n: I18n) {} + constructor(private i18nService: I18nService) {} transform(key: string, params?: any): string { - return this.i18n.translate(key, params); + return this.i18nService.translate(key, params); } }