mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
feat: add i18n infrastructure (#2854)
* feat: add i18n infrastructure * store langauge selection to patchDB ui section * feat: react to patchdb language change --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
1
web/package-lock.json
generated
1
web/package-lock.json
generated
@@ -125,6 +125,7 @@
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"deep-equality-data-structures": "^1.5.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mime-types": "^2.1.35",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||
import { Title } from '@angular/platform-browser'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, map, merge, startWith } from 'rxjs'
|
||||
import { i18nService } from 'src/app/i18n/i18n.service'
|
||||
import { ConnectionService } from './services/connection.service'
|
||||
import { PatchDataService } from './services/patch-data.service'
|
||||
import { DataModel } from './services/patch-db/data-model'
|
||||
@@ -15,6 +16,7 @@ import { PatchMonitorService } from './services/patch-monitor.service'
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
private readonly title = inject(Title)
|
||||
private readonly i18n = inject(i18nService)
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
|
||||
readonly subscription = merge(
|
||||
@@ -37,9 +39,10 @@ export class AppComponent implements OnInit {
|
||||
startWith(true),
|
||||
)
|
||||
|
||||
async ngOnInit() {
|
||||
this.patch
|
||||
.watch$('ui', 'name')
|
||||
.subscribe(name => this.title.setTitle(name || 'StartOS'))
|
||||
ngOnInit() {
|
||||
this.patch.watch$('ui').subscribe(({ name, language }) => {
|
||||
this.title.setTitle(name || 'StartOS')
|
||||
this.i18n.setLanguage(language)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import { tuiTextfieldOptionsProvider } from '@taiga-ui/legacy'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { filter, of, pairwise } from 'rxjs'
|
||||
import { I18N_PROVIDERS } from 'src/app/i18n/i18n.providers'
|
||||
import { i18nService } from 'src/app/i18n/i18n.service'
|
||||
import {
|
||||
PATCH_CACHE,
|
||||
PatchDbSource,
|
||||
@@ -43,6 +45,7 @@ const {
|
||||
|
||||
export const APP_PROVIDERS: Provider[] = [
|
||||
NG_EVENT_PLUGINS,
|
||||
I18N_PROVIDERS,
|
||||
FilterPackagesPipe,
|
||||
UntypedFormBuilder,
|
||||
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
||||
@@ -75,7 +78,6 @@ export const APP_PROVIDERS: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
deps: [StorageService, AuthService, ClientStorageService, Router],
|
||||
useFactory: appInitializer,
|
||||
multi: true,
|
||||
},
|
||||
@@ -100,16 +102,18 @@ export const APP_PROVIDERS: Provider[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export function appInitializer(
|
||||
storage: StorageService,
|
||||
auth: AuthService,
|
||||
localStorage: ClientStorageService,
|
||||
router: Router,
|
||||
): () => void {
|
||||
export function appInitializer(): () => void {
|
||||
const storage = inject(StorageService)
|
||||
const auth = inject(AuthService)
|
||||
const localStorage = inject(ClientStorageService)
|
||||
const router = inject(Router)
|
||||
const i18n = inject(i18nService)
|
||||
|
||||
return () => {
|
||||
storage.migrate036()
|
||||
auth.init()
|
||||
localStorage.init()
|
||||
router.initialNavigation()
|
||||
i18n.setLanguage(i18n.language)
|
||||
}
|
||||
}
|
||||
|
||||
42
web/projects/ui/src/app/i18n/dictionaries/english.ts
Normal file
42
web/projects/ui/src/app/i18n/dictionaries/english.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export default {
|
||||
ui: {
|
||||
back: 'Back',
|
||||
change: 'Change',
|
||||
update: 'Update',
|
||||
reset: 'Reset',
|
||||
},
|
||||
system: {
|
||||
outlet: {
|
||||
system: 'System',
|
||||
general: 'General',
|
||||
email: 'Email',
|
||||
backup: 'Create Backup',
|
||||
restore: 'Restore Backup',
|
||||
interfaces: 'User Interface Addresses',
|
||||
acme: 'ACME',
|
||||
wifi: 'WiFi',
|
||||
sessions: 'Active Sessions',
|
||||
ssh: 'SSH',
|
||||
password: 'Change Password',
|
||||
},
|
||||
general: {
|
||||
title: 'General Settings',
|
||||
subtitle: 'Manage your overall setup and preferences',
|
||||
update: 'Software Update',
|
||||
restart: 'Restart to apply',
|
||||
check: 'Check for updates',
|
||||
tab: 'Browser Tab Title',
|
||||
language: 'Language',
|
||||
tor: 'Reset Tor',
|
||||
daemon: 'Restart the Tor daemon on your server',
|
||||
disk: 'Disk Repair',
|
||||
attempt: 'Attempt automatic repair',
|
||||
repair: 'Repair',
|
||||
sync: {
|
||||
title: 'Clock sync failure',
|
||||
subtitle:
|
||||
'This will cause connectivity issues. To resolve it, refer to the',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
44
web/projects/ui/src/app/i18n/dictionaries/spanish.ts
Normal file
44
web/projects/ui/src/app/i18n/dictionaries/spanish.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { i18n } from '../i18n.providers'
|
||||
|
||||
export default {
|
||||
ui: {
|
||||
back: 'Atrás',
|
||||
change: 'Cambiar',
|
||||
update: 'Actualizar',
|
||||
reset: 'Reiniciar',
|
||||
},
|
||||
system: {
|
||||
outlet: {
|
||||
system: 'Sistema',
|
||||
general: 'General',
|
||||
email: 'Correo Electrónico',
|
||||
backup: 'Crear Copia de Seguridad',
|
||||
restore: 'Restaurar Copia de Seguridad',
|
||||
interfaces: 'Direcciones de Interfaz de Usuario',
|
||||
acme: 'ACME',
|
||||
wifi: 'WiFi',
|
||||
sessions: 'Sesiones Activas',
|
||||
ssh: 'SSH',
|
||||
password: 'Cambiar Contraseña',
|
||||
},
|
||||
general: {
|
||||
title: 'Configuración General',
|
||||
subtitle: 'Gestiona tu configuración general y preferencias',
|
||||
update: 'Actualización de Software',
|
||||
restart: 'Reiniciar para aplicar',
|
||||
check: 'Buscar actualizaciones',
|
||||
tab: 'Título de la Pestaña del Navegador',
|
||||
language: 'Idioma',
|
||||
tor: 'Reiniciar Tor',
|
||||
daemon: 'Reiniciar el daemon de Tor en tu servidor',
|
||||
disk: 'Reparación de Disco',
|
||||
attempt: 'Intentar reparación automática',
|
||||
repair: 'Reparar',
|
||||
sync: {
|
||||
title: 'Fallo en la sincronización del reloj',
|
||||
subtitle:
|
||||
'Esto causará problemas de conectividad. Para resolverlo, consulta la',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies i18n
|
||||
23
web/projects/ui/src/app/i18n/i18n.pipe.ts
Normal file
23
web/projects/ui/src/app/i18n/i18n.pipe.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { I18N, i18n } from './i18n.providers'
|
||||
|
||||
type DeepKeyOf<T> = {
|
||||
[K in keyof T & string]: T[K] extends {}
|
||||
? T[K] extends string
|
||||
? K
|
||||
: `${K}.${DeepKeyOf<T[K]>}`
|
||||
: never
|
||||
}[keyof T & string]
|
||||
|
||||
@Pipe({
|
||||
standalone: true,
|
||||
name: 'i18n',
|
||||
pure: false,
|
||||
})
|
||||
export class i18nPipe implements PipeTransform {
|
||||
private readonly i18n = inject(I18N)
|
||||
|
||||
transform(path: DeepKeyOf<i18n>): string {
|
||||
return path.split('.').reduce((acc, part) => acc[part], this.i18n() as any)
|
||||
}
|
||||
}
|
||||
38
web/projects/ui/src/app/i18n/i18n.providers.ts
Normal file
38
web/projects/ui/src/app/i18n/i18n.providers.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { signal } from '@angular/core'
|
||||
import { tuiCreateToken, tuiProvide } from '@taiga-ui/cdk'
|
||||
import {
|
||||
TuiLanguageName,
|
||||
tuiLanguageSwitcher,
|
||||
TuiLanguageSwitcherService,
|
||||
} from '@taiga-ui/i18n'
|
||||
import ENGLISH from './dictionaries/english'
|
||||
import { i18nService } from './i18n.service'
|
||||
|
||||
export type i18n = typeof ENGLISH
|
||||
|
||||
export const I18N = tuiCreateToken(signal(ENGLISH))
|
||||
export const I18N_LOADER =
|
||||
tuiCreateToken<(lang: TuiLanguageName) => Promise<i18n>>()
|
||||
|
||||
export const I18N_PROVIDERS = [
|
||||
tuiLanguageSwitcher(async (language: TuiLanguageName): Promise<unknown> => {
|
||||
switch (language) {
|
||||
case 'spanish':
|
||||
return import('@taiga-ui/i18n/languages/spanish')
|
||||
default:
|
||||
return import('@taiga-ui/i18n/languages/english')
|
||||
}
|
||||
}),
|
||||
{
|
||||
provide: I18N_LOADER,
|
||||
useValue: async (language: TuiLanguageName): Promise<unknown> => {
|
||||
switch (language) {
|
||||
case 'spanish':
|
||||
return import('./dictionaries/spanish').then(v => v.default)
|
||||
default:
|
||||
return import('./dictionaries/english').then(v => v.default)
|
||||
}
|
||||
},
|
||||
},
|
||||
tuiProvide(TuiLanguageSwitcherService, i18nService),
|
||||
]
|
||||
30
web/projects/ui/src/app/i18n/i18n.service.ts
Normal file
30
web/projects/ui/src/app/i18n/i18n.service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { inject, Injectable, signal } from '@angular/core'
|
||||
import { TuiLanguageName, TuiLanguageSwitcherService } from '@taiga-ui/i18n'
|
||||
import { I18N, I18N_LOADER } from './i18n.providers'
|
||||
import { ApiService } from '../services/api/embassy-api.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class i18nService extends TuiLanguageSwitcherService {
|
||||
private readonly i18n = inject(I18N)
|
||||
private readonly i18nLoader = inject(I18N_LOADER)
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
readonly loading = signal(false)
|
||||
|
||||
override setLanguage(language: TuiLanguageName): void {
|
||||
if (this.language === language) {
|
||||
return
|
||||
}
|
||||
|
||||
super.setLanguage(language)
|
||||
this.loading.set(true)
|
||||
this.api.setDbValue(['language'], language).then(() =>
|
||||
this.i18nLoader(language).then(value => {
|
||||
this.i18n.set(value)
|
||||
this.loading.set(false)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
INJECTOR,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
@@ -16,13 +17,21 @@ import {
|
||||
tuiFadeIn,
|
||||
TuiIcon,
|
||||
tuiScaleIn,
|
||||
TuiTextfield,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
||||
import {
|
||||
TUI_CONFIRM,
|
||||
TuiButtonLoading,
|
||||
TuiButtonSelect,
|
||||
TuiDataListWrapper,
|
||||
} from '@taiga-ui/kit'
|
||||
import { TuiCell, tuiCellOptionsProvider, TuiHeader } from '@taiga-ui/layout'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { filter } from 'rxjs'
|
||||
import { i18nPipe } from 'src/app/i18n/i18n.pipe'
|
||||
import { i18nService } from 'src/app/i18n/i18n.service'
|
||||
import { PROMPT } from 'src/app/routes/portal/modals/prompt.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
@@ -36,13 +45,19 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
General Settings
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'ui.back' | i18n }}
|
||||
</a>
|
||||
{{ 'system.general.title' | i18n }}
|
||||
</ng-container>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>General</h3>
|
||||
<p tuiSubtitle>Manage your overall setup and preferences</p>
|
||||
<h3>
|
||||
{{ 'system.general.title' | i18n }}
|
||||
</h3>
|
||||
<p tuiSubtitle>
|
||||
{{ 'system.general.subtitle' | i18n }}
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
@if (server(); as server) {
|
||||
@@ -52,7 +67,9 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.zap" />
|
||||
<span tuiTitle>
|
||||
<strong>Software Update</strong>
|
||||
<strong>
|
||||
{{ 'system.general.update' | i18n }}
|
||||
</strong>
|
||||
<span tuiSubtitle>{{ server.version }}</span>
|
||||
</span>
|
||||
<button
|
||||
@@ -62,12 +79,12 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
(click)="onUpdate()"
|
||||
>
|
||||
@if (server.statusInfo.updated) {
|
||||
Restart to apply
|
||||
{{ 'system.general.restart' | i18n }}
|
||||
} @else {
|
||||
@if (eos.showUpdate$ | async) {
|
||||
Update
|
||||
{{ 'ui.update' | i18n }}
|
||||
} @else {
|
||||
Check for updates
|
||||
{{ 'system.general.check' | i18n }}
|
||||
}
|
||||
}
|
||||
</button>
|
||||
@@ -75,36 +92,65 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.app-window" />
|
||||
<span tuiTitle>
|
||||
<strong>Browser Tab Title</strong>
|
||||
<strong>
|
||||
{{ 'system.general.tab' | i18n }}
|
||||
</strong>
|
||||
<span tuiSubtitle>{{ name() }}</span>
|
||||
</span>
|
||||
<button tuiButton (click)="onTitle()">Change</button>
|
||||
<button tuiButton (click)="onTitle()">
|
||||
{{ 'ui.change' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.languages" />
|
||||
<span tuiTitle>
|
||||
<strong>Language</strong>
|
||||
<span tuiSubtitle>English</span>
|
||||
<strong>
|
||||
{{ 'system.general.language' | i18n }}
|
||||
</strong>
|
||||
<span tuiSubtitle>{{ i18n.language }}</span>
|
||||
</span>
|
||||
<button tuiButton>Change</button>
|
||||
<button
|
||||
tuiButtonSelect
|
||||
tuiButton
|
||||
[loading]="i18n.loading()"
|
||||
[ngModel]="i18n.language"
|
||||
(ngModelChange)="i18n.setLanguage($event)"
|
||||
>
|
||||
{{ 'ui.change' | i18n }}
|
||||
<tui-data-list-wrapper
|
||||
*tuiTextfieldDropdown
|
||||
size="l"
|
||||
[items]="languages"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.circle-power" (click)="count = count + 1" />
|
||||
<span tuiTitle>
|
||||
<strong>Reset Tor</strong>
|
||||
<span tuiSubtitle>Restart the Tor daemon on your server</span>
|
||||
<strong>
|
||||
{{ 'system.general.tor' | i18n }}
|
||||
</strong>
|
||||
<span tuiSubtitle>
|
||||
{{ 'system.general.daemon' | i18n }}
|
||||
</span>
|
||||
</span>
|
||||
<button tuiButton appearance="glass" (click)="onReset()">Reset</button>
|
||||
<button tuiButton appearance="glass" (click)="onReset()">
|
||||
{{ 'ui.reset' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
@if (count > 4) {
|
||||
<div tuiCell tuiAppearance="outline-grayscale" @tuiScaleIn @tuiFadeIn>
|
||||
<tui-icon icon="@tui.briefcase-medical" />
|
||||
<span tuiTitle>
|
||||
<strong>Disk Repair</strong>
|
||||
<span tuiSubtitle>Attempt automatic repair</span>
|
||||
<strong>
|
||||
{{ 'system.general.disk' | i18n }}
|
||||
</strong>
|
||||
<span tuiSubtitle>
|
||||
{{ 'system.general.attempt' | i18n }}
|
||||
</span>
|
||||
</span>
|
||||
<button tuiButton appearance="glass" (click)="onRepair()">
|
||||
Repair
|
||||
{{ 'system.general.repair' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -122,6 +168,11 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
[tuiCell] {
|
||||
background: var(--tui-background-neutral-1);
|
||||
}
|
||||
|
||||
[tuiSubtitle],
|
||||
tui-data-list-wrapper ::ng-deep [tuiOption] {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`,
|
||||
providers: [tuiCellOptionsProvider({ height: 'spacious' })],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -130,14 +181,20 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
RouterLink,
|
||||
i18nPipe,
|
||||
TuiTitle,
|
||||
TuiHeader,
|
||||
TuiCell,
|
||||
TuiAppearance,
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TitleDirective,
|
||||
SystemSyncComponent,
|
||||
TuiIcon,
|
||||
TuiButtonLoading,
|
||||
TuiButtonSelect,
|
||||
TuiDataListWrapper,
|
||||
TuiTextfield,
|
||||
FormsModule,
|
||||
],
|
||||
})
|
||||
export default class SystemGeneralComponent {
|
||||
@@ -159,6 +216,8 @@ export default class SystemGeneralComponent {
|
||||
readonly server = toSignal(this.patch.watch$('serverInfo'))
|
||||
readonly name = toSignal(this.patch.watch$('ui', 'name'))
|
||||
readonly eos = inject(EOSService)
|
||||
readonly i18n = inject(i18nService)
|
||||
readonly languages = ['english', 'spanish']
|
||||
|
||||
onUpdate() {
|
||||
if (this.server()?.statusInfo.updated) {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLink, TuiNotification, TuiTitle } from '@taiga-ui/core'
|
||||
import { i18nPipe } from 'src/app/i18n/i18n.pipe'
|
||||
|
||||
@Component({
|
||||
selector: 'system-sync',
|
||||
template: `
|
||||
<tui-notification appearance="warning">
|
||||
<div tuiTitle>
|
||||
Clock sync failure
|
||||
{{ 'system.general.sync.title' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
This will cause connectivity issues. Refer to the
|
||||
{{ 'system.general.sync.subtitle' | i18n }}
|
||||
<a
|
||||
tuiLink
|
||||
iconEnd="@tui.external-link"
|
||||
@@ -17,13 +18,12 @@ import { TuiLink, TuiNotification, TuiTitle } from '@taiga-ui/core'
|
||||
rel="noreferrer"
|
||||
[textContent]="'StartOS docs'"
|
||||
></a>
|
||||
to resolve it
|
||||
</div>
|
||||
</div>
|
||||
</tui-notification>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiNotification, TuiTitle, TuiLink],
|
||||
imports: [TuiNotification, TuiTitle, TuiLink, i18nPipe],
|
||||
})
|
||||
export class SystemSyncComponent {}
|
||||
|
||||
@@ -4,13 +4,14 @@ import { RouterModule } from '@angular/router'
|
||||
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiBadgeNotification } from '@taiga-ui/kit'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { i18nPipe } from 'src/app/i18n/i18n.pipe'
|
||||
import { BadgeService } from 'src/app/services/badge.service'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { SYSTEM_MENU } from './system.const'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<span *title>System</span>
|
||||
<span *title>{{ 'system.outlet.general' | i18n }}</span>
|
||||
<aside class="g-aside">
|
||||
@for (cat of menu; track $index) {
|
||||
@if ($index) {
|
||||
@@ -20,13 +21,13 @@ import { SYSTEM_MENU } from './system.const'
|
||||
<a
|
||||
tuiCell="s"
|
||||
routerLinkActive="active"
|
||||
[routerLink]="page.routerLink"
|
||||
[routerLink]="page.item.split('.').at(-1)"
|
||||
>
|
||||
<tui-icon [icon]="page.icon" />
|
||||
<span tuiTitle>
|
||||
<span>
|
||||
{{ page.title }}
|
||||
@if (page.routerLink === 'general' && badge()) {
|
||||
{{ page.item | i18n }}
|
||||
@if (page.item === 'system.outlet.general' && badge()) {
|
||||
<tui-badge-notification>{{ badge() }}</tui-badge-notification>
|
||||
}
|
||||
</span>
|
||||
@@ -106,6 +107,7 @@ import { SYSTEM_MENU } from './system.const'
|
||||
TuiTitle,
|
||||
TitleDirective,
|
||||
TuiBadgeNotification,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class SystemComponent {
|
||||
|
||||
@@ -1,60 +1,50 @@
|
||||
export const SYSTEM_MENU = [
|
||||
[
|
||||
{
|
||||
title: 'General',
|
||||
icon: '@tui.settings',
|
||||
routerLink: 'general',
|
||||
item: 'system.outlet.general',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
icon: '@tui.mail',
|
||||
routerLink: 'email',
|
||||
item: 'system.outlet.email',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'Create Backup',
|
||||
icon: '@tui.copy-plus',
|
||||
routerLink: 'backup',
|
||||
item: 'system.outlet.backup',
|
||||
},
|
||||
{
|
||||
title: 'Restore Backup',
|
||||
icon: '@tui.database-backup',
|
||||
routerLink: 'restore',
|
||||
item: 'system.outlet.restore',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'User Interface Addresses',
|
||||
icon: '@tui.monitor',
|
||||
routerLink: 'interfaces',
|
||||
item: 'system.outlet.interfaces',
|
||||
},
|
||||
{
|
||||
title: 'ACME',
|
||||
icon: '@tui.award',
|
||||
routerLink: 'acme',
|
||||
item: 'system.outlet.acme',
|
||||
},
|
||||
{
|
||||
title: 'WiFi',
|
||||
icon: '@tui.wifi',
|
||||
routerLink: 'wifi',
|
||||
item: 'system.outlet.wifi',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'Active Sessions',
|
||||
icon: '@tui.clock',
|
||||
routerLink: 'sessions',
|
||||
item: 'system.outlet.sessions',
|
||||
},
|
||||
{
|
||||
title: 'SSH',
|
||||
icon: '@tui.terminal',
|
||||
routerLink: 'ssh',
|
||||
item: 'system.outlet.ssh',
|
||||
},
|
||||
{
|
||||
title: 'Change Password',
|
||||
icon: '@tui.key',
|
||||
routerLink: 'password',
|
||||
item: 'system.outlet.password',
|
||||
},
|
||||
],
|
||||
]
|
||||
] as const
|
||||
|
||||
@@ -24,6 +24,7 @@ export const mockPatchData: DataModel = {
|
||||
highScore: 0,
|
||||
},
|
||||
},
|
||||
language: 'english',
|
||||
ackInstructions: {},
|
||||
},
|
||||
serverInfo: {
|
||||
|
||||
@@ -12,6 +12,7 @@ export type UIData = {
|
||||
}
|
||||
ackInstructions: Record<string, boolean>
|
||||
theme: string
|
||||
language: 'english' | 'spanish'
|
||||
}
|
||||
|
||||
export type UIMarketplaceData = {
|
||||
|
||||
Reference in New Issue
Block a user