Added flags for language change
This commit is contained in:
36
src/app/app.html
Normal file
36
src/app/app.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<mat-toolbar color="primary">
|
||||||
|
<span>{{ 'APP.TITLE' | translate }}</span>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
|
||||||
|
<!-- Language -->
|
||||||
|
<mat-form-field appearance="outline" style="width: 170px; margin-right: 8px;">
|
||||||
|
<mat-select [value]="lang.lang()" (selectionChange)="lang.use($event.value)">
|
||||||
|
<mat-select-trigger>
|
||||||
|
<img class="flag-icon" [src]="lang.lang() === 'de' ? '/assets/flags/de.svg' : '/assets/flags/gb.svg'"
|
||||||
|
alt="" aria-hidden="true">
|
||||||
|
<span style="margin-left: 8px;">
|
||||||
|
{{ lang.lang() === 'de' ? ('LANG.DE' | translate) : ('LANG.EN' | translate) }}
|
||||||
|
</span>
|
||||||
|
</mat-select-trigger>
|
||||||
|
|
||||||
|
<mat-option value="de">
|
||||||
|
<img class="flag-icon" src="/assets/flags/de.svg" alt="" aria-hidden="true">
|
||||||
|
<span> {{ 'LANG.DE' | translate }} </span>
|
||||||
|
</mat-option>
|
||||||
|
|
||||||
|
<mat-option value="en">
|
||||||
|
<img class="flag-icon" src="/assets/flags/gb.svg" alt="" aria-hidden="true">
|
||||||
|
<span> {{ 'LANG.EN' | translate }} </span>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Theme -->
|
||||||
|
<button mat-icon-button (click)="theme.toggle()">
|
||||||
|
<mat-icon>{{ themeIcon() }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
|
<main class="container app-surface">
|
||||||
|
<router-outlet />
|
||||||
|
</main>
|
||||||
13
src/app/app.scss
Normal file
13
src/app/app.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.spacer { flex: 1 1 auto; }
|
||||||
|
.container { max-width: 960px; margin: 24px auto; padding: 0 16px; }
|
||||||
|
mat-form-field { --mdc-outlined-text-field-container-shape: 20px; }
|
||||||
|
|
||||||
|
.flag-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 14px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 0 0 1px rgba(0,0,0,.08) inset;
|
||||||
|
vertical-align: -2px;
|
||||||
|
}
|
||||||
|
mat-option .flag-icon { margin-right: 8px; }
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, computed, inject } from '@angular/core';
|
import {Component, computed, effect, inject} from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@@ -22,35 +22,8 @@ import {LanguageService} from './service/language.service';
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
TranslateModule
|
TranslateModule
|
||||||
],
|
],
|
||||||
template: `
|
templateUrl: './app.html',
|
||||||
<mat-toolbar color="primary">
|
styleUrl: './app.scss',
|
||||||
<span>{{ 'APP.TITLE' | translate }}</span>
|
|
||||||
<span class="spacer"></span>
|
|
||||||
|
|
||||||
<!-- Sprache -->
|
|
||||||
<mat-form-field appearance="outline" style="width: 150px; margin-right: 8px;">
|
|
||||||
<mat-select [value]="lang.lang()" (selectionChange)="lang.use($event.value)">
|
|
||||||
<mat-option value="de">{{ 'LANG.DE' | translate }}</mat-option>
|
|
||||||
<mat-option value="en">{{ 'LANG.EN' | translate }}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<!-- Theme -->
|
|
||||||
<button mat-icon-button aria-label="Theme umschalten" (click)="theme.toggle()">
|
|
||||||
<mat-icon>{{ themeIcon() }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
</mat-toolbar>
|
|
||||||
|
|
||||||
<main class="container app-surface">
|
|
||||||
<router-outlet />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.spacer { flex: 1 1 auto; }
|
|
||||||
.container { max-width: 960px; margin: 24px auto; padding: 0 16px; }
|
|
||||||
mat-form-field { --mdc-outlined-text-field-container-shape: 20px; }
|
|
||||||
</style>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
export class App {
|
export class App {
|
||||||
readonly theme = inject(ThemeService);
|
readonly theme = inject(ThemeService);
|
||||||
|
|||||||
@@ -2,4 +2,6 @@
|
|||||||
|
|
||||||
static readonly THEME_KEY = 'theme';
|
static readonly THEME_KEY = 'theme';
|
||||||
static readonly LANGUAGE_KEY = 'lang';
|
static readonly LANGUAGE_KEY = 'lang';
|
||||||
|
|
||||||
|
static readonly RELOAD_ALL_LANG_LISTENER_KEY = 'language_dirty_flag';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class LanguageService {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.translate.addLangs(['de', 'en']);
|
this.translate.addLangs(['de', 'en']);
|
||||||
this.translate.setFallbackLang('en');
|
this.translate.setFallbackLang('en');
|
||||||
|
this.lang.set(this.getInitial());
|
||||||
this.use(this.lang());
|
this.use(this.lang());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
src/app/service/reload.service.ts
Normal file
32
src/app/service/reload.service.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable, NgZone, signal } from '@angular/core';
|
||||||
|
import {Constants} from '../constants/Constants';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class ReloadService {
|
||||||
|
private readonly _reloadTick = signal(0);
|
||||||
|
readonly reloadTick = this._reloadTick.asReadonly();
|
||||||
|
|
||||||
|
private readonly _languageChangedTick = signal(0);
|
||||||
|
readonly languageChangedTick = this._languageChangedTick.asReadonly();
|
||||||
|
|
||||||
|
constructor(zone: NgZone) {
|
||||||
|
zone.runOutsideAngular(() => {
|
||||||
|
globalThis.addEventListener('storage', (e: StorageEvent) => {
|
||||||
|
this.informListeners(e, zone);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private informListeners(e: StorageEvent, zone: NgZone) {
|
||||||
|
if (e.key === Constants.LANGUAGE_KEY) {
|
||||||
|
zone.run(() => this._languageChangedTick.update(v => v + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bumpLanguageChanged(): void {
|
||||||
|
this._reloadTick.update(v => v + 1);
|
||||||
|
localStorage.setItem(Constants.RELOAD_ALL_LANG_LISTENER_KEY, String(Date.now()));
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/assets/flags/de.svg
Normal file
5
src/assets/flags/de.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">
|
||||||
|
<path fill="#000" d="M0 0h640v160H0z"/>
|
||||||
|
<path fill="#dd0000" d="M0 160h640v160H0z"/>
|
||||||
|
<path fill="#ffce00" d="M0 320h640v160H0z"/>
|
||||||
|
</svg>
|
||||||
10
src/assets/flags/gb.svg
Normal file
10
src/assets/flags/gb.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">
|
||||||
|
<defs><clipPath id="a"><path d="M0 0h640v480H0z"/></clipPath></defs>
|
||||||
|
<g clip-path="url(#a)">
|
||||||
|
<path fill="#012169" d="M0 0h640v480H0z"/>
|
||||||
|
<path stroke="#fff" stroke-width="60" d="M0 0l640 480M640 0L0 480"/>
|
||||||
|
<path stroke="#c8102e" stroke-width="40" d="M0 0l640 480M640 0L0 480"/>
|
||||||
|
<path fill="#fff" d="M0 192h640v96H0zM272 0h96v480h-96z"/>
|
||||||
|
<path fill="#c8102e" d="M0 208h640v64H0zM288 0h64v480h-64z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
Reference in New Issue
Block a user