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

Refacto language + add navigator preferences #349

Merged
merged 10 commits into from
Nov 15, 2024
14 changes: 2 additions & 12 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@
>
{{ 'app.user-manual' | translate }}
</a>
<sbb-select
(selectionChange)="changeLocale($event.value)"
[value]="locale"
class="language-selector"
>
<sbb-option value="en">🇬🇧 English</sbb-option>
<sbb-option value="de">🇩🇪 Deutsch</sbb-option>
<sbb-option value="fr">🇫🇷 Français</sbb-option>
<!-- <sbb-option value="it">🇮🇹 Italiano</sbb-option> -->
</sbb-select>
</sbb-app-chooser-section>
<sbb-header-environment class="noprint" *ngIf="environmentLabel">{{
environmentLabel
Expand All @@ -35,8 +25,8 @@
</sbb-usermenu>
<sbb-menu #menu="sbbMenu">
<sbb-select
(selectionChange)="changeLocale($event.value)"
[value]="locale"
(selectionChange)="language = $event.value"
[value]="localStorageLanguage"
class="language-selector language-selector-menu"
(click)="$event.stopPropagation()"
(keydown)="$event.stopPropagation()"
Expand Down
16 changes: 12 additions & 4 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {NetzgrafikDto} from "./data-structures/business.data.structures";
import {Operation} from "./models/operation.model";
import {LabelService} from "./services/data/label.serivce";
import {NodeService} from "./services/data/node.service";
import {I18nService} from "./core/i18n/i18n.service";

@Component({
selector: "sbb-root",
Expand All @@ -20,9 +21,9 @@ import {NodeService} from "./services/data/node.service";
export class AppComponent {
readonly disableBackend = environment.disableBackend;
readonly version = packageJson.version;
readonly locale = localStorage.getItem("locale");
readonly environmentLabel = environment.label;
readonly authenticated: Promise<unknown>;
protected localStorageLanguage: string;
louisgreiner marked this conversation as resolved.
Show resolved Hide resolved

projectInMenu: Observable<ProjectDto | null>;

Expand All @@ -46,7 +47,10 @@ export class AppComponent {
private trainrunSectionService: TrainrunSectionService,
private nodeService: NodeService,
private labelService: LabelService,
private i18nService: I18nService,
) {
this.i18nService.setLanguage();
this.localStorageLanguage = localStorage.getItem("i18nLng");
if (!this.disableBackend) {
this.authenticated = authService.initialized;
}
Expand All @@ -58,9 +62,13 @@ export class AppComponent {
}
}

changeLocale(locale: string) {
localStorage.setItem("locale", locale);
location.reload();
@Input()
set language(value: string) {
louisgreiner marked this conversation as resolved.
Show resolved Hide resolved
if (value !== this.localStorageLanguage) {
localStorage.setItem("i18nLng", value);
this.i18nService.setLanguage();
this.localStorageLanguage = localStorage.getItem("i18nLng");
louisgreiner marked this conversation as resolved.
Show resolved Hide resolved
}
}

@Input()
Expand Down
12 changes: 6 additions & 6 deletions src/app/core/i18n/i18n.module.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
45 changes: 25 additions & 20 deletions src/app/core/i18n/i18n.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,46 @@ import {loadTranslations} from "@angular/localize";
@Injectable({
providedIn: "root",
})
export class I18n {
locale = "en";
readonly allowedLocales = ["en", "fr", "de", "it"];
export class I18nService {
readonly allowedLanguages = ["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);
language: string;

async setLanguage() {
emersion marked this conversation as resolved.
Show resolved Hide resolved
const userLanguage = localStorage.getItem("i18nLng");
if (userLanguage && this.allowedLanguages.includes(userLanguage)) {
this.language = userLanguage;
}

else {
louisgreiner marked this conversation as resolved.
Show resolved Hide resolved
const navigatorLanguage = navigator.language.slice(0, 2);
if (this.allowedLanguages.includes(navigatorLanguage)) {
this.language = navigatorLanguage;
} else {
this.language = this.allowedLanguages[0];
}
localStorage.setItem("i18nLng", this.language);
louisgreiner marked this conversation as resolved.
Show resolved Hide resolved
}

// Use webpack magic string to only include required locale data
const localeModule = await import(
const languageModule = await import(
/* webpackInclude: /(en|de|fr|it)\.mjs$/ */
`/node_modules/@angular/common/locales/${this.locale}.mjs`
`/node_modules/@angular/common/locales/${this.language}.mjs`
);
registerLocaleData(localeModule.default);
registerLocaleData(languageModule.default);

// Load translation file initially
await this.loadTranslations();
}

async loadTranslations() {
const localeTranslationsModule = await import(
`src/assets/i18n/${this.locale}.json`
const languageTranslationsModule = await import(
`src/assets/i18n/${this.language}.json`
);

// Ensure translations are flattened if necessary
this.translations = this.flattenTranslations(localeTranslationsModule.default);
this.translations = this.flattenTranslations(languageTranslationsModule.default);

// Load translations for the current locale at runtime
// Load translations for the current language at runtime
loadTranslations(this.translations);
}

Expand Down
6 changes: 3 additions & 3 deletions src/app/core/i18n/translate.pipe.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}