From 6726863081f148bc42ff1ff5b188198d79d61e8b Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 27 Jul 2026 10:15:27 +0200 Subject: [PATCH] feat(settings): secure playlist backup credentials --- ...s-backup-credentials-dialog.component.html | 105 ++++++++++++++++ ...s-backup-credentials-dialog.component.scss | 55 ++++++++ ...ackup-credentials-dialog.component.spec.ts | 118 ++++++++++++++++++ ...ngs-backup-credentials-dialog.component.ts | 70 +++++++++++ .../settings-backup-section.component.html | 31 +++++ .../settings-backup-section.component.ts | 27 +++- .../app/settings/settings-backup.facade.ts | 50 +++++++- .../app/settings/settings.component.spec.ts | 118 ++++++++++++++++++ apps/web/src/assets/i18n/ar.json | 16 +++ apps/web/src/assets/i18n/ary.json | 16 +++ apps/web/src/assets/i18n/by.json | 16 +++ apps/web/src/assets/i18n/de.json | 16 +++ apps/web/src/assets/i18n/el.json | 16 +++ apps/web/src/assets/i18n/en.json | 16 +++ apps/web/src/assets/i18n/es.json | 16 +++ apps/web/src/assets/i18n/fr.json | 16 +++ apps/web/src/assets/i18n/hu.json | 16 +++ apps/web/src/assets/i18n/it.json | 16 +++ apps/web/src/assets/i18n/ja.json | 16 +++ apps/web/src/assets/i18n/ko.json | 16 +++ apps/web/src/assets/i18n/nl.json | 16 +++ apps/web/src/assets/i18n/pl.json | 16 +++ apps/web/src/assets/i18n/pt.json | 16 +++ apps/web/src/assets/i18n/ru.json | 16 +++ apps/web/src/assets/i18n/tr.json | 16 +++ apps/web/src/assets/i18n/zh.json | 16 +++ apps/web/src/assets/i18n/zhtw.json | 16 +++ 27 files changed, 874 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/app/settings/settings-backup-credentials-dialog.component.html create mode 100644 apps/web/src/app/settings/settings-backup-credentials-dialog.component.scss create mode 100644 apps/web/src/app/settings/settings-backup-credentials-dialog.component.spec.ts create mode 100644 apps/web/src/app/settings/settings-backup-credentials-dialog.component.ts diff --git a/apps/web/src/app/settings/settings-backup-credentials-dialog.component.html b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.html new file mode 100644 index 000000000..0031037d3 --- /dev/null +++ b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.html @@ -0,0 +1,105 @@ +

