Merge branch 'show-login-required' into 'master'

Display a message when login is required.

See merge request openlp/web-remote!125
This commit is contained in:
Chris Witterholt 2024-07-22 17:42:58 +00:00
commit 59f7e3407c
42 changed files with 488 additions and 352 deletions

View File

@ -25,36 +25,36 @@
"tx": "node scripts/tx.js"
},
"dependencies": {
"@angular/animations": "^18.1.0",
"@angular/cdk": "^18.1.0",
"@angular/common": "^18.1.0",
"@angular/compiler": "^18.1.0",
"@angular/core": "^18.1.0",
"@angular/forms": "^18.1.0",
"@angular/animations": "^18.1.1",
"@angular/cdk": "^18.1.1",
"@angular/common": "^18.1.1",
"@angular/compiler": "^18.1.1",
"@angular/core": "^18.1.1",
"@angular/forms": "^18.1.1",
"@angular/material": "^18.1.1",
"@angular/platform-browser": "^18.1.0",
"@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0",
"@angular/platform-browser": "^18.1.1",
"@angular/platform-browser-dynamic": "^18.1.1",
"@angular/router": "^18.1.1",
"@fontsource/roboto": "^5.0.13",
"@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0",
"core-js": "^3.37.1",
"material-icons": "^1.13.12",
"rxjs": "^7.8.1",
"zone.js": "^0.14.7"
"zone.js": "^0.14.8"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.1.0",
"@angular-devkit/core": "^18.1.0",
"@angular-devkit/schematics": "^18.1.0",
"@angular-devkit/build-angular": "^18.1.1",
"@angular-devkit/core": "^18.1.1",
"@angular-devkit/schematics": "^18.1.1",
"@angular-eslint/builder": "^18.1.0",
"@angular-eslint/eslint-plugin": "^18.1.0",
"@angular-eslint/eslint-plugin-template": "^18.1.0",
"@angular-eslint/schematics": "^18.1.0",
"@angular-eslint/template-parser": "^18.1.0",
"@angular/cli": "~18.1.0",
"@angular/compiler-cli": "^18.1.0",
"@angular/language-service": "^18.1.0",
"@angular/cli": "~18.1.1",
"@angular/compiler-cli": "^18.1.1",
"@angular/language-service": "^18.1.1",
"@chiragrupani/karma-chromium-edge-launcher": "^2.4.1",
"@transifex/api": "^7.1.2",
"@types/jasmine": "~5.1.4",
@ -69,9 +69,9 @@
"browserslist-useragent-regexp": "^4.1.3",
"eslint": "^8.57.0",
"eslint-plugin-import": "~2.29.1",
"eslint-plugin-jsdoc": "~48.7.0",
"eslint-plugin-jsdoc": "~48.8.3",
"eslint-plugin-prefer-arrow": "~1.2.3",
"jasmine-core": "~5.1.2",
"jasmine-core": "~5.2.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.3",
"karma-chrome-launcher": "~3.2.0",

View File

@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { TranslateService } from '@ngx-translate/core';
@ -8,7 +7,6 @@ import { State, Display, DisplayMode } from './responses';
import { OpenLPService, WebSocketStatus } from './openlp.service';
import { WindowRef } from './window-ref.service';
import { PageTitleService } from './page-title.service';
import { LoginComponent } from './components/login/login.component';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { DisplayModeSelectorComponent } from './components/display-mode-selector/display-mode-selector.component';
@ -27,7 +25,7 @@ export class AppComponent implements OnInit {
DisplayMode = DisplayMode;
state = new State();
showLogin = false;
showLogin: boolean;
pageTitle = 'OpenLP Remote';
appVersion = '0.0';
webSocketOpen = false;
@ -38,12 +36,13 @@ export class AppComponent implements OnInit {
useLanguageFromOpenlp = false;
constructor(private translateService: TranslateService, private pageTitleService: PageTitleService,
private openlpService: OpenLPService, private dialog: MatDialog, private bottomSheet: MatBottomSheet,
private openlpService: OpenLPService, private bottomSheet: MatBottomSheet,
private windowRef: WindowRef, private shortcutsService: ShortcutsService, private settingsService: SettingsService) {
this.pageTitleService.pageTitleChanged$.subscribe(pageTitle => this.pageTitle = pageTitle);
this.openlpService.stateChanged$.subscribe(item => this.state = item);
this.openlpService.webSocketStateChanged$.subscribe(status => this.webSocketOpen = status === WebSocketStatus.Open);
this.shortcutsService.shortcutsChanged$.subscribe(shortcuts => this.addShortcuts(shortcuts));
this.openlpService.isLoggedInChanged$.subscribe(result => this.showLogin = !result);
this.appVersion = this.windowRef.nativeWindow.appVersion || '0.0';
this.webSocketOpen = openlpService.webSocketStatus === WebSocketStatus.Open;
@ -146,16 +145,7 @@ export class AppComponent implements OnInit {
}
login() {
const dialogRef = this.dialog.open(LoginComponent, {
width: '250px'
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.showLogin = false;
this.openlpService.setAuthToken(result.token);
}
});
this.openlpService.openLoginDialog();
}
nextItem() {

View File

@ -21,10 +21,14 @@
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-raised-button id="loginButton"
color="primary"
<button mat-button
mat-dialog-close
(click)="cancel()">
{{ 'CANCEL' | translate | titlecase }}
</button>
<button mat-button
[disabled]="!loginForm.form.valid"
(click)="performLogin()">
(click)="login()">
{{ 'LOGIN' | translate | titlecase }}
</button>
</div>

View File

@ -31,13 +31,18 @@ export class LoginComponent {
});
}
performLogin() {
cancel() {
this.dialogRef.close(false);
}
login() {
this.openlpService.login({ username: this.username, password: this.password }).subscribe({
next: result => {
this.snackBar.open(this.loginSucceededMessage, '', { duration: 2000 });
this.openlpService.isLoggedInChanged$.emit(true);
this.snackBar.open(this.loginSucceededMessage, null, { duration: 2000 });
this.dialogRef.close(result);
},
error: () => this.snackBar.open(this.loginFailedMessage, '', { duration: 2000 })
error: () => this.snackBar.open(this.loginFailedMessage, null, { duration: 2000 })
});
}
}

View File

@ -1,7 +1,13 @@
import { Injectable, EventEmitter } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, Subscription } from 'rxjs';
import { finalize, shareReplay, tap } from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpStatusCode } from '@angular/common/http';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { TitleCasePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { Observable, of, Subscription } from 'rxjs';
import { catchError, finalize, shareReplay, tap } from 'rxjs/operators';
import { SentenceCasePipe } from './components/pipes/sentence-case.pipe';
import { LoginComponent } from './components/login/login.component';
import {
PluginDescription,
@ -44,14 +50,21 @@ export class OpenLPService {
public stateChanged$: EventEmitter<State>;
public messageReceived$: EventEmitter<Message<MessageType>>;
public webSocketStateChanged$: EventEmitter<WebSocketStatus>;
public isLoggedInChanged$: EventEmitter<boolean> = new EventEmitter<boolean>();
private isTwelveHourTime = true;
loginMessage: string;
unauthorizedMessage: string;
private webSocketTimeoutHandle: any = 0;
private _stateWebSocketSubscription: Subscription;
private _messageWebSocketSubscription: Subscription;
private _retrieveSystemInformationSubscription: Subscription;
constructor(private http: HttpClient) {
constructor(
private http: HttpClient, private dialog: MatDialog, private snackBar: MatSnackBar,
private titleCasePipe: TitleCasePipe, private sentenceCasePipe: SentenceCasePipe,
private translateService: TranslateService
) {
const host = window.location.hostname;
let port: string;
if (environment.production) {
@ -66,6 +79,12 @@ export class OpenLPService {
this.webSocketStateChanged$ = new EventEmitter<WebSocketStatus>();
this.messageReceived$ = new EventEmitter<Message<MessageType>>();
this.createWebSocket();
this.translateService.stream('LOGIN').subscribe((res: string) => {
this.loginMessage = this.titleCasePipe.transform(res);
});
this.translateService.stream('UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION').subscribe((res: string) => {
this.unauthorizedMessage = this.sentenceCasePipe.transform(res);
});
}
assertApiVersionExact(version: number, revision: number) {
@ -218,14 +237,45 @@ export class OpenLPService {
return this.doPost<AuthToken>(`${this.apiURL}/core/login`, credentials);
}
private handleError<T>(result?: T) {
return (error: any): Observable<T> => {
if (error.status == HttpStatusCode.Unauthorized) {
const snackBarRef = this.snackBar.open(this.unauthorizedMessage, this.loginMessage, {
duration: 5000
});
snackBarRef.onAction().subscribe(() => {
this.openLoginDialog()
})
throw this.unauthorizedMessage
}
return of(result as T);
};
}
public openLoginDialog() {
const dialogRef = this.dialog.open(LoginComponent, {
width: '250px'
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.setAuthToken(result.token);
}
});
}
protected doGet<T>(url: string): Observable<T> {
return this.http.get<T>(url, httpOptions);
return this.http.get<T>(url, httpOptions).pipe(
catchError(this.handleError<T>())
);
}
protected doPost<T>(url: string, body: any): Observable<T> {
// User is expecting instant response, so we'll accelerate the websocket reconnection process if needed.
this.reconnectWebSocketIfNeeded();
return this.http.post<T>(url, body, httpOptions);
return this.http.post<T>(url, body, httpOptions).pipe(
catchError(this.handleError<T>())
);
}
get webSocketStatus(): WebSocketStatus {

View File

@ -56,6 +56,7 @@
"THEMES": "Temas",
"THEME_LEVEL": "Temavlak",
"THEME_OPTIONS": "Tema Opsies",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Ongemagtigde: u moet eers aanmeld om hierdie aksie uit te voer",
"USER_NAME": "Gebruikersnaam",
"USER_INTERFACE": "Gebruikerskoppelvlak",
"YES": "Ja"

View File

@ -56,6 +56,7 @@
"THEMES": "Теми",
"THEME_LEVEL": "Ниво на темата",
"THEME_OPTIONS": "Опции за темата",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Неупълномощен: първо трябва да влезете, за да извършите това действие",
"USER_NAME": "Потребителско име",
"USER_INTERFACE": "Потребителски интерфейс",
"YES": "Да"

View File

@ -56,6 +56,7 @@
"THEMES": "Témata",
"THEME_LEVEL": "Úroveň Tématu",
"THEME_OPTIONS": "Možnosti Tématu",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Neautorizováno: Chcete-li provést tuto akci, měli byste se nejprve přihlásit",
"USER_NAME": "Uživatelské Jméno",
"USER_INTERFACE": "Uživatelské Rozhraní",
"YES": "Ano"

View File

@ -56,6 +56,7 @@
"THEMES": "Temaer",
"THEME_LEVEL": "Temaniveau",
"THEME_OPTIONS": "Temaindstillinger",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Uautoriseret: du skal logge ind først for at udføre denne handling",
"USER_NAME": "Brugernavn",
"USER_INTERFACE": "Brugergrænseflade",
"YES": "Ja"

View File

@ -56,6 +56,7 @@
"THEMES": "Themen",
"THEME_LEVEL": "Themenlevel",
"THEME_OPTIONS": "Themenoptionen",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Nicht autorisiert: Sie sollten sich zuerst anmelden, um diese Aktion auszuführen",
"USER_NAME": "Benutzername",
"USER_INTERFACE": "Benutzeroberfläche",
"YES": "Ja"

View File

@ -56,6 +56,7 @@
"THEMES": "Θέματα",
"THEME_LEVEL": "Επίπεδο Θέματος",
"THEME_OPTIONS": "Επιλογές Θέματος",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Μη εξουσιοδοτημένο: θα πρέπει πρώτα να συνδεθείτε για να εκτελέσετε αυτήν την ενέργεια",
"USER_NAME": "Όνομα Χρήστη",
"USER_INTERFACE": "Διεπαφή Χρήστη",
"YES": "Ναι"

View File

@ -56,6 +56,7 @@
"THEMES": "Themes",
"THEME_LEVEL": "Theme Level",
"THEME_OPTIONS": "Theme Options",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Unauthorized: you should login first to perform this action",
"USER_NAME": "User Name",
"USER_INTERFACE": "User Interface",
"YES": "Yes"

View File

@ -56,6 +56,7 @@
"THEMES": "Themes",
"THEME_LEVEL": "Theme Level",
"THEME_OPTIONS": "Theme Options",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Unauthorized: you should login first to perform this action",
"USER_NAME": "User Name",
"USER_INTERFACE": "User Interface",
"YES": "Yes"

View File

@ -56,6 +56,7 @@
"THEMES": "Themes",
"THEME_LEVEL": "Theme Level",
"THEME_OPTIONS": "Theme Options",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Unauthorized: you should login first to perform this action",
"USER_NAME": "User Name",
"USER_INTERFACE": "User Interface",
"YES": "Yes"

View File

@ -56,6 +56,7 @@
"THEMES": "Temas",
"THEME_LEVEL": "Nivel de Tema",
"THEME_OPTIONS": "Opciones de Tema",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "No autorizada: primero debe iniciar sesión para realizar esta acción",
"USER_NAME": "Nombre de Usuario",
"USER_INTERFACE": "Interfaz de Usuario",
"YES": "Sí"

View File

@ -56,6 +56,7 @@
"THEMES": "Temas",
"THEME_LEVEL": "Nivel de Tema",
"THEME_OPTIONS": "Opciones de Tema",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "No autorizada: primero debe iniciar sesión para realizar esta acción",
"USER_NAME": "Nombre de Usuario",
"USER_INTERFACE": "Interfaz de Usuario",
"YES": "Sí"

View File

@ -56,6 +56,7 @@
"THEMES": "Teemad",
"THEME_LEVEL": "Teema Tase",
"THEME_OPTIONS": "Teema Valikud",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Volitamata: selle toimingu tegemiseks peaksite kõigepealt sisse logima",
"USER_NAME": "Kasutajanimi",
"USER_INTERFACE": "Kasutajaliides",
"YES": "Jah"

View File

@ -56,6 +56,7 @@
"THEMES": "Teemoja",
"THEME_LEVEL": "Teeman Taso",
"THEME_OPTIONS": "Teeman Asetukset",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Luvaton: kirjaudu ensin sisään suorittaaksesi tämän toiminnon",
"USER_NAME": "Käyttäjänimi",
"USER_INTERFACE": "Käyttöliittymä",
"YES": "Kyllä"

View File

@ -56,6 +56,7 @@
"THEMES": "Thèmes",
"THEME_LEVEL": "Niveau de Thème",
"THEME_OPTIONS": "Options de Thème",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Non autorisée : vous devez dabord vous connecter pour effectuer cette action",
"USER_NAME": "Nom dUtilisateur",
"USER_INTERFACE": "Interface Utilisateur",
"YES": "Oui"

View File

@ -56,6 +56,7 @@
"THEMES": "Témák",
"THEME_LEVEL": "Téma Szintje",
"THEME_OPTIONS": "Téma Beállítások",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Jogosulatlan: a művelet végrehajtásához először be kell jelentkeznie",
"USER_NAME": "Felhasználónév",
"USER_INTERFACE": "Felhasználói Felület",
"YES": "Igen"

View File

@ -56,6 +56,7 @@
"THEMES": "Tema",
"THEME_LEVEL": "Tingkat Tema",
"THEME_OPTIONS": "Opsi Tema",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Tidak sah: Anda harus login terlebih dahulu untuk melakukan tindakan ini",
"USER_NAME": "Nama Pengguna",
"USER_INTERFACE": "Antarmuka Pengguna",
"YES": "Ya"

View File

@ -56,6 +56,7 @@
"THEMES": "Temi",
"THEME_LEVEL": "Livello del Tema",
"THEME_OPTIONS": "Opzioni del Tema",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Non autorizzato: è necessario effettuare prima l'accesso per eseguire questa azione",
"USER_NAME": "Nome Utente",
"USER_INTERFACE": "Interfaccia Utente",
"YES": "Sì"

View File

@ -56,6 +56,7 @@
"THEMES": "Temi",
"THEME_LEVEL": "Livello del Tema",
"THEME_OPTIONS": "Opzioni del Tema",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Non autorizzato: è necessario effettuare prima l'accesso per eseguire questa azione",
"USER_NAME": "Nome Utente",
"USER_INTERFACE": "Interfaccia Utente",
"YES": "Sì"

View File

@ -56,6 +56,7 @@
"THEMES": "Temi",
"THEME_LEVEL": "Livello del Tema",
"THEME_OPTIONS": "Opzioni del Tema",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Non autorizzato: è necessario effettuare prima l'accesso per eseguire questa azione",
"USER_NAME": "Nome Utente",
"USER_INTERFACE": "Interfaccia Utente",
"YES": "Sì"

View File

@ -56,6 +56,7 @@
"THEMES": "テーマ",
"THEME_LEVEL": "テーマレベル",
"THEME_OPTIONS": "テーマオプション",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Unauthorized:このアクションを実行するには、最初にログインする必要があります",
"USER_NAME": "ユーザー名",
"USER_INTERFACE": "ユーザーインターフェース",
"YES": "はい"

View File

@ -56,6 +56,7 @@
"THEMES": "테마",
"THEME_LEVEL": "테마 수준",
"THEME_OPTIONS": "테마 옵션",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "권한 없음: 이 작업을 수행하려면 먼저 로그인해야 합니다",
"USER_NAME": "사용자 이름",
"USER_INTERFACE": "사용자 인터페이스",
"YES": "예"

View File

@ -56,6 +56,7 @@
"THEMES": "Temos",
"THEME_LEVEL": "Temos Lygis",
"THEME_OPTIONS": "Temos Parinktys",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Neteisėta: pirmiausia turėtumėte prisijungti, kad atliktumėte šį veiksmą",
"USER_NAME": "Vartotojo Vardas",
"USER_INTERFACE": "Vartotojo Sąsaja",
"YES": "Taip"

View File

@ -56,6 +56,7 @@
"THEMES": "Temaer",
"THEME_LEVEL": "Temanivå",
"THEME_OPTIONS": "Temaopsjoner",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Uautorisert: du bør logge inn først for å utføre denne handlingen",
"USER_NAME": "Brukernavn",
"USER_INTERFACE": "Brukergrensesnitt",
"YES": "Ja"

View File

@ -56,6 +56,7 @@
"THEMES": "Thema's",
"THEME_LEVEL": "Thema Niveau",
"THEME_OPTIONS": "Thema Opties",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Ongeautoriseerd: u moet eerst inloggen om deze actie uit te voeren",
"USER_NAME": "Gebruikersnaam",
"USER_INTERFACE": "Gebruikersinterface",
"YES": "Ja"

View File

@ -56,6 +56,7 @@
"THEMES": "Motywy",
"THEME_LEVEL": "Poziom Motywu",
"THEME_OPTIONS": "Opcje Motywu",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Nieautoryzowany: należy się najpierw zalogować, aby wykonać tę czynność",
"USER_NAME": "Nazwa Użytkownika",
"USER_INTERFACE": "Interfejs Użytkownika",
"YES": "Tak"

View File

@ -56,6 +56,7 @@
"THEMES": "Temas",
"THEME_LEVEL": "Nível de Tema",
"THEME_OPTIONS": "Opções de Tema",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Não autorizado: você deve fazer login primeiro para executar esta ação",
"USER_NAME": "Nome de Usuário",
"USER_INTERFACE": "Interface do Usuário",
"YES": "Sim"

View File

@ -56,6 +56,7 @@
"THEMES": "Teme",
"THEME_LEVEL": "Nivel Temă",
"THEME_OPTIONS": "Opțiuni Temă",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Neautorizat: trebuie să vă conectați mai întâi pentru a efectua această acțiune",
"USER_NAME": "Nume Utilizator",
"USER_INTERFACE": "Interfață Utilizator",
"YES": "Da"

View File

@ -56,6 +56,7 @@
"THEMES": "Темы",
"THEME_LEVEL": "Уровень темы",
"THEME_OPTIONS": "Варианты темы",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Неавторизованно: для выполнения этого действия необходимо сначала войти в систему",
"USER_NAME": "Имя пользователя",
"USER_INTERFACE": "Пользовательский интерфейс",
"YES": "Да"

View File

@ -56,6 +56,7 @@
"THEMES": "Témy",
"THEME_LEVEL": "Úroveň Témy",
"THEME_OPTIONS": "Možnosti Témy",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Neoprávnené: na vykonanie tejto akcie by ste sa mali najprv prihlásiť",
"USER_NAME": "Meno Používateľa",
"USER_INTERFACE": "Používateľské Rozhranie",
"YES": "Áno"

View File

@ -56,6 +56,7 @@
"THEMES": "Teme",
"THEME_LEVEL": "Raven Teme",
"THEME_OPTIONS": "Možnosti Teme",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Nepooblaščeno: za izvedbo tega dejanja se morate najprej prijaviti",
"USER_NAME": "Uporabniško Ime",
"USER_INTERFACE": "Uporabniški Vmesnik",
"YES": "Da"

View File

@ -56,6 +56,7 @@
"THEMES": "Teman",
"THEME_LEVEL": "Temanivå",
"THEME_OPTIONS": "Temaval",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Obehörig: du bör logga in först för att utföra den här åtgärden",
"USER_NAME": "Användarnamn",
"USER_INTERFACE": "Användargränssnitt",
"YES": "Ja"

View File

@ -56,6 +56,7 @@
"THEMES": "தீம்கள்",
"THEME_LEVEL": "தீம் நிலை",
"THEME_OPTIONS": "தீம் விருப்பங்கள்",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "அங்கீகரிக்கப்படாதது: இந்தச் செயலைச் செய்ய நீங்கள் முதலில் உள்நுழைய வேண்டும்",
"USER_NAME": "பயனர் பெயர்",
"USER_INTERFACE": "பயனர் இடைமுகம்",
"YES": "ஆம்"

View File

@ -56,6 +56,7 @@
"THEMES": "ธีม",
"THEME_LEVEL": "ระดับธีม",
"THEME_OPTIONS": "ตัวเลือกธีม",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "ไม่ได้รับอนุญาต: คุณควรเข้าสู่ระบบก่อนเพื่อดําเนินการนี้",
"USER_NAME": "ชื่อผู้ใช้",
"USER_INTERFACE": "ส่วนติดต่อผู้ใช้",
"YES": "ใช่"

View File

@ -56,6 +56,7 @@
"THEMES": "Chủ đề",
"THEME_LEVEL": "Cấp chủ đề",
"THEME_OPTIONS": "Tùy chọn chủ đề",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "Không được phép: bạn nên đăng nhập trước để thực hiện hành động này",
"USER_NAME": "Tên người dùn",
"USER_INTERFACE": "Giao diện người dùng",
"YES": "Có"

View File

@ -56,6 +56,7 @@
"THEMES": "主题",
"THEME_LEVEL": "主题级别",
"THEME_OPTIONS": "主题选项",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "未经授权:您应先登录才能执行此操作",
"USER_NAME": "用户名",
"USER_INTERFACE": "用户界面",
"YES": "是的"

View File

@ -56,6 +56,7 @@
"THEMES": "主題",
"THEME_LEVEL": "主題層級",
"THEME_OPTIONS": "主題選項",
"UNAUTHORIZED_YOU_SHOULD_LOGIN_FIRST_TO_PERFORM_THIS_ACTION": "未經授權:您應先登錄才能執行此操作",
"USER_NAME": "使用者名稱",
"USER_INTERFACE": "使用者介面",
"YES": "是的"

665
yarn.lock

File diff suppressed because it is too large Load Diff