This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch add-option-to-manually-change-language in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 101a3aa214779ec7fdd7efd40efce1ce7fd8cbb4 Author: Dominik Riemer <[email protected]> AuthorDate: Thu Oct 23 20:49:21 2025 +0200 feat: Add option to manually change language --- .../streampipes/model/client/user/UserAccount.java | 9 +++++ .../org/apache/streampipes/model/UserInfo.java | 9 +++++ .../user/management/util/UserInfoUtil.java | 1 + ui/deployment/i18n/de.json | 29 +++++++++++++- ui/deployment/i18n/en.json | 29 +++++++++++++- .../src/lib/model/gen/streampipes-model-client.ts | 7 +++- .../src/lib/model/gen/streampipes-model.ts | 5 ++- .../streampipes/streampipes.component.ts | 11 +++++- .../profile/components/basic-profile-settings.ts | 12 +++++- .../general-profile-settings.component.html | 40 ++++++++++++------- .../general/general-profile-settings.component.ts | 7 ++++ .../token/token-management-settings.component.html | 46 ++++++++++++++-------- ui/src/app/profile/profile.component.html | 8 ++-- ui/src/app/profile/profile.module.ts | 4 ++ 14 files changed, 173 insertions(+), 44 deletions(-) diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java index 9b1dbcdf44..2fb2379588 100644 --- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java +++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java @@ -41,6 +41,7 @@ public class UserAccount extends Principal { protected boolean hideTutorial; protected boolean darkMode = false; protected boolean hasAcknowledged = false; + protected String language; protected long createdAtMillis; protected long lastLoginAtMillis; @@ -210,4 +211,12 @@ public class UserAccount extends Principal { public void setLastLoginAtMillis(long lastLoginAtMillis) { this.lastLoginAtMillis = lastLoginAtMillis; } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } } diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/UserInfo.java b/streampipes-model/src/main/java/org/apache/streampipes/model/UserInfo.java index b2298160cc..433c3215f8 100644 --- a/streampipes-model/src/main/java/org/apache/streampipes/model/UserInfo.java +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/UserInfo.java @@ -31,6 +31,7 @@ public class UserInfo { private boolean showTutorial; private boolean darkMode; private boolean hasAcknowledged; + private String language; public UserInfo() { } @@ -82,4 +83,12 @@ public class UserInfo { public void setHasAcknowledged(boolean hasAcknowledged) { this.hasAcknowledged = hasAcknowledged; } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } } diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java index b06bae827f..e89ae58afa 100644 --- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java +++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java @@ -38,6 +38,7 @@ public class UserInfoUtil { UserInfo userInfo = prepareUserInfo(userAccount, roles); userInfo.setShowTutorial(!userAccount.isHideTutorial()); userInfo.setHasAcknowledged(userAccount.isHasAcknowledged()); + userInfo.setLanguage(userAccount.getLanguage()); return userInfo; } diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json index 49ca5da77b..feb5828fb9 100644 --- a/ui/deployment/i18n/de.json +++ b/ui/deployment/i18n/de.json @@ -1,4 +1,27 @@ { + "General Settings": "Allgemeine Einstellungen", + "API Keys": "API-Schlüssel", + "Manage your API keys for third-party application access.": "API-Schlüssel für den Zugriff von Drittanbieter-Anwendungen verwalten", + "New API key": "Neuer API-Schlüssel", + "Name": "Name", + "Token name must be at least 3 characters long and contain only alphanumeric characters, hyphens, or underscores.": "Der Token-Name muss mindestens 3 Zeichen lang sein und darf nur alphanumerische Zeichen, Bindestriche oder Unterstriche enthalten.", + "Create new API key": "Neuen API-Schlüssel erstellen", + "Key created": "Schlüssel erstellt", + "Existing API keys": "Vorhandene API-Schlüssel", + "API Docs": "API-Doku", + "View the documentation of the API. Here you can see all provided endpoints and how to query them.": "Anzeigen der API-Dokumentation. Bereitgestellte Endpunkte und Funktionen einsehen.", + "View API Docs": "API-Doku anzeigen", + "Profile": "Profil", + "Manage your basic profile settings here.": "Grundlegenden Profileinstellungen verwalten.", + "Change email": "E-Mail ändern", + "Full name": "Vor- und Nachname", + "Update profile": "Profil aktualisieren", + "Change password": "Passwort ändern", + "Appearance": "Darstellung", + "Change the look and feel of your installation": "Darstellung der Anwendung ändern", + "Light mode": "Hell", + "Dark mode": "Dunkel", + "Save color schema": "Farbschema speichern", "New Pipeline": "Neue Pipeline", "Start All Pipelines": "Alle Pipelines starten", "Stop all pipelines": "Alle Pipelines stoppen", @@ -30,12 +53,12 @@ "Start": "Start", "Start pipeline": "Pipeline starten", "Stop pipeline": "Pipeline stoppen", - "Name": "Name", "Last modified": "Zuletzt geändert", "Show": "Anzeigen", "Edit": "Bearbeiten", "Manage permissions": "Berechtigungen verwalten", "Delete": "Löschen", + "The desired pipeline was not found!": "Die gewünschte Pipeline wurde nicht gefunden", "no log messages available": "keine Logs verfügbar", "Data Preview": "Datenvorschau", "Enable live preview": "Live-Vorschau aktivieren", @@ -220,13 +243,13 @@ "Edit chart": "Diagramm bearbeiten", "Clone chart": "Diagramm kopieren", "Delete chart": "Diagramm löschen", + "The desired chart was not found!": null, "Chart Name": "Diagrammname", "Add to Asset": "Zu Asset hinzufügen", "Add To Asset": "Zu Asset hinzufügen", "Discard": "Verwerfen", "Data": "Daten", "Visualization": "Visualisierung", - "Appearance": "Darstellung", "Back": "Zurück", "Create": "Erstellen", "Chart Type": "Diagrammtyp", @@ -290,6 +313,7 @@ "Deep clone (also clone widgets)": "Tiefes Klonen (auch Widgets klonen)", "Modify chart configurations": "Ändern von Diagrammkonfigurationen", "Clone": "Klonen", + "The desired dashboard was not found!": null, "This dashboard is empty and doesn't contain any charts.": "Dieses Dashboard ist leer und hat keine Diagramm zum Anzeigen.", "View mode": "Ansicht", "Slides": "Folien", @@ -366,6 +390,7 @@ "Create adapter": "Adapter erstellen", "Docs": "Doku", "Refresh": "Neu laden", + "The desired adapter was not found!": null, "Last published message": "Zuletzt veröffentlichte Nachricht", "Published messages": "Nachrichten", "Adapter running": "Adapter läuft", diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json index 3227983e13..3416797bfc 100644 --- a/ui/deployment/i18n/en.json +++ b/ui/deployment/i18n/en.json @@ -1,4 +1,27 @@ { + "General Settings": null, + "API Keys": null, + "Manage your API keys for third-party application access.": null, + "New API key": null, + "Name": null, + "Token name must be at least 3 characters long and contain only alphanumeric characters, hyphens, or underscores.": null, + "Create new API key": null, + "Key created": null, + "Existing API keys": null, + "API Docs": null, + "View the documentation of the API. Here you can see all provided endpoints and how to query them.": null, + "View API Docs": null, + "Profile": null, + "Manage your basic profile settings here.": null, + "Change email": null, + "Full name": null, + "Update profile": null, + "Change password": null, + "Appearance": null, + "Change the look and feel of your installation": null, + "Light mode": null, + "Dark mode": null, + "Save color schema": null, "New Pipeline": null, "Start All Pipelines": null, "Stop all pipelines": null, @@ -30,12 +53,12 @@ "Start": null, "Start pipeline": null, "Stop pipeline": null, - "Name": null, "Last modified": null, "Show": null, "Edit": null, "Manage permissions": null, "Delete": null, + "The desired pipeline was not found!": null, "no log messages available": null, "Data Preview": null, "Enable live preview": null, @@ -220,13 +243,13 @@ "Edit chart": null, "Clone chart": null, "Delete chart": null, + "The desired chart was not found!": null, "Chart Name": null, "Add to Asset": null, "Add To Asset": null, "Discard": null, "Data": null, "Visualization": null, - "Appearance": null, "Back": null, "Create": null, "Chart Type": null, @@ -290,6 +313,7 @@ "Deep clone (also clone widgets)": null, "Modify chart configurations": null, "Clone": null, + "The desired dashboard was not found!": null, "This dashboard is empty and doesn't contain any charts.": null, "View mode": null, "Slides": null, @@ -366,6 +390,7 @@ "Create adapter": null, "Docs": null, "Refresh": null, + "The desired adapter was not found!": null, "Last published message": null, "Published messages": null, "Adapter running": null, diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts index 55adefc997..36eacaf170 100644 --- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts +++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + /* tslint:disable */ /* eslint-disable */ // @ts-nocheck -// Generated using typescript-generator version 3.2.1263 on 2025-10-09 16:21:15. +// Generated using typescript-generator version 3.2.1263 on 2025-10-23 20:03:54. -import { Storable } from './platform-services'; +import { Storable } from './streampipes-model'; export class Group implements Storable { alternateIds: string[]; @@ -246,6 +247,7 @@ export class UserAccount extends Principal { fullName: string; hasAcknowledged: boolean; hideTutorial: boolean; + language: string; lastLoginAtMillis: number; password: string; preferredDataProcessors: string[]; @@ -266,6 +268,7 @@ export class UserAccount extends Principal { instance.fullName = data.fullName; instance.hasAcknowledged = data.hasAcknowledged; instance.hideTutorial = data.hideTutorial; + instance.language = data.language; instance.lastLoginAtMillis = data.lastLoginAtMillis; instance.password = data.password; instance.preferredDataProcessors = __getCopyArrayFn( diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts index ebe29936c7..6f9793ff2f 100644 --- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts +++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts @@ -16,10 +16,11 @@ * specific language governing permissions and limitations * under the License. */ + /* tslint:disable */ /* eslint-disable */ // @ts-nocheck -// Generated using typescript-generator version 3.2.1263 on 2025-10-09 16:21:10. +// Generated using typescript-generator version 3.2.1263 on 2025-10-23 20:03:42. export class NamedStreamPipesEntity implements Storable { '@class': @@ -4209,6 +4210,7 @@ export class UserInfo { darkMode: boolean; displayName: string; hasAcknowledged: boolean; + language: string; roles: string[]; showTutorial: boolean; username: string; @@ -4221,6 +4223,7 @@ export class UserInfo { instance.darkMode = data.darkMode; instance.displayName = data.displayName; instance.hasAcknowledged = data.hasAcknowledged; + instance.language = data.language; instance.roles = __getCopyArrayFn(__identity<string>())(data.roles); instance.showTutorial = data.showTutorial; instance.username = data.username; diff --git a/ui/src/app/core/components/streampipes/streampipes.component.ts b/ui/src/app/core/components/streampipes/streampipes.component.ts index 114a17666a..e4d96b3580 100644 --- a/ui/src/app/core/components/streampipes/streampipes.component.ts +++ b/ui/src/app/core/components/streampipes/streampipes.component.ts @@ -16,10 +16,11 @@ * */ -import { Component, OnInit } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; import { AuthService } from '../../../services/auth.service'; import { animate, style, transition, trigger } from '@angular/animations'; import { CurrentUserService } from '@streampipes/shared-ui'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'sp-streampipes', @@ -39,9 +40,17 @@ import { CurrentUserService } from '@streampipes/shared-ui'; export class StreampipesComponent implements OnInit { darkMode: boolean; + private translate = inject(TranslateService); + constructor(public currentUserService: CurrentUserService) {} ngOnInit(): void { this.currentUserService.darkMode$.subscribe(dm => (this.darkMode = dm)); + this.currentUserService.user$.subscribe(user => { + console.log(user); + if (user.language !== 'browser') { + this.translate.use(user.language); + } + }); } } diff --git a/ui/src/app/profile/components/basic-profile-settings.ts b/ui/src/app/profile/components/basic-profile-settings.ts index 7602eae651..fe909237c2 100644 --- a/ui/src/app/profile/components/basic-profile-settings.ts +++ b/ui/src/app/profile/components/basic-profile-settings.ts @@ -18,10 +18,11 @@ import { ProfileService } from '../profile.service'; import { UserAccount } from '@streampipes/platform-services'; -import { Directive } from '@angular/core'; +import { Directive, inject } from '@angular/core'; import { AppConstants } from '../../services/app.constants'; import { AuthService } from '../../services/auth.service'; import { CurrentUserService } from '@streampipes/shared-ui'; +import { TranslateService } from '@ngx-translate/core'; @Directive() export abstract class BasicProfileSettings { @@ -30,6 +31,10 @@ export abstract class BasicProfileSettings { profileUpdating = false; errorMessage: string; + selectedLanguage = 'browser'; + + private translate = inject(TranslateService); + constructor( protected profileService: ProfileService, public appConstants: AppConstants, @@ -41,6 +46,7 @@ export abstract class BasicProfileSettings { this.profileService .getUserProfile(this.currentUserService.user$.getValue().username) .subscribe(userData => { + userData.language ??= 'browser'; this.userData = userData; this.onUserDataReceived(); this.profileLoaded = true; @@ -49,6 +55,10 @@ export abstract class BasicProfileSettings { saveProfileSettings() { this.profileUpdating = true; + if (this.selectedLanguage !== this.userData.language) { + this.userData.language = this.selectedLanguage; + this.translate.use(this.selectedLanguage); + } this.profileService .updateUserProfile(this.userData) .subscribe(response => { diff --git a/ui/src/app/profile/components/general/general-profile-settings.component.html b/ui/src/app/profile/components/general/general-profile-settings.component.html index 09a1be7021..c38ea2999f 100644 --- a/ui/src/app/profile/components/general/general-profile-settings.component.html +++ b/ui/src/app/profile/components/general/general-profile-settings.component.html @@ -24,11 +24,11 @@ *ngIf="profileLoaded" > <sp-split-section - title="Main Settings" - subtitle="Manage your basic profile settings here." + [title]="'Profile' | translate" + [subtitle]="'Manage your basic profile settings here.' | translate" > <sp-warning-box *ngIf="isExternalUser"> - Settings for externally-managed users can't be changed. + {{ 'Settings for externally-managed users can't be changed.' | translate }} </sp-warning-box> <div fxLayout="row" fxLayoutAlign="start center"> <span>{{ userData.username }}</span> @@ -40,26 +40,36 @@ class="ml-15" (click)="openChangeEmailDialog()" > - Change email + {{ 'Change email' | translate }} </button> </div> <mat-form-field fxFlex color="accent" class="mt-10 mb-10"> - <mat-label>Full Name</mat-label> + <mat-label>{{ 'Full name' | translate }}</mat-label> <input [disabled]="isExternalUser" [(ngModel)]="userData.fullName" matInput /> </mat-form-field> + <mat-form-field fxFlex color="accent" class="mt-10 mb-10"> + <mat-label>Language</mat-label> + <mat-select [(ngModel)]="selectedLanguage"> + <mat-option + *ngFor="let lang of availableLanguages" + [value]="lang.id" + > + {{ lang.label }} + </mat-option> + </mat-select> + </mat-form-field> <div fxLayout="row" fxLayoutAlign="start center"> <button - [disabled]="isExternalUser" mat-button mat-flat-button color="accent" (click)="saveProfileSettings()" > - Update profile + {{ 'Update profile' | translate }} </button> <button [disabled]="isExternalUser" @@ -69,17 +79,17 @@ class="ml-15" (click)="openChangePasswordDialog()" > - Change password + {{ 'Change password' | translate }} </button> </div> </sp-split-section> <mat-divider></mat-divider> <div fxLayout="row" fxFlex="100" class="mt-30"> <sp-split-section - title="Appearance" - subtitle="Change the appearance of your {{ - appConstants.APP_NAME - }} installation" + [title]="'Appearance' | translate" + [subtitle]=" + 'Change the look and feel of your installation' | translate + " > <label id="radio-group-label">Color Scheme</label> <mat-radio-group @@ -91,12 +101,12 @@ <mat-radio-button [value]="false" class="appearance-radio-button" - >Light Mode</mat-radio-button + >{{ 'Light mode' | translate }}</mat-radio-button > <mat-radio-button [value]="true" class="appearance-radio-button" - >Dark Mode</mat-radio-button + >{{ 'Dark mode' | translate }}</mat-radio-button > </mat-radio-group> <div> @@ -106,7 +116,7 @@ color="accent" (click)="updateAppearanceMode()" > - Save color scheme + {{ 'Save color schema' | translate }} </button> </div> </sp-split-section> diff --git a/ui/src/app/profile/components/general/general-profile-settings.component.ts b/ui/src/app/profile/components/general/general-profile-settings.component.ts index 8ad7d49766..39c863bbdf 100644 --- a/ui/src/app/profile/components/general/general-profile-settings.component.ts +++ b/ui/src/app/profile/components/general/general-profile-settings.component.ts @@ -46,6 +46,12 @@ export class GeneralProfileSettingsComponent darkModeChanged = false; isExternalUser = false; + availableLanguages: { label: string; id: string }[] = [ + { label: 'Browser language', id: 'browser' }, + { label: 'English', id: 'en' }, + { label: 'Deutsch', id: 'de' }, + ]; + constructor( authService: AuthService, profileService: ProfileService, @@ -75,6 +81,7 @@ export class GeneralProfileSettingsComponent } onUserDataReceived() { + this.selectedLanguage = this.userData.language; this.originalDarkMode = this.userData.darkMode; this.currentUserService.darkMode$.next(this.userData.darkMode); this.isExternalUser = this.userData.provider !== 'local'; diff --git a/ui/src/app/profile/components/token/token-management-settings.component.html b/ui/src/app/profile/components/token/token-management-settings.component.html index dceb956d4b..d050fea41b 100644 --- a/ui/src/app/profile/components/token/token-management-settings.component.html +++ b/ui/src/app/profile/components/token/token-management-settings.component.html @@ -24,14 +24,18 @@ *ngIf="profileLoaded" > <sp-split-section - title="API Keys" - subtitle="Manage your API keys for third-party application access to - {{ appConstants.APP_NAME }}" + [title]="'API Keys' | translate" + [subtitle]=" + 'Manage your API keys for third-party application access.' + | translate + " > <div fxLayout="column" class="subsection"> - <div class="subsection-title">New API key</div> + <div class="subsection-title"> + {{ 'New API key' | translate }} + </div> <mat-form-field fxFlex color="accent"> - <mat-label>Name</mat-label> + <mat-label>{{ 'Name' | translate }}</mat-label> <input [(ngModel)]="newTokenName" matInput @@ -48,9 +52,10 @@ tokenNameFormControl.hasError('pattern') " > - Token name must be at least 3 characters long and - contain only alphanumeric characters, hyphens, or - underscores. + {{ + 'Token name must be at least 3 characters long and contain only alphanumeric characters, hyphens, or underscores.' + | translate + }} </mat-error> </mat-form-field> <div> @@ -62,7 +67,7 @@ [disabled]="!tokenNameFormControl.valid" (click)="requestNewKey()" > - Create new API key + {{ 'Create new API key' | translate }} </button> </div> </div> @@ -71,10 +76,12 @@ class="subsection mt-10" *ngIf="newTokenCreated" > - <div class="subsection-title">Key created</div> + <div class="subsection-title"> + {{ 'Key created' | translate }} + </div> <div class="subsection-small"> - Your new API key has been created. Please copy the key now - - you won't be able to see the key again. + {{ 'Your new API key has been created. Please copy the key now - + you won't be able to see the key again.' | translate }} </div> <div fxFlex="100" @@ -100,9 +107,11 @@ </div> <mat-divider class="divider"></mat-divider> <div fxLayout="column" class="subsection mt-10"> - <div class="subsection-title">Existing API keys</div> + <div class="subsection-title"> + {{ 'Existing API keys' | translate }} + </div> <div *ngIf="userData.userApiTokens.length === 0"> - (no keys available) + ({{ 'no keys available | translate' }}) </div> <table mat-table @@ -144,8 +153,11 @@ </sp-split-section> <mat-divider></mat-divider> <sp-split-section - title="API Docs" - subtitle="View the documentation of the StreamPipes API. Here you can see all provided endpoints and how to query them." + [title]="'API Docs' | translate" + [subtitle]=" + 'View the documentation of the API. Here you can see all provided endpoints and how to query them.' + | translate + " > <div fxLayout="column" class="subsection"> <div fxLayout="column" class="subsection mt-8"></div> @@ -156,7 +168,7 @@ color="accent" routerLink="/apidocs" > - View API Docs + {{ 'View API Docs' | translate }} </button> </div> </div> diff --git a/ui/src/app/profile/profile.component.html b/ui/src/app/profile/profile.component.html index 293e26fa1b..0db10fb3e9 100644 --- a/ui/src/app/profile/profile.component.html +++ b/ui/src/app/profile/profile.component.html @@ -26,7 +26,9 @@ (selectedIndexChange)="selectedIndexChange($event)" color="accent" > - <mat-tab label="General Settings"></mat-tab> + <mat-tab + [label]="'General Settings' | translate" + ></mat-tab> <mat-tab label="API"></mat-tab> </mat-tab-group> </div> @@ -40,7 +42,7 @@ fxFlex="100" > <div - class="fixed-height page-container-padding-inner" + class="fixed-height" fxLayout="column" fxFlex="100" *ngIf="selectedIndex === 0" @@ -50,7 +52,7 @@ ></sp-general-profile-settings> </div> <div - class="fixed-height page-container-padding-inner" + class="fixed-height" fxLayout="column" fxFlex="100" *ngIf="selectedIndex === 1" diff --git a/ui/src/app/profile/profile.module.ts b/ui/src/app/profile/profile.module.ts index 3177eaa62d..79cc3bd46b 100644 --- a/ui/src/app/profile/profile.module.ts +++ b/ui/src/app/profile/profile.module.ts @@ -36,6 +36,8 @@ import { SharedUiModule } from '@streampipes/shared-ui'; import { MatInputModule } from '@angular/material/input'; import { MatRadioModule } from '@angular/material/radio'; import { MatTableModule } from '@angular/material/table'; +import { MatSelectModule } from '@angular/material/select'; +import { TranslatePipe } from '@ngx-translate/core'; @NgModule({ imports: [ @@ -54,6 +56,8 @@ import { MatTableModule } from '@angular/material/table'; PlatformServicesModule, SharedUiModule, RouterLink, + MatSelectModule, + TranslatePipe, ], declarations: [ ChangeEmailDialogComponent,