+ {{ 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.TITLE' | translate }} +

+ +
+ +

+ {{ 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.MESSAGE' | translate }} +

+ +
+
+
+ {{ + 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.PLAYLIST' + | translate + }} +
+
{{ dialogData.playlistTitle }}
+
+ @if (dialogData.serverHost) { +
+
+ {{ + 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.SERVER' + | translate + }} +
+
{{ dialogData.serverHost }}
+
+ } +
+ +
+ + + {{ + 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.USERNAME' + | translate + }} + + + @if ( + credentialsForm.controls.username.invalid && + credentialsForm.controls.username.touched + ) { + + {{ + 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.USERNAME_REQUIRED' + | translate + }} + + } + + + + + {{ + 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.PASSWORD' + | translate + }} + + + @if ( + credentialsForm.controls.password.invalid && + credentialsForm.controls.password.touched + ) { + + {{ + 'SETTINGS.BACKUP_CREDENTIALS_DIALOG.PASSWORD_REQUIRED' + | translate + }} + + } + +
+
+ + + + + +
diff --git a/apps/web/src/app/settings/settings-backup-credentials-dialog.component.scss b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.scss new file mode 100644 index 000000000..cbb6d519b --- /dev/null +++ b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.scss @@ -0,0 +1,55 @@ +:host { + display: block; +} + +.settings-backup-credentials-dialog { + min-width: min(100%, 380px); +} + +.settings-backup-credentials-dialog__lead { + margin: 0; + line-height: 1.55; + text-wrap: pretty; +} + +.settings-backup-credentials-dialog__context { + display: grid; + gap: 8px; + margin: 18px 0; + padding: 12px 14px; + border: 1px solid + color-mix(in srgb, var(--mat-sys-outline) 42%, transparent); + border-radius: 14px; + background: color-mix( + in srgb, + var(--mat-sys-surface-container) 84%, + transparent + ); + + div { + display: grid; + grid-template-columns: minmax(72px, auto) minmax(0, 1fr); + gap: 12px; + } + + dt { + color: var(--mat-sys-on-surface-variant); + font-size: 0.8rem; + font-weight: 700; + } + + dd { + min-width: 0; + margin: 0; + overflow-wrap: anywhere; + } +} + +.settings-backup-credentials-dialog__fields { + display: grid; + gap: 4px; +} + +.settings-backup-credentials-dialog__actions { + padding-top: 2px; +} diff --git a/apps/web/src/app/settings/settings-backup-credentials-dialog.component.spec.ts b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.spec.ts new file mode 100644 index 000000000..450eb7e04 --- /dev/null +++ b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.spec.ts @@ -0,0 +1,118 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { + SettingsBackupCredentialsDialogComponent, + SettingsBackupCredentialsDialogData, +} from './settings-backup-credentials-dialog.component'; + +describe('SettingsBackupCredentialsDialogComponent', () => { + let component: SettingsBackupCredentialsDialogComponent; + let fixture: ComponentFixture; + let dialogRef: { close: jest.Mock }; + + beforeEach(async () => { + dialogRef = { + close: jest.fn(), + }; + + await TestBed.configureTestingModule({ + imports: [ + SettingsBackupCredentialsDialogComponent, + NoopAnimationsModule, + TranslateModule.forRoot(), + ], + providers: [ + { + provide: MAT_DIALOG_DATA, + useValue: { + playlistTitle: 'Living room', + serverHost: 'provider.example:8443', + } satisfies SettingsBackupCredentialsDialogData, + }, + { + provide: MatDialogRef, + useValue: dialogRef, + }, + ], + }).compileComponents(); + + const translate = TestBed.inject(TranslateService); + translate.setTranslation( + 'en', + { + SETTINGS: { + BACKUP_CREDENTIALS_DIALOG: { + TITLE: 'Xtream credentials required', + MESSAGE: + 'This redacted backup does not contain the credentials for this playlist.', + PLAYLIST: 'Playlist', + SERVER: 'Server', + USERNAME: 'Username', + PASSWORD: 'Password', + USERNAME_REQUIRED: 'Enter a username.', + PASSWORD_REQUIRED: 'Enter a password.', + SKIP: 'Skip playlist', + IMPORT: 'Import playlist', + }, + }, + }, + true + ); + translate.use('en'); + + fixture = TestBed.createComponent( + SettingsBackupCredentialsDialogComponent + ); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('identifies the redacted playlist without exposing a full source URL', () => { + const text = (fixture.nativeElement as HTMLElement).textContent ?? ''; + + expect(text).toContain('Xtream credentials required'); + expect(text).toContain('Living room'); + expect(text).toContain('provider.example:8443'); + }); + + it('marks blank credentials as invalid and keeps the dialog open', () => { + component.credentialsForm.setValue({ + username: ' ', + password: ' ', + }); + + component.submit(); + fixture.detectChanges(); + + expect(component.credentialsForm.invalid).toBe(true); + expect(component.credentialsForm.controls.username.touched).toBe(true); + expect(component.credentialsForm.controls.password.touched).toBe(true); + expect(dialogRef.close).not.toHaveBeenCalled(); + + const text = (fixture.nativeElement as HTMLElement).textContent ?? ''; + expect(text).toContain('Enter a username.'); + expect(text).toContain('Enter a password.'); + }); + + it('returns a trimmed username while preserving the password exactly', () => { + component.credentialsForm.setValue({ + username: ' viewer ', + password: ' secret with spaces ', + }); + + component.submit(); + + expect(dialogRef.close).toHaveBeenCalledWith({ + username: 'viewer', + password: ' secret with spaces ', + }); + }); + + it('returns null when the playlist is skipped', () => { + component.skip(); + + expect(dialogRef.close).toHaveBeenCalledWith(null); + }); +}); diff --git a/apps/web/src/app/settings/settings-backup-credentials-dialog.component.ts b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.ts new file mode 100644 index 000000000..e2f3a6759 --- /dev/null +++ b/apps/web/src/app/settings/settings-backup-credentials-dialog.component.ts @@ -0,0 +1,70 @@ +import { Component, inject } from '@angular/core'; +import { + NonNullableFormBuilder, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { PlaylistBackupXtreamCredentials } from '@iptvnator/services'; +import { TranslateModule } from '@ngx-translate/core'; + +export interface SettingsBackupCredentialsDialogData { + playlistTitle: string; + serverHost: string; +} + +@Component({ + selector: 'app-settings-backup-credentials-dialog', + templateUrl: './settings-backup-credentials-dialog.component.html', + styleUrls: ['./settings-backup-credentials-dialog.component.scss'], + imports: [ + MatButtonModule, + MatDialogModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + TranslateModule, + ], +}) +export class SettingsBackupCredentialsDialogComponent { + private readonly dialogRef = + inject< + MatDialogRef< + SettingsBackupCredentialsDialogComponent, + PlaylistBackupXtreamCredentials | null + > + >(MatDialogRef); + private readonly formBuilder = inject(NonNullableFormBuilder); + + readonly dialogData = + inject(MAT_DIALOG_DATA); + + readonly credentialsForm = this.formBuilder.group({ + username: ['', [Validators.required, Validators.pattern(/\S/)]], + password: ['', [Validators.required, Validators.pattern(/\S/)]], + }); + + submit(): void { + if (this.credentialsForm.invalid) { + this.credentialsForm.markAllAsTouched(); + return; + } + + const { username, password } = this.credentialsForm.getRawValue(); + this.dialogRef.close({ + username: username.trim(), + password, + }); + } + + skip(): void { + this.dialogRef.close(null); + } +} diff --git a/apps/web/src/app/settings/settings-backup-section.component.html b/apps/web/src/app/settings/settings-backup-section.component.html index 0a9f7189d..8c4bb1cae 100644 --- a/apps/web/src/app/settings/settings-backup-section.component.html +++ b/apps/web/src/app/settings/settings-backup-section.component.html @@ -63,6 +63,37 @@ +
+
+

{{ 'SETTINGS.BACKUP_CONTENTS' | translate }}

+

+ {{ 'SETTINGS.BACKUP_REDACTED_WARNING' | translate }} +

+ @if (includeSecrets()) { +

+ {{ 'SETTINGS.BACKUP_SECRETS_WARNING' | translate }} +

+ } +
+
+ + {{ 'SETTINGS.BACKUP_INCLUDE_SECRETS' | translate }} + +
+
+ @if (isPwa()) {
{{ 'SETTINGS.EPG_NOTE' | translate }} diff --git a/apps/web/src/app/settings/settings-backup-section.component.ts b/apps/web/src/app/settings/settings-backup-section.component.ts index 9cfb5af6c..4db2d5c54 100644 --- a/apps/web/src/app/settings/settings-backup-section.component.ts +++ b/apps/web/src/app/settings/settings-backup-section.component.ts @@ -1,26 +1,49 @@ -import { Component, input, output, ViewEncapsulation } from '@angular/core'; +import { + Component, + inject, + input, + output, + ViewEncapsulation, +} from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { TranslateModule } from '@ngx-translate/core'; +import { SettingsBackupFacade } from './settings-backup.facade'; @Component({ selector: 'app-settings-backup-section', imports: [ MatButtonModule, + MatCheckboxModule, MatIconModule, MatProgressSpinnerModule, TranslateModule, ], templateUrl: './settings-backup-section.component.html', encapsulation: ViewEncapsulation.None, - styles: [':host { display: contents; }'], + styles: [ + ` + :host { + display: contents; + } + + .settings-backup-warning--secrets { + color: var(--mat-sys-error); + font-weight: 600; + } + `, + ], }) export class SettingsBackupSectionComponent { + private readonly backupFacade = inject(SettingsBackupFacade); + readonly activeSection = input.required(); readonly isPwa = input(false); readonly isRemovingAllPlaylists = input(false); readonly isExportingData = input(false); + readonly includeSecrets = this.backupFacade.includeSecrets; readonly importData = output(); readonly exportData = output(); diff --git a/apps/web/src/app/settings/settings-backup.facade.ts b/apps/web/src/app/settings/settings-backup.facade.ts index 6aa377b4c..4c83e7c1a 100644 --- a/apps/web/src/app/settings/settings-backup.facade.ts +++ b/apps/web/src/app/settings/settings-backup.facade.ts @@ -1,12 +1,20 @@ import { inject, Injectable, signal } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { PlaylistActions } from '@iptvnator/m3u-state'; import { PlaylistBackupImportSummary, PlaylistBackupService, + PlaylistBackupXtreamCredentials, + PlaylistBackupXtreamCredentialsRequest, RuntimeCapabilitiesService, } from '@iptvnator/services'; +import { firstValueFrom } from 'rxjs'; +import { + SettingsBackupCredentialsDialogComponent, + SettingsBackupCredentialsDialogData, +} from './settings-backup-credentials-dialog.component'; import { SettingsSnackbarService } from './settings-snackbar.service'; @Injectable() @@ -16,7 +24,9 @@ export class SettingsBackupFacade { private readonly settingsSnackbar = inject(SettingsSnackbarService); private readonly store = inject(Store); private readonly translate = inject(TranslateService); + private readonly matDialog = inject(MatDialog); + readonly includeSecrets = signal(false); readonly isExportingData = signal(false); async exportData(waitForUiFeedbackFrame: () => Promise) { @@ -28,7 +38,9 @@ export class SettingsBackupFacade { await waitForUiFeedbackFrame(); try { - const backup = await this.playlistBackupService.exportBackup(); + const backup = await this.playlistBackupService.exportBackup({ + includeSecrets: this.includeSecrets(), + }); if (this.runtime.supportsDesktopFileSave && window.electron) { const savePath = await window.electron.saveFileDialog( @@ -77,7 +89,11 @@ export class SettingsBackupFacade { try { const summary = await this.playlistBackupService.importBackup( - await file.text() + await file.text(), + { + resolveXtreamCredentials: (request) => + this.resolveXtreamCredentials(request), + } ); if (summary.imported > 0 || summary.merged > 0) { @@ -124,6 +140,36 @@ export class SettingsBackupFacade { window.URL.revokeObjectURL(url); } + private async resolveXtreamCredentials( + request: PlaylistBackupXtreamCredentialsRequest + ): Promise { + const dialogRef = this.matDialog.open< + SettingsBackupCredentialsDialogComponent, + SettingsBackupCredentialsDialogData, + PlaylistBackupXtreamCredentials | null + >(SettingsBackupCredentialsDialogComponent, { + data: { + playlistTitle: request.title, + serverHost: this.getServerHost(request.serverUrl), + }, + maxWidth: 'calc(100vw - 32px)', + width: '440px', + }); + + return (await firstValueFrom(dialogRef.afterClosed())) ?? null; + } + + private getServerHost(serverUrl: string): string { + try { + const normalizedUrl = serverUrl.includes('://') + ? serverUrl + : `https://${serverUrl}`; + return new URL(normalizedUrl).host; + } catch { + return ''; + } + } + private buildBackupImportSummary( summary: PlaylistBackupImportSummary ): string { diff --git a/apps/web/src/app/settings/settings.component.spec.ts b/apps/web/src/app/settings/settings.component.spec.ts index 473326b72..3eb7b8fbd 100644 --- a/apps/web/src/app/settings/settings.component.spec.ts +++ b/apps/web/src/app/settings/settings.component.spec.ts @@ -46,6 +46,8 @@ import { } from '@iptvnator/shared/interfaces'; import { SettingsComponent } from './settings.component'; import { AppUpdateReleaseNotesDialogComponent } from './app-update-release-notes-dialog.component'; +import { SettingsBackupCredentialsDialogComponent } from './settings-backup-credentials-dialog.component'; +import { SettingsBackupSectionComponent } from './settings-backup-section.component'; import { signal } from '@angular/core'; import { SettingsContextService } from '@iptvnator/workspace/shell/util'; @@ -1192,9 +1194,125 @@ describe('SettingsComponent', () => { '/tmp/backup.json', '{}' ); + expect(playlistBackupService.exportBackup).toHaveBeenCalledWith({ + includeSecrets: false, + }); expect(component.isExportingData()).toBe(false); }); + it('includes credentials only after the backup checkbox is enabled', async () => { + const backupSection = fixture.debugElement.query( + By.directive(SettingsBackupSectionComponent) + ).componentInstance as SettingsBackupSectionComponent; + + expect(backupSection.includeSecrets()).toBe(false); + + backupSection.includeSecrets.set(true); + await component.exportData(); + + expect(playlistBackupService.exportBackup).toHaveBeenCalledWith({ + includeSecrets: true, + }); + }); + + it('shows both redacted-backup and secret-export guidance', () => { + const backupSection = fixture.debugElement.query( + By.directive(SettingsBackupSectionComponent) + ); + + expect(backupSection.nativeElement.textContent).toContain( + 'SETTINGS.BACKUP_REDACTED_WARNING' + ); + expect(backupSection.nativeElement.textContent).not.toContain( + 'SETTINGS.BACKUP_SECRETS_WARNING' + ); + + ( + backupSection.componentInstance as SettingsBackupSectionComponent + ).includeSecrets.set(true); + fixture.detectChanges(); + + expect(backupSection.nativeElement.textContent).toContain( + 'SETTINGS.BACKUP_SECRETS_WARNING' + ); + }); + + it('resolves redacted Xtream credentials through the dialog and reports skipped entries', async () => { + const backupJson = JSON.stringify({ + kind: 'iptvnator-playlist-backup', + version: 1, + exportedAt: '2026-04-21T00:00:00.000Z', + includeSecrets: false, + playlists: [], + }); + const file = { + text: jest.fn().mockResolvedValue(backupJson), + } as unknown as File; + const inputClickSpy = jest + .spyOn(HTMLInputElement.prototype, 'click') + .mockImplementation(function (this: HTMLInputElement) { + Object.defineProperty(this, 'files', { + configurable: true, + value: [file], + }); + this.dispatchEvent(new Event('change')); + }); + const dialogOpenSpy = jest + .spyOn(privateApi(component).matDialog, 'open') + .mockReturnValue({ + afterClosed: () => of(undefined), + } as unknown as ReturnType); + ( + playlistBackupService.importBackup as jest.Mock + ).mockImplementationOnce(async (_json, options) => { + const credentials = await options.resolveXtreamCredentials({ + exportedId: 'xtream-redacted', + title: 'Living room', + serverUrl: + 'https://viewer:secret@provider.example:8443/player_api.php?token=private', + }); + + expect(credentials).toBeNull(); + + return { + imported: 0, + merged: 0, + skipped: 1, + failed: 0, + errors: [], + }; + }); + + try { + component.importData(); + await fixture.whenStable(); + + expect(playlistBackupService.importBackup).toHaveBeenCalledWith( + backupJson, + { + resolveXtreamCredentials: expect.any(Function), + } + ); + expect(dialogOpenSpy).toHaveBeenCalledWith( + SettingsBackupCredentialsDialogComponent, + expect.objectContaining({ + data: { + playlistTitle: 'Living room', + serverHost: 'provider.example:8443', + }, + }) + ); + expect(snackBar.open).toHaveBeenCalledWith( + expect.stringContaining('1 skipped'), + undefined, + expect.any(Object) + ); + } finally { + dialogOpenSpy.mockRestore(); + inputClickSpy.mockRestore(); + } + }); + it('falls back to browser backup download when desktop file-save preload is incomplete', async () => { fixture.destroy(); const saveFileDialog = jest.fn().mockResolvedValue('/tmp/backup.json'); diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json index 9c2ef982f..c4678116d 100644 --- a/apps/web/src/assets/i18n/ar.json +++ b/apps/web/src/assets/i18n/ar.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "تصدير", "EXPORTING_DATA": "جارٍ التصدير…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json index 6f8a3ca06..f39bb7ce1 100644 --- a/apps/web/src/assets/i18n/ary.json +++ b/apps/web/src/assets/i18n/ary.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "صدّر", "EXPORTING_DATA": "جاري التصدير…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json index 12ef0bfb5..d8d0a5993 100644 --- a/apps/web/src/assets/i18n/by.json +++ b/apps/web/src/assets/i18n/by.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Экспарт", "EXPORTING_DATA": "Экспартаванне...", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json index 8786a0736..1e879abba 100644 --- a/apps/web/src/assets/i18n/de.json +++ b/apps/web/src/assets/i18n/de.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Export", "EXPORTING_DATA": "Wird exportiert …", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json index 8ca4913ce..2cf4aaaed 100644 --- a/apps/web/src/assets/i18n/el.json +++ b/apps/web/src/assets/i18n/el.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Εξαγωγή", "EXPORTING_DATA": "Εξαγωγή…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index 130b118e9..b938cb374 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Export", "EXPORTING_DATA": "Exporting…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json index 256f10042..16ca28003 100644 --- a/apps/web/src/assets/i18n/es.json +++ b/apps/web/src/assets/i18n/es.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Exportar", "EXPORTING_DATA": "Exportando…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json index cb86e2daa..569c269f6 100644 --- a/apps/web/src/assets/i18n/fr.json +++ b/apps/web/src/assets/i18n/fr.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Exporter", "EXPORTING_DATA": "Exportation…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/hu.json b/apps/web/src/assets/i18n/hu.json index 83183c4a4..5c7991b3a 100644 --- a/apps/web/src/assets/i18n/hu.json +++ b/apps/web/src/assets/i18n/hu.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Exportálás", "EXPORTING_DATA": "Exportálás…", "REMOVE_ALL": "Az összes lejátszási lista és kapcsolódó adat törlése", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Összes törlése", "REMOVE_ALL_DESCRIPTION": "Az alkalmazásban tárolt összes M3U-, Xtream- és Stalker-lejátszási lista törlése.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json index a57ea5146..e46ef744c 100644 --- a/apps/web/src/assets/i18n/it.json +++ b/apps/web/src/assets/i18n/it.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Esporta", "EXPORTING_DATA": "Esportazione...", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json index ccbdfeddb..4a04a6c1f 100644 --- a/apps/web/src/assets/i18n/ja.json +++ b/apps/web/src/assets/i18n/ja.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "エクスポート", "EXPORTING_DATA": "エクスポート中…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json index 1c7351c29..e9d81cf31 100644 --- a/apps/web/src/assets/i18n/ko.json +++ b/apps/web/src/assets/i18n/ko.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "내보내다", "EXPORTING_DATA": "내보내는 중…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json index c28d9c7e3..c2389039e 100644 --- a/apps/web/src/assets/i18n/nl.json +++ b/apps/web/src/assets/i18n/nl.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Exporteren", "EXPORTING_DATA": "Exporteren…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json index 46f5db3c9..174c8e85f 100644 --- a/apps/web/src/assets/i18n/pl.json +++ b/apps/web/src/assets/i18n/pl.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Eksportuj", "EXPORTING_DATA": "Eksportowanie…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json index da02cce09..22e760367 100644 --- a/apps/web/src/assets/i18n/pt.json +++ b/apps/web/src/assets/i18n/pt.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Exportar", "EXPORTING_DATA": "Exportando…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json index c2eb29748..65dba30f2 100644 --- a/apps/web/src/assets/i18n/ru.json +++ b/apps/web/src/assets/i18n/ru.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Экспорт", "EXPORTING_DATA": "Экспорт…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json index 5df212dc2..e25b487e0 100644 --- a/apps/web/src/assets/i18n/tr.json +++ b/apps/web/src/assets/i18n/tr.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "Dışa Aktar", "EXPORTING_DATA": "Dışa aktarılıyor…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json index 3f0dc3ac2..d5ed0ca5c 100644 --- a/apps/web/src/assets/i18n/zh.json +++ b/apps/web/src/assets/i18n/zh.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "出口", "EXPORTING_DATA": "导出中…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": { diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json index 042b7c831..bef80d0c6 100644 --- a/apps/web/src/assets/i18n/zhtw.json +++ b/apps/web/src/assets/i18n/zhtw.json @@ -354,6 +354,22 @@ "EXPORT_DATA": "匯出", "EXPORTING_DATA": "正在匯出…", "REMOVE_ALL": "Delete all playlists and playlist data", + "BACKUP_CONTENTS": "Backup contents", + "BACKUP_INCLUDE_SECRETS": "Include portal credentials and Stalker device identity", + "BACKUP_REDACTED_WARNING": "Portal passwords and Stalker device identity are excluded by default. Even a redacted backup can still contain MAC addresses, portal hosts/source URLs, and private URLs embedded in raw M3U playlist data.", + "BACKUP_SECRETS_WARNING": "Warning: this export will include portal usernames and passwords plus Stalker device identity fields. Store the backup securely.", + "BACKUP_CREDENTIALS_DIALOG": { + "TITLE": "Xtream credentials required", + "MESSAGE": "This redacted backup does not contain the credentials for this playlist.", + "PLAYLIST": "Playlist", + "SERVER": "Server", + "USERNAME": "Username", + "PASSWORD": "Password", + "USERNAME_REQUIRED": "Enter a username.", + "PASSWORD_REQUIRED": "Enter a password.", + "SKIP": "Skip playlist", + "IMPORT": "Import playlist" + }, "REMOVE_ALL_BUTTON": "Delete all", "REMOVE_ALL_DESCRIPTION": "Delete every M3U, Xtream, and Stalker playlist stored in the app.", "REMOVE_DIALOG": {