feat: custom pwd of bak (#827)

This commit is contained in:
lollipopkit🏳️‍⚧️
2025-07-25 16:38:28 +08:00
committed by GitHub
parent 8c3302cf0d
commit 682a6e4f2d
37 changed files with 779 additions and 236 deletions

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/provider/app.dart'; import 'package:server_box/data/provider/app.dart';
@@ -13,7 +12,7 @@ abstract final class KeybordInteractive {
}) async { }) async {
try { try {
final res = await (ctx ?? AppProvider.ctx)?.showPwdDialog( final res = await (ctx ?? AppProvider.ctx)?.showPwdDialog(
title: l10n.pwd, title: libL10n.pwd,
id: spi.id, id: spi.id,
label: spi.id, label: spi.id,
); );

View File

@@ -74,15 +74,27 @@ abstract class BackupV2 with _$BackupV2 implements Mergeable {
); );
} }
static Future<String> backup([String? name]) async { static Future<String> backup([String? name, String? password]) async {
final bak = await BackupV2.loadFromStore(); final bak = await BackupV2.loadFromStore();
final result = json.encode(bak.toJson()); var result = json.encode(bak.toJson());
if (password != null && password.isNotEmpty) {
result = Cryptor.encrypt(result, password);
}
final path = Paths.doc.joinPath(name ?? Miscs.bakFileName); final path = Paths.doc.joinPath(name ?? Miscs.bakFileName);
await File(path).writeAsString(result); await File(path).writeAsString(result);
return path; return path;
} }
factory BackupV2.fromJsonString(String jsonString) { factory BackupV2.fromJsonString(String jsonString, [String? password]) {
if (Cryptor.isEncrypted(jsonString)) {
if (password == null || password.isEmpty) {
throw Exception('Backup is encrypted but no password provided');
}
jsonString = Cryptor.decrypt(jsonString, password);
}
final map = json.decode(jsonString) as Map<String, dynamic>; final map = json.decode(jsonString) as Map<String, dynamic>;
return BackupV2.fromJson(map); return BackupV2.fromJson(map);
} }

View File

@@ -0,0 +1,198 @@
import 'package:computer/computer.dart';
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart';
import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/model/app/bak/backup2.dart';
import 'package:server_box/data/model/app/bak/backup_source.dart';
import 'package:server_box/data/model/app/bak/utils.dart';
import 'package:server_box/data/res/store.dart';
/// Service class for handling backup operations
class BackupService {
/// Perform backup operation with the given source
static Future<void> backup(BuildContext context, BackupSource source) async {
final password = await _getBackupPassword(context);
if (password == null) return;
try {
final path = await BackupV2.backup(null, password.isEmpty ? null : password);
await source.saveContent(path);
// Show success message for clipboard source
if (source is ClipboardBackupSource) {
context.showSnackBar(libL10n.success);
}
} catch (e, s) {
context.showErrDialog(e, s, libL10n.backup);
}
}
/// Perform restore operation with the given source
static Future<void> restore(BuildContext context, BackupSource source) async {
final text = await source.getContent();
if (text == null) {
// Show empty message for clipboard source
if (source is ClipboardBackupSource) {
context.showSnackBar(libL10n.empty);
}
return;
}
await restoreFromText(context, text);
}
/// Handle password dialog for backup operations
static Future<String?> _getBackupPassword(BuildContext context) async {
final savedPassword = await Stores.setting.backupasswd.read();
String? password;
if (savedPassword != null && savedPassword.isNotEmpty) {
// Use saved password or ask for custom password
final useCustom = await context.showRoundDialog<bool>(
title: l10n.backupPassword,
child: Text(l10n.backupPasswordTip),
actions: [
Btn.cancel(),
TextButton(onPressed: () => context.pop(false), child: Text(l10n.backupPasswordSet)),
TextButton(onPressed: () => context.pop(true), child: Text(libL10n.custom)),
],
);
if (useCustom == null) return null;
if (useCustom) {
password = await _showPasswordDialog(context, initial: savedPassword);
} else {
password = savedPassword;
}
} else {
// No saved password, ask if user wants to set one
password = await _showPasswordDialog(context);
}
return password;
}
/// Handle restore from text with decryption support
static Future<void> restoreFromText(BuildContext context, String text) async {
// Check if backup is encrypted
final isEncrypted = Cryptor.isEncrypted(text);
String? password;
if (!isEncrypted) {
try {
final (backup, err) = await context.showLoadingDialog(
fn: () => Computer.shared.start(MergeableUtils.fromJsonString, text),
);
if (err != null || backup == null) return;
await _confirmAndRestore(context, backup);
} catch (e, s) {
Loggers.app.warning('Import backup failed', e, s);
context.showErrDialog(e, s, libL10n.restore);
}
return;
}
// Try with saved password first
final savedPassword = await Stores.setting.backupasswd.read();
if (savedPassword != null && savedPassword.isNotEmpty) {
try {
final (backup, err) = await context.showLoadingDialog(
fn: () => Computer.shared.start((args) => MergeableUtils.fromJsonString(args.$1, args.$2), (
text,
savedPassword,
)),
);
if (err == null && backup != null) {
await _confirmAndRestore(context, backup);
return;
}
} catch (e) {
// Saved password failed, will prompt for manual input
}
}
// Prompt for password with retry logic
while (true) {
password = await _showPasswordDialog(context, title: libL10n.pwd, hint: l10n.backupEncrypted);
if (password == null) return; // User cancelled
try {
final (backup, err) = await context.showLoadingDialog(
fn: () => Computer.shared.start((args) => MergeableUtils.fromJsonString(args.$1, args.$2), (
text,
password,
)),
);
if (err != null || backup == null) continue;
await _confirmAndRestore(context, backup);
return;
} catch (e) {
if (e.toString().contains('incorrect password') || e.toString().contains('Failed to decrypt')) {
final retry = await context.showRoundDialog<bool>(
title: l10n.backupPasswordWrong,
child: Text(l10n.backupPasswordWrong),
actions: [
TextButton(onPressed: () => context.pop(false), child: Text(libL10n.cancel)),
TextButton(onPressed: () => context.pop(true), child: Text(libL10n.retry)),
],
);
if (retry != true) return;
continue; // Try again
} else {
// Other error, show and exit
context.showErrDialog(e, null, libL10n.restore);
return;
}
}
}
}
/// Confirm and execute restore operation
static Future<void> _confirmAndRestore(BuildContext context, (dynamic, String) backup) async {
await context.showRoundDialog(
title: libL10n.restore,
child: Text(libL10n.askContinue('${libL10n.restore} ${libL10n.backup}(${backup.$2})')),
actions: Btn.ok(
onTap: () async {
await backup.$1.merge(force: true);
context.pop();
},
).toList,
);
}
/// Show password input dialog
static Future<String?> _showPasswordDialog(
BuildContext context, {
String? initial,
String? title,
String? hint,
}) async {
final controller = TextEditingController(text: initial ?? '');
final result = await context.showRoundDialog<String>(
title: title ?? libL10n.pwd,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(hint ?? l10n.backupPasswordTip, style: UIs.textGrey),
UIs.height13,
Input(
label: l10n.backupPassword,
controller: controller,
obscureText: true,
onSubmitted: (_) => context.pop(controller.text),
),
],
),
actions: [
Btn.cancel(),
TextButton(onPressed: () => context.pop(controller.text), child: Text(libL10n.ok)),
],
);
controller.dispose();
return result;
}
}

View File

@@ -0,0 +1,62 @@
import 'dart:io';
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart';
/// Abstract interface for backup content sources
abstract class BackupSource {
/// Get content from this source for restore
Future<String?> getContent();
/// Save content to this source for backup
Future<void> saveContent(String filePath);
/// Display name for this source
String get displayName;
/// Icon for this source
IconData get icon;
}
/// File-based backup source
class FileBackupSource implements BackupSource {
@override
Future<String?> getContent() async {
return await Pfs.pickFileString();
}
@override
Future<void> saveContent(String filePath) async {
await Pfs.sharePaths(paths: [filePath]);
}
@override
String get displayName => libL10n.file;
@override
IconData get icon => Icons.file_open;
}
/// Clipboard-based backup source
class ClipboardBackupSource implements BackupSource {
@override
Future<String?> getContent() async {
final text = await Pfs.paste();
if (text == null || text.isEmpty) {
return null;
}
return text.trim();
}
@override
Future<void> saveContent(String filePath) async {
final content = await File(filePath).readAsString();
Pfs.copy(content);
}
@override
String get displayName => libL10n.clipboard;
@override
IconData get icon => Icons.content_paste;
}

View File

@@ -3,9 +3,9 @@ import 'package:server_box/data/model/app/bak/backup.dart';
import 'package:server_box/data/model/app/bak/backup2.dart'; import 'package:server_box/data/model/app/bak/backup2.dart';
abstract final class MergeableUtils { abstract final class MergeableUtils {
static (Mergeable, String) fromJsonString(String json) { static (Mergeable, String) fromJsonString(String json, [String? password]) {
try { try {
final bak = BackupV2.fromJsonString(json); final bak = BackupV2.fromJsonString(json, password);
return (bak, DateTime.fromMillisecondsSinceEpoch(bak.date).hms()); return (bak, DateTime.fromMillisecondsSinceEpoch(bak.date).hms());
} catch (e) { } catch (e) {
final bak = Backup.fromJsonString(json); final bak = Backup.fromJsonString(json);

View File

@@ -270,4 +270,7 @@ class SettingStore extends HiveStore {
/// Have notified user for notificaiton permission or not /// Have notified user for notificaiton permission or not
late final noNotiPerm = propertyDefault('noNotiPerm', false); late final noNotiPerm = propertyDefault('noNotiPerm', false);
/// The backup password
late final backupasswd = SecureProp('bakPasswd');
} }

View File

@@ -188,7 +188,7 @@ abstract class AppLocalizations {
/// No description provided for @backupTip. /// No description provided for @backupTip.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'The exported data is weakly encrypted. \nPlease keep it safe.'** /// **'The exported data can be encrypted with password. \nPlease keep it safe.'**
String get backupTip; String get backupTip;
/// No description provided for @backupVersionNotMatch. /// No description provided for @backupVersionNotMatch.
@@ -197,6 +197,48 @@ abstract class AppLocalizations {
/// **'Backup version is not match.'** /// **'Backup version is not match.'**
String get backupVersionNotMatch; String get backupVersionNotMatch;
/// No description provided for @backupPassword.
///
/// In en, this message translates to:
/// **'Backup password'**
String get backupPassword;
/// No description provided for @backupPasswordTip.
///
/// In en, this message translates to:
/// **'Set a password to encrypt backup files. Leave empty to disable encryption.'**
String get backupPasswordTip;
/// No description provided for @backupPasswordWrong.
///
/// In en, this message translates to:
/// **'Incorrect backup password'**
String get backupPasswordWrong;
/// No description provided for @backupEncrypted.
///
/// In en, this message translates to:
/// **'Backup is encrypted'**
String get backupEncrypted;
/// No description provided for @backupNotEncrypted.
///
/// In en, this message translates to:
/// **'Backup is not encrypted'**
String get backupNotEncrypted;
/// No description provided for @backupPasswordSet.
///
/// In en, this message translates to:
/// **'Backup password set'**
String get backupPasswordSet;
/// No description provided for @backupPasswordRemoved.
///
/// In en, this message translates to:
/// **'Backup password removed'**
String get backupPasswordRemoved;
/// No description provided for @battery. /// No description provided for @battery.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -944,12 +986,6 @@ abstract class AppLocalizations {
/// **'This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.'** /// **'This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.'**
String get pveVersionLow; String get pveVersionLow;
/// No description provided for @pwd.
///
/// In en, this message translates to:
/// **'Password'**
String get pwd;
/// No description provided for @read. /// No description provided for @read.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -47,12 +47,34 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Das Backup wird nur einfach verschlüsselt.\nBitte bewahre die Datei sicher auf.'; 'Die exportierten Daten können mit einem Passwort verschlüsselt werden. \nBitte sicher aufbewahren.';
@override @override
String get backupVersionNotMatch => String get backupVersionNotMatch =>
'Die Backup-Version stimmt nicht überein.'; 'Die Backup-Version stimmt nicht überein.';
@override
String get backupPassword => 'Backup-Passwort';
@override
String get backupPasswordTip =>
'Setzen Sie ein Passwort, um Backup-Dateien zu verschlüsseln. Leer lassen, um Verschlüsselung zu deaktivieren.';
@override
String get backupPasswordWrong => 'Falsches Backup-Passwort';
@override
String get backupEncrypted => 'Backup ist verschlüsselt';
@override
String get backupNotEncrypted => 'Backup ist nicht verschlüsselt';
@override
String get backupPasswordSet => 'Backup-Passwort gesetzt';
@override
String get backupPasswordRemoved => 'Backup-Passwort entfernt';
@override @override
String get battery => 'Batterie'; String get battery => 'Batterie';
@@ -470,9 +492,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.'; 'Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.';
@override
String get pwd => 'Passwort';
@override @override
String get read => 'Lesen'; String get read => 'Lesen';

View File

@@ -47,11 +47,33 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'The exported data is weakly encrypted. \nPlease keep it safe.'; 'The exported data can be encrypted with password. \nPlease keep it safe.';
@override @override
String get backupVersionNotMatch => 'Backup version is not match.'; String get backupVersionNotMatch => 'Backup version is not match.';
@override
String get backupPassword => 'Backup password';
@override
String get backupPasswordTip =>
'Set a password to encrypt backup files. Leave empty to disable encryption.';
@override
String get backupPasswordWrong => 'Incorrect backup password';
@override
String get backupEncrypted => 'Backup is encrypted';
@override
String get backupNotEncrypted => 'Backup is not encrypted';
@override
String get backupPasswordSet => 'Backup password set';
@override
String get backupPasswordRemoved => 'Backup password removed';
@override @override
String get battery => 'Battery'; String get battery => 'Battery';
@@ -468,9 +490,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.'; 'This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.';
@override
String get pwd => 'Password';
@override @override
String get read => 'Read'; String get read => 'Read';

View File

@@ -47,12 +47,34 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Los datos exportados solo están encriptados de manera básica, por favor guárdalos en un lugar seguro.'; 'Los datos exportados pueden ser encriptados con contraseña. \nPor favor guárdalos en un lugar seguro.';
@override @override
String get backupVersionNotMatch => String get backupVersionNotMatch =>
'La versión de la copia de seguridad no coincide, no se puede restaurar'; 'La versión de la copia de seguridad no coincide, no se puede restaurar';
@override
String get backupPassword => 'Contraseña de respaldo';
@override
String get backupPasswordTip =>
'Establece una contraseña para encriptar archivos de respaldo. Déjalo vacío para desactivar la encriptación.';
@override
String get backupPasswordWrong => 'Contraseña de respaldo incorrecta';
@override
String get backupEncrypted => 'El respaldo está encriptado';
@override
String get backupNotEncrypted => 'El respaldo no está encriptado';
@override
String get backupPasswordSet => 'Contraseña de respaldo establecida';
@override
String get backupPasswordRemoved => 'Contraseña de respaldo eliminada';
@override @override
String get battery => 'Batería'; String get battery => 'Batería';
@@ -472,9 +494,6 @@ class AppLocalizationsEs extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.'; 'Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.';
@override
String get pwd => 'Contraseña';
@override @override
String get read => 'Leer'; String get read => 'Leer';

View File

@@ -47,12 +47,34 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Les données exportées sont simplement chiffrées. \nVeuillez les garder en sécurité.'; 'Les données exportées peuvent être chiffrées avec un mot de passe. \nVeuillez les garder en sécurité.';
@override @override
String get backupVersionNotMatch => String get backupVersionNotMatch =>
'La version de sauvegarde ne correspond pas.'; 'La version de sauvegarde ne correspond pas.';
@override
String get backupPassword => 'Mot de passe de sauvegarde';
@override
String get backupPasswordTip =>
'Définissez un mot de passe pour chiffrer les fichiers de sauvegarde. Laissez vide pour désactiver le chiffrement.';
@override
String get backupPasswordWrong => 'Mot de passe de sauvegarde incorrect';
@override
String get backupEncrypted => 'La sauvegarde est chiffrée';
@override
String get backupNotEncrypted => 'La sauvegarde n\'est pas chiffrée';
@override
String get backupPasswordSet => 'Mot de passe de sauvegarde défini';
@override
String get backupPasswordRemoved => 'Mot de passe de sauvegarde supprimé';
@override @override
String get battery => 'Batterie'; String get battery => 'Batterie';
@@ -473,9 +495,6 @@ class AppLocalizationsFr extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Cette fonctionnalité est actuellement en phase de test et n\'a été testée que sur PVE 8+. Veuillez l\'utiliser avec prudence.'; 'Cette fonctionnalité est actuellement en phase de test et n\'a été testée que sur PVE 8+. Veuillez l\'utiliser avec prudence.';
@override
String get pwd => 'Mot de passe';
@override @override
String get read => 'Lire'; String get read => 'Lire';

View File

@@ -47,11 +47,33 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Data yang diekspor hanya dienkripsi.\nTolong jaga keamanannya.'; 'Data yang diekspor dapat dienkripsi dengan kata sandi. \nHarap jaga keamanannya.';
@override @override
String get backupVersionNotMatch => 'Versi cadangan tidak cocok.'; String get backupVersionNotMatch => 'Versi cadangan tidak cocok.';
@override
String get backupPassword => 'Kata sandi cadangan';
@override
String get backupPasswordTip =>
'Setel kata sandi untuk mengenkripsi file cadangan. Biarkan kosong untuk menonaktifkan enkripsi.';
@override
String get backupPasswordWrong => 'Kata sandi cadangan salah';
@override
String get backupEncrypted => 'Cadangan telah dienkripsi';
@override
String get backupNotEncrypted => 'Cadangan tidak dienkripsi';
@override
String get backupPasswordSet => 'Kata sandi cadangan ditetapkan';
@override
String get backupPasswordRemoved => 'Kata sandi cadangan dihapus';
@override @override
String get battery => 'Baterai'; String get battery => 'Baterai';
@@ -468,9 +490,6 @@ class AppLocalizationsId extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Fitur ini saat ini sedang dalam tahap pengujian dan hanya diuji pada PVE 8+. Gunakan dengan hati-hati.'; 'Fitur ini saat ini sedang dalam tahap pengujian dan hanya diuji pada PVE 8+. Gunakan dengan hati-hati.';
@override
String get pwd => 'Kata sandi';
@override @override
String get read => 'Baca'; String get read => 'Baca';

View File

@@ -43,11 +43,33 @@ class AppLocalizationsJa extends AppLocalizations {
String get autoUpdateHomeWidget => 'ホームウィジェットを自動更新'; String get autoUpdateHomeWidget => 'ホームウィジェットを自動更新';
@override @override
String get backupTip => 'エクスポートされたデータは簡単に暗号化されています。適切に保管してください。'; String get backupTip => 'エクスポートされたデータはパスワードで暗号化できます。 \n適切に保管してください。';
@override @override
String get backupVersionNotMatch => 'バックアップバージョンが一致しないため、復元できません'; String get backupVersionNotMatch => 'バックアップバージョンが一致しないため、復元できません';
@override
String get backupPassword => 'バックアップパスワード';
@override
String get backupPasswordTip =>
'バックアップファイルを暗号化するためのパスワードを設定してください。暗号化を無効にするには空白のままにしてください。';
@override
String get backupPasswordWrong => 'バックアップパスワードが間違っています';
@override
String get backupEncrypted => 'バックアップは暗号化されています';
@override
String get backupNotEncrypted => 'バックアップは暗号化されていません';
@override
String get backupPasswordSet => 'バックアップパスワードが設定されました';
@override
String get backupPasswordRemoved => 'バックアップパスワードが削除されました';
@override @override
String get battery => 'バッテリー'; String get battery => 'バッテリー';
@@ -453,9 +475,6 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get pveVersionLow => 'この機能は現在テスト段階にあり、PVE 8+でのみテストされています。ご利用の際は慎重に。'; String get pveVersionLow => 'この機能は現在テスト段階にあり、PVE 8+でのみテストされています。ご利用の際は慎重に。';
@override
String get pwd => 'パスワード';
@override @override
String get read => '読み取り'; String get read => '読み取り';

View File

@@ -47,11 +47,33 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'De geëxporteerde gegevens zijn simpelweg versleuteld. \nBewaar deze aub veilig.'; 'De geëxporteerde gegevens kunnen worden versleuteld met een wachtwoord. \nBewaar deze aub veilig.';
@override @override
String get backupVersionNotMatch => 'Back-upversie komt niet overeen.'; String get backupVersionNotMatch => 'Back-upversie komt niet overeen.';
@override
String get backupPassword => 'Back-up wachtwoord';
@override
String get backupPasswordTip =>
'Stel een wachtwoord in om back-upbestanden te versleutelen. Laat leeg om versleuteling uit te schakelen.';
@override
String get backupPasswordWrong => 'Onjuist back-up wachtwoord';
@override
String get backupEncrypted => 'Back-up is versleuteld';
@override
String get backupNotEncrypted => 'Back-up is niet versleuteld';
@override
String get backupPasswordSet => 'Back-up wachtwoord ingesteld';
@override
String get backupPasswordRemoved => 'Back-up wachtwoord verwijderd';
@override @override
String get battery => 'Batterij'; String get battery => 'Batterij';
@@ -469,9 +491,6 @@ class AppLocalizationsNl extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Deze functie bevindt zich momenteel in de testfase en is alleen getest op PVE 8+. Gebruik het met voorzichtigheid.'; 'Deze functie bevindt zich momenteel in de testfase en is alleen getest op PVE 8+. Gebruik het met voorzichtigheid.';
@override
String get pwd => 'Wachtwoord';
@override @override
String get read => 'Lezen'; String get read => 'Lezen';

View File

@@ -47,12 +47,34 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Os dados exportados são criptografados de forma simples, por favor, guarde-os com segurança.'; 'Os dados exportados podem ser criptografados com senha. \nPor favor, guarde-os com segurança.';
@override @override
String get backupVersionNotMatch => String get backupVersionNotMatch =>
'Versão de backup não compatível, não é possível restaurar'; 'Versão de backup não compatível, não é possível restaurar';
@override
String get backupPassword => 'Senha de backup';
@override
String get backupPasswordTip =>
'Defina uma senha para criptografar arquivos de backup. Deixe vazio para desabilitar a criptografia.';
@override
String get backupPasswordWrong => 'Senha de backup incorreta';
@override
String get backupEncrypted => 'Backup está criptografado';
@override
String get backupNotEncrypted => 'Backup não está criptografado';
@override
String get backupPasswordSet => 'Senha de backup definida';
@override
String get backupPasswordRemoved => 'Senha de backup removida';
@override @override
String get battery => 'Bateria'; String get battery => 'Bateria';
@@ -469,9 +491,6 @@ class AppLocalizationsPt extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Esta funcionalidade está atualmente em fase de teste e foi testada apenas no PVE 8+. Por favor, use com cautela.'; 'Esta funcionalidade está atualmente em fase de teste e foi testada apenas no PVE 8+. Por favor, use com cautela.';
@override
String get pwd => 'Senha';
@override @override
String get read => 'Leitura'; String get read => 'Leitura';

View File

@@ -47,12 +47,34 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Экспортированные данные зашифрованы простым способом \nПожалуйста, храните их в безопасности.'; 'Экспортированные данные могут быть зашифрованы паролем. \nПожалуйста, храните их в безопасности.';
@override @override
String get backupVersionNotMatch => String get backupVersionNotMatch =>
'Версия резервной копии не совпадает, восстановление невозможно'; 'Версия резервной копии не совпадает, восстановление невозможно';
@override
String get backupPassword => 'Пароль резервной копии';
@override
String get backupPasswordTip =>
'Установите пароль для шифрования файлов резервных копий. Оставьте пустым, чтобы отключить шифрование.';
@override
String get backupPasswordWrong => 'Неверный пароль резервной копии';
@override
String get backupEncrypted => 'Резервная копия зашифрована';
@override
String get backupNotEncrypted => 'Резервная копия не зашифрована';
@override
String get backupPasswordSet => 'Пароль резервной копии установлен';
@override
String get backupPasswordRemoved => 'Пароль резервной копии удален';
@override @override
String get battery => 'Батарея'; String get battery => 'Батарея';
@@ -470,9 +492,6 @@ class AppLocalizationsRu extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Эта функция в настоящее время находится на стадии тестирования и была протестирована только на PVE 8+. Используйте ее с осторожностью.'; 'Эта функция в настоящее время находится на стадии тестирования и была протестирована только на PVE 8+. Используйте ее с осторожностью.';
@override
String get pwd => 'Пароль';
@override @override
String get read => 'Чтение'; String get read => 'Чтение';

View File

@@ -46,11 +46,33 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Dışa aktarılan veriler zayıf bir şekilde şifrelenmiştir. \nLütfen güvenli bir şekilde saklayın.'; 'Dışa aktarılan veriler parola ile şifrelenebilir. \nLütfen güvenli bir şekilde saklayın.';
@override @override
String get backupVersionNotMatch => 'Yedekleme sürümü eşleşmiyor.'; String get backupVersionNotMatch => 'Yedekleme sürümü eşleşmiyor.';
@override
String get backupPassword => 'Yedekleme parolası';
@override
String get backupPasswordTip =>
'Yedekleme dosyalarını şifrelemek için bir parola belirleyin. Şifrelemeyi devre dışı bırakmak için boş bırakın.';
@override
String get backupPasswordWrong => 'Yanlış yedekleme parolası';
@override
String get backupEncrypted => 'Yedekleme şifrelenmiş';
@override
String get backupNotEncrypted => 'Yedekleme şifreli değil';
@override
String get backupPasswordSet => 'Yedekleme parolası ayarlandı';
@override
String get backupPasswordRemoved => 'Yedekleme parolası kaldırıldı';
@override @override
String get battery => 'Pil'; String get battery => 'Pil';
@@ -467,9 +489,6 @@ class AppLocalizationsTr extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Bu özellik şu anda test aşamasında ve yalnızca PVE 8+ üzerinde test edildi. Lütfen dikkatli kullanın.'; 'Bu özellik şu anda test aşamasında ve yalnızca PVE 8+ üzerinde test edildi. Lütfen dikkatli kullanın.';
@override
String get pwd => 'Şifre';
@override @override
String get read => 'Oku'; String get read => 'Oku';

View File

@@ -47,12 +47,34 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get backupTip => String get backupTip =>
'Експортовані дані слабо зашифровані. \nБудь ласка, зберігайте їх у безпеці.'; 'Експортовані дані можуть бути зашифровані паролем. \nБудь ласка, зберігайте їх у безпеці.';
@override @override
String get backupVersionNotMatch => String get backupVersionNotMatch =>
'Версія резервного копіювання не збіглася.'; 'Версія резервного копіювання не збіглася.';
@override
String get backupPassword => 'Пароль резервного копіювання';
@override
String get backupPasswordTip =>
'Встановіть пароль для шифрування файлів резервного копіювання. Залиште порожнім для відключення шифрування.';
@override
String get backupPasswordWrong => 'Неправильний пароль резервного копіювання';
@override
String get backupEncrypted => 'Резервна копія зашифрована';
@override
String get backupNotEncrypted => 'Резервна копія не зашифрована';
@override
String get backupPasswordSet => 'Пароль резервного копіювання встановлено';
@override
String get backupPasswordRemoved => 'Пароль резервного копіювання видалено';
@override @override
String get battery => 'Акумулятор'; String get battery => 'Акумулятор';
@@ -472,9 +494,6 @@ class AppLocalizationsUk extends AppLocalizations {
String get pveVersionLow => String get pveVersionLow =>
'Ця функція наразі перебуває на стадії тестування та випробувалася лише на PVE 8+. Будь ласка, використовуйте її з обережністю.'; 'Ця функція наразі перебуває на стадії тестування та випробувалася лише на PVE 8+. Будь ласка, використовуйте її з обережністю.';
@override
String get pwd => 'Пароль';
@override @override
String get read => 'Читати'; String get read => 'Читати';

View File

@@ -42,11 +42,32 @@ class AppLocalizationsZh extends AppLocalizations {
String get autoUpdateHomeWidget => '自动更新桌面小部件'; String get autoUpdateHomeWidget => '自动更新桌面小部件';
@override @override
String get backupTip => '导出的数据仅进行了简单加密,请妥善保管。'; String get backupTip => '导出的数据可以使用密码加密,请妥善保管。';
@override @override
String get backupVersionNotMatch => '备份版本不匹配,无法恢复'; String get backupVersionNotMatch => '备份版本不匹配,无法恢复';
@override
String get backupPassword => '备份密码';
@override
String get backupPasswordTip => '设置密码以加密备份文件。留空则禁用加密。';
@override
String get backupPasswordWrong => '备份密码错误';
@override
String get backupEncrypted => '备份已加密';
@override
String get backupNotEncrypted => '备份未加密';
@override
String get backupPasswordSet => '备份密码已设置';
@override
String get backupPasswordRemoved => '备份密码已移除';
@override @override
String get battery => '电池'; String get battery => '电池';
@@ -446,9 +467,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get pveVersionLow => '当前该功能处于测试阶段,仅在 PVE 8+ 上测试过,请谨慎使用'; String get pveVersionLow => '当前该功能处于测试阶段,仅在 PVE 8+ 上测试过,请谨慎使用';
@override
String get pwd => '密码';
@override @override
String get read => ''; String get read => '';
@@ -776,11 +794,32 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
String get autoUpdateHomeWidget => '自動更新桌面小工具'; String get autoUpdateHomeWidget => '自動更新桌面小工具';
@override @override
String get backupTip => '匯出的資料僅進行了簡單加密,請妥善保管。'; String get backupTip => '匯出的資料可以使用密碼加密。 \n請妥善保管。';
@override @override
String get backupVersionNotMatch => '備份版本不相符,無法還原'; String get backupVersionNotMatch => '備份版本不相符,無法還原';
@override
String get backupPassword => '備份密碼';
@override
String get backupPasswordTip => '設定密碼來加密備份檔案。留空則停用加密。';
@override
String get backupPasswordWrong => '備份密碼錯誤';
@override
String get backupEncrypted => '備份已加密';
@override
String get backupNotEncrypted => '備份未加密';
@override
String get backupPasswordSet => '備份密碼已設定';
@override
String get backupPasswordRemoved => '備份密碼已移除';
@override @override
String get battery => '電池'; String get battery => '電池';
@@ -1180,9 +1219,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get pveVersionLow => '此功能目前處於測試階段,僅在 PVE 8+ 上進行過測試。請謹慎使用。'; String get pveVersionLow => '此功能目前處於測試階段,僅在 PVE 8+ 上進行過測試。請謹慎使用。';
@override
String get pwd => '密碼';
@override @override
String get read => '讀取'; String get read => '讀取';

View File

@@ -11,8 +11,15 @@
"autoConnect": "Automatisch verbinden", "autoConnect": "Automatisch verbinden",
"autoRun": "Automatischer Start", "autoRun": "Automatischer Start",
"autoUpdateHomeWidget": "Home-Widget automatisch aktualisieren", "autoUpdateHomeWidget": "Home-Widget automatisch aktualisieren",
"backupTip": "Das Backup wird nur einfach verschlüsselt.\nBitte bewahre die Datei sicher auf.", "backupTip": "Die exportierten Daten können mit einem Passwort verschlüsselt werden. \nBitte sicher aufbewahren.",
"backupVersionNotMatch": "Die Backup-Version stimmt nicht überein.", "backupVersionNotMatch": "Die Backup-Version stimmt nicht überein.",
"backupPassword": "Backup-Passwort",
"backupPasswordTip": "Setzen Sie ein Passwort, um Backup-Dateien zu verschlüsseln. Leer lassen, um Verschlüsselung zu deaktivieren.",
"backupPasswordWrong": "Falsches Backup-Passwort",
"backupEncrypted": "Backup ist verschlüsselt",
"backupNotEncrypted": "Backup ist nicht verschlüsselt",
"backupPasswordSet": "Backup-Passwort gesetzt",
"backupPasswordRemoved": "Backup-Passwort entfernt",
"battery": "Batterie", "battery": "Batterie",
"bgRun": "Hintergrundaktualisierung", "bgRun": "Hintergrundaktualisierung",
"bgRunTip": "Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".", "bgRunTip": "Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Nicht empfohlen, Achten Sie auf Sicherheitsrisiken! Wenn Sie das Standardzertifikat von PVE verwenden, müssen Sie diese Option aktivieren.", "pveIgnoreCertTip": "Nicht empfohlen, Achten Sie auf Sicherheitsrisiken! Wenn Sie das Standardzertifikat von PVE verwenden, müssen Sie diese Option aktivieren.",
"pveLoginFailed": "Anmeldung fehlgeschlagen. Kann nicht mit Benutzername/Passwort aus der Serverkonfiguration angemeldet werden, um sich über Linux PAM anzumelden.", "pveLoginFailed": "Anmeldung fehlgeschlagen. Kann nicht mit Benutzername/Passwort aus der Serverkonfiguration angemeldet werden, um sich über Linux PAM anzumelden.",
"pveVersionLow": "Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.", "pveVersionLow": "Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.",
"pwd": "Passwort",
"read": "Lesen", "read": "Lesen",
"reboot": "Neustart", "reboot": "Neustart",
"rememberPwdInMem": "Passwort im Speicher behalten", "rememberPwdInMem": "Passwort im Speicher behalten",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Auto connect", "autoConnect": "Auto connect",
"autoRun": "Auto run", "autoRun": "Auto run",
"autoUpdateHomeWidget": "Automatic home widget update", "autoUpdateHomeWidget": "Automatic home widget update",
"backupTip": "The exported data is weakly encrypted. \nPlease keep it safe.", "backupTip": "The exported data can be encrypted with password. \nPlease keep it safe.",
"backupVersionNotMatch": "Backup version is not match.", "backupVersionNotMatch": "Backup version is not match.",
"backupPassword": "Backup password",
"backupPasswordTip": "Set a password to encrypt backup files. Leave empty to disable encryption.",
"backupPasswordWrong": "Incorrect backup password",
"backupEncrypted": "Backup is encrypted",
"backupNotEncrypted": "Backup is not encrypted",
"backupPasswordSet": "Backup password set",
"backupPasswordRemoved": "Backup password removed",
"battery": "Battery", "battery": "Battery",
"bgRun": "Run in background", "bgRun": "Run in background",
"bgRunTip": "This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".", "bgRunTip": "This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Not recommended to enable, beware of security risks! If you are using the default certificate from PVE, you need to enable this option.", "pveIgnoreCertTip": "Not recommended to enable, beware of security risks! If you are using the default certificate from PVE, you need to enable this option.",
"pveLoginFailed": "Login failed. Unable to authenticate with username/password from server configuration for Linux PAM login.", "pveLoginFailed": "Login failed. Unable to authenticate with username/password from server configuration for Linux PAM login.",
"pveVersionLow": "This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.", "pveVersionLow": "This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.",
"pwd": "Password",
"read": "Read", "read": "Read",
"reboot": "Reboot", "reboot": "Reboot",
"rememberPwdInMem": "Remember password in memory", "rememberPwdInMem": "Remember password in memory",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Conexión automática", "autoConnect": "Conexión automática",
"autoRun": "Ejecución automática", "autoRun": "Ejecución automática",
"autoUpdateHomeWidget": "Actualizar automáticamente el widget del escritorio", "autoUpdateHomeWidget": "Actualizar automáticamente el widget del escritorio",
"backupTip": "Los datos exportados solo están encriptados de manera básica, por favor guárdalos en un lugar seguro.", "backupTip": "Los datos exportados pueden ser encriptados con contraseña. \nPor favor guárdalos en un lugar seguro.",
"backupVersionNotMatch": "La versión de la copia de seguridad no coincide, no se puede restaurar", "backupVersionNotMatch": "La versión de la copia de seguridad no coincide, no se puede restaurar",
"backupPassword": "Contraseña de respaldo",
"backupPasswordTip": "Establece una contraseña para encriptar archivos de respaldo. Déjalo vacío para desactivar la encriptación.",
"backupPasswordWrong": "Contraseña de respaldo incorrecta",
"backupEncrypted": "El respaldo está encriptado",
"backupNotEncrypted": "El respaldo no está encriptado",
"backupPasswordSet": "Contraseña de respaldo establecida",
"backupPasswordRemoved": "Contraseña de respaldo eliminada",
"battery": "Batería", "battery": "Batería",
"bgRun": "Ejecución en segundo plano", "bgRun": "Ejecución en segundo plano",
"bgRunTip": "Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.", "bgRunTip": "Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "No se recomienda activarlo, ¡tenga cuidado con los riesgos de seguridad! Si está utilizando el certificado predeterminado de PVE, debe habilitar esta opción.", "pveIgnoreCertTip": "No se recomienda activarlo, ¡tenga cuidado con los riesgos de seguridad! Si está utilizando el certificado predeterminado de PVE, debe habilitar esta opción.",
"pveLoginFailed": "Fallo al iniciar sesión. No se puede autenticar con el nombre de usuario/contraseña de la configuración del servidor para el inicio de sesión de Linux PAM.", "pveLoginFailed": "Fallo al iniciar sesión. No se puede autenticar con el nombre de usuario/contraseña de la configuración del servidor para el inicio de sesión de Linux PAM.",
"pveVersionLow": "Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.", "pveVersionLow": "Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.",
"pwd": "Contraseña",
"read": "Leer", "read": "Leer",
"reboot": "Reiniciar", "reboot": "Reiniciar",
"rememberPwdInMem": "Recordar contraseña en la memoria", "rememberPwdInMem": "Recordar contraseña en la memoria",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Connexion automatique", "autoConnect": "Connexion automatique",
"autoRun": "Exécution automatique", "autoRun": "Exécution automatique",
"autoUpdateHomeWidget": "Mise à jour automatique du widget d'accueil", "autoUpdateHomeWidget": "Mise à jour automatique du widget d'accueil",
"backupTip": "Les données exportées sont simplement chiffrées. \nVeuillez les garder en sécurité.", "backupTip": "Les données exportées peuvent être chiffrées avec un mot de passe. \nVeuillez les garder en sécurité.",
"backupVersionNotMatch": "La version de sauvegarde ne correspond pas.", "backupVersionNotMatch": "La version de sauvegarde ne correspond pas.",
"backupPassword": "Mot de passe de sauvegarde",
"backupPasswordTip": "Définissez un mot de passe pour chiffrer les fichiers de sauvegarde. Laissez vide pour désactiver le chiffrement.",
"backupPasswordWrong": "Mot de passe de sauvegarde incorrect",
"backupEncrypted": "La sauvegarde est chiffrée",
"backupNotEncrypted": "La sauvegarde n'est pas chiffrée",
"backupPasswordSet": "Mot de passe de sauvegarde défini",
"backupPasswordRemoved": "Mot de passe de sauvegarde supprimé",
"battery": "Batterie", "battery": "Batterie",
"bgRun": "Exécution en arrière-plan", "bgRun": "Exécution en arrière-plan",
"bgRunTip": "Cette option signifie seulement que le programme essaiera de s'exécuter en arrière-plan, que cela soit possible dépend de l'autorisation activée ou non. Pour Android natif, veuillez désactiver l'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d'économie d'énergie en « Illimité ».", "bgRunTip": "Cette option signifie seulement que le programme essaiera de s'exécuter en arrière-plan, que cela soit possible dépend de l'autorisation activée ou non. Pour Android natif, veuillez désactiver l'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d'économie d'énergie en « Illimité ».",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Il n'est pas recommandé de l'activer, attention aux risques de sécurité ! Si vous utilisez le certificat par défaut de PVE, vous devez activer cette option.", "pveIgnoreCertTip": "Il n'est pas recommandé de l'activer, attention aux risques de sécurité ! Si vous utilisez le certificat par défaut de PVE, vous devez activer cette option.",
"pveLoginFailed": "Échec de la connexion. Impossible d'authentifier avec le nom d'utilisateur / mot de passe de la configuration du serveur pour la connexion Linux PAM.", "pveLoginFailed": "Échec de la connexion. Impossible d'authentifier avec le nom d'utilisateur / mot de passe de la configuration du serveur pour la connexion Linux PAM.",
"pveVersionLow": "Cette fonctionnalité est actuellement en phase de test et n'a été testée que sur PVE 8+. Veuillez l'utiliser avec prudence.", "pveVersionLow": "Cette fonctionnalité est actuellement en phase de test et n'a été testée que sur PVE 8+. Veuillez l'utiliser avec prudence.",
"pwd": "Mot de passe",
"read": "Lire", "read": "Lire",
"reboot": "Redémarrer", "reboot": "Redémarrer",
"rememberPwdInMem": "Mémoriser le mot de passe en mémoire", "rememberPwdInMem": "Mémoriser le mot de passe en mémoire",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Hubungkan otomatis", "autoConnect": "Hubungkan otomatis",
"autoRun": "Berjalan Otomatis", "autoRun": "Berjalan Otomatis",
"autoUpdateHomeWidget": "Widget Rumah Pembaruan Otomatis", "autoUpdateHomeWidget": "Widget Rumah Pembaruan Otomatis",
"backupTip": "Data yang diekspor hanya dienkripsi.\nTolong jaga keamanannya.", "backupTip": "Data yang diekspor dapat dienkripsi dengan kata sandi. \nHarap jaga keamanannya.",
"backupVersionNotMatch": "Versi cadangan tidak cocok.", "backupVersionNotMatch": "Versi cadangan tidak cocok.",
"backupPassword": "Kata sandi cadangan",
"backupPasswordTip": "Setel kata sandi untuk mengenkripsi file cadangan. Biarkan kosong untuk menonaktifkan enkripsi.",
"backupPasswordWrong": "Kata sandi cadangan salah",
"backupEncrypted": "Cadangan telah dienkripsi",
"backupNotEncrypted": "Cadangan tidak dienkripsi",
"backupPasswordSet": "Kata sandi cadangan ditetapkan",
"backupPasswordRemoved": "Kata sandi cadangan dihapus",
"battery": "Baterai", "battery": "Baterai",
"bgRun": "Jalankan di Backgroud", "bgRun": "Jalankan di Backgroud",
"bgRunTip": "Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".", "bgRunTip": "Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Tidak disarankan untuk diaktifkan, waspadai risiko keamanan! Jika Anda menggunakan sertifikat default dari PVE, Anda perlu mengaktifkan opsi ini.", "pveIgnoreCertTip": "Tidak disarankan untuk diaktifkan, waspadai risiko keamanan! Jika Anda menggunakan sertifikat default dari PVE, Anda perlu mengaktifkan opsi ini.",
"pveLoginFailed": "Login gagal. Tidak dapat mengautentikasi dengan nama pengguna/kata sandi dari konfigurasi server untuk login Linux PAM.", "pveLoginFailed": "Login gagal. Tidak dapat mengautentikasi dengan nama pengguna/kata sandi dari konfigurasi server untuk login Linux PAM.",
"pveVersionLow": "Fitur ini saat ini sedang dalam tahap pengujian dan hanya diuji pada PVE 8+. Gunakan dengan hati-hati.", "pveVersionLow": "Fitur ini saat ini sedang dalam tahap pengujian dan hanya diuji pada PVE 8+. Gunakan dengan hati-hati.",
"pwd": "Kata sandi",
"read": "Baca", "read": "Baca",
"reboot": "Reboot", "reboot": "Reboot",
"rememberPwdInMem": "Ingat kata sandi di dalam memori", "rememberPwdInMem": "Ingat kata sandi di dalam memori",

View File

@@ -11,8 +11,15 @@
"autoConnect": "自動接続", "autoConnect": "自動接続",
"autoRun": "自動実行", "autoRun": "自動実行",
"autoUpdateHomeWidget": "ホームウィジェットを自動更新", "autoUpdateHomeWidget": "ホームウィジェットを自動更新",
"backupTip": "エクスポートされたデータは簡単に暗号化されています。適切に保管してください。", "backupTip": "エクスポートされたデータはパスワードで暗号化できます。 \n適切に保管してください。",
"backupVersionNotMatch": "バックアップバージョンが一致しないため、復元できません", "backupVersionNotMatch": "バックアップバージョンが一致しないため、復元できません",
"backupPassword": "バックアップパスワード",
"backupPasswordTip": "バックアップファイルを暗号化するためのパスワードを設定してください。暗号化を無効にするには空白のままにしてください。",
"backupPasswordWrong": "バックアップパスワードが間違っています",
"backupEncrypted": "バックアップは暗号化されています",
"backupNotEncrypted": "バックアップは暗号化されていません",
"backupPasswordSet": "バックアップパスワードが設定されました",
"backupPasswordRemoved": "バックアップパスワードが削除されました",
"battery": "バッテリー", "battery": "バッテリー",
"bgRun": "バックグラウンド実行", "bgRun": "バックグラウンド実行",
"bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。", "bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "オプションを有効にすることは推奨されません、セキュリティリスクに注意してくださいPVEのデフォルト証明書を使用している場合は、このオプションを有効にする必要があります。", "pveIgnoreCertTip": "オプションを有効にすることは推奨されません、セキュリティリスクに注意してくださいPVEのデフォルト証明書を使用している場合は、このオプションを有効にする必要があります。",
"pveLoginFailed": "ログインに失敗しました。Linux PAMログインのためにサーバー構成からのユーザー名/パスワードで認証できません。", "pveLoginFailed": "ログインに失敗しました。Linux PAMログインのためにサーバー構成からのユーザー名/パスワードで認証できません。",
"pveVersionLow": "この機能は現在テスト段階にあり、PVE 8+でのみテストされています。ご利用の際は慎重に。", "pveVersionLow": "この機能は現在テスト段階にあり、PVE 8+でのみテストされています。ご利用の際は慎重に。",
"pwd": "パスワード",
"read": "読み取り", "read": "読み取り",
"reboot": "再起動", "reboot": "再起動",
"rememberPwdInMem": "メモリにパスワードを記憶する", "rememberPwdInMem": "メモリにパスワードを記憶する",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Automatisch verbinden", "autoConnect": "Automatisch verbinden",
"autoRun": "Automatisch uitvoeren", "autoRun": "Automatisch uitvoeren",
"autoUpdateHomeWidget": "Automatische update van home-widget", "autoUpdateHomeWidget": "Automatische update van home-widget",
"backupTip": "De geëxporteerde gegevens zijn simpelweg versleuteld. \nBewaar deze aub veilig.", "backupTip": "De geëxporteerde gegevens kunnen worden versleuteld met een wachtwoord. \nBewaar deze aub veilig.",
"backupVersionNotMatch": "Back-upversie komt niet overeen.", "backupVersionNotMatch": "Back-upversie komt niet overeen.",
"backupPassword": "Back-up wachtwoord",
"backupPasswordTip": "Stel een wachtwoord in om back-upbestanden te versleutelen. Laat leeg om versleuteling uit te schakelen.",
"backupPasswordWrong": "Onjuist back-up wachtwoord",
"backupEncrypted": "Back-up is versleuteld",
"backupNotEncrypted": "Back-up is niet versleuteld",
"backupPasswordSet": "Back-up wachtwoord ingesteld",
"backupPasswordRemoved": "Back-up wachtwoord verwijderd",
"battery": "Batterij", "battery": "Batterij",
"bgRun": "Uitvoeren op de achtergrond", "bgRun": "Uitvoeren op de achtergrond",
"bgRunTip": "Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".", "bgRunTip": "Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Niet aanbevolen om in te schakelen, let op beveiligingsrisico's! Als u de standaardcertificaat van PVE gebruikt, moet u deze optie inschakelen.", "pveIgnoreCertTip": "Niet aanbevolen om in te schakelen, let op beveiligingsrisico's! Als u de standaardcertificaat van PVE gebruikt, moet u deze optie inschakelen.",
"pveLoginFailed": "Aanmelden mislukt. Kan niet authenticeren met gebruikersnaam/wachtwoord van serverconfiguratie voor Linux PAM-login.", "pveLoginFailed": "Aanmelden mislukt. Kan niet authenticeren met gebruikersnaam/wachtwoord van serverconfiguratie voor Linux PAM-login.",
"pveVersionLow": "Deze functie bevindt zich momenteel in de testfase en is alleen getest op PVE 8+. Gebruik het met voorzichtigheid.", "pveVersionLow": "Deze functie bevindt zich momenteel in de testfase en is alleen getest op PVE 8+. Gebruik het met voorzichtigheid.",
"pwd": "Wachtwoord",
"read": "Lezen", "read": "Lezen",
"reboot": "Herstart", "reboot": "Herstart",
"rememberPwdInMem": "Wachtwoord onthouden in geheugen", "rememberPwdInMem": "Wachtwoord onthouden in geheugen",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Conexão automática", "autoConnect": "Conexão automática",
"autoRun": "Execução automática", "autoRun": "Execução automática",
"autoUpdateHomeWidget": "Atualização automática do widget da tela inicial", "autoUpdateHomeWidget": "Atualização automática do widget da tela inicial",
"backupTip": "Os dados exportados são criptografados de forma simples, por favor, guarde-os com segurança.", "backupTip": "Os dados exportados podem ser criptografados com senha. \nPor favor, guarde-os com segurança.",
"backupVersionNotMatch": "Versão de backup não compatível, não é possível restaurar", "backupVersionNotMatch": "Versão de backup não compatível, não é possível restaurar",
"backupPassword": "Senha de backup",
"backupPasswordTip": "Defina uma senha para criptografar arquivos de backup. Deixe vazio para desabilitar a criptografia.",
"backupPasswordWrong": "Senha de backup incorreta",
"backupEncrypted": "Backup está criptografado",
"backupNotEncrypted": "Backup não está criptografado",
"backupPasswordSet": "Senha de backup definida",
"backupPasswordRemoved": "Senha de backup removida",
"battery": "Bateria", "battery": "Bateria",
"bgRun": "Execução em segundo plano", "bgRun": "Execução em segundo plano",
"bgRunTip": "Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a 'Otimização de bateria' para este app, no MIUI, altere a estratégia de economia de energia para 'Sem restrições'.", "bgRunTip": "Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a 'Otimização de bateria' para este app, no MIUI, altere a estratégia de economia de energia para 'Sem restrições'.",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Não recomendado para ativar, cuidado com os riscos de segurança! Se estiver usando o certificado padrão do PVE, você precisa habilitar esta opção.", "pveIgnoreCertTip": "Não recomendado para ativar, cuidado com os riscos de segurança! Se estiver usando o certificado padrão do PVE, você precisa habilitar esta opção.",
"pveLoginFailed": "Falha no login. Não é possível autenticar com o nome de usuário/senha da configuração do servidor para login no Linux PAM.", "pveLoginFailed": "Falha no login. Não é possível autenticar com o nome de usuário/senha da configuração do servidor para login no Linux PAM.",
"pveVersionLow": "Esta funcionalidade está atualmente em fase de teste e foi testada apenas no PVE 8+. Por favor, use com cautela.", "pveVersionLow": "Esta funcionalidade está atualmente em fase de teste e foi testada apenas no PVE 8+. Por favor, use com cautela.",
"pwd": "Senha",
"read": "Leitura", "read": "Leitura",
"reboot": "Reiniciar", "reboot": "Reiniciar",
"rememberPwdInMem": "Lembrar senha na memória", "rememberPwdInMem": "Lembrar senha na memória",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Автоматическое подключение", "autoConnect": "Автоматическое подключение",
"autoRun": "Автозапуск", "autoRun": "Автозапуск",
"autoUpdateHomeWidget": "Автоматическое обновление виджета на главном экране", "autoUpdateHomeWidget": "Автоматическое обновление виджета на главном экране",
"backupTip": "Экспортированные данные зашифрованы простым способом \nПожалуйста, храните их в безопасности.", "backupTip": "Экспортированные данные могут быть зашифрованы паролем. \nПожалуйста, храните их в безопасности.",
"backupVersionNotMatch": "Версия резервной копии не совпадает, восстановление невозможно", "backupVersionNotMatch": "Версия резервной копии не совпадает, восстановление невозможно",
"backupPassword": "Пароль резервной копии",
"backupPasswordTip": "Установите пароль для шифрования файлов резервных копий. Оставьте пустым, чтобы отключить шифрование.",
"backupPasswordWrong": "Неверный пароль резервной копии",
"backupEncrypted": "Резервная копия зашифрована",
"backupNotEncrypted": "Резервная копия не зашифрована",
"backupPasswordSet": "Пароль резервной копии установлен",
"backupPasswordRemoved": "Пароль резервной копии удален",
"battery": "Батарея", "battery": "Батарея",
"bgRun": "Работа в фоновом режиме", "bgRun": "Работа в фоновом режиме",
"bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».", "bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Не рекомендуется включать, обратите внимание на риски безопасности! Если вы используете стандартный сертификат от PVE, вам нужно включить эту опцию.", "pveIgnoreCertTip": "Не рекомендуется включать, обратите внимание на риски безопасности! Если вы используете стандартный сертификат от PVE, вам нужно включить эту опцию.",
"pveLoginFailed": "Ошибка входа. Невозможно аутентифицироваться с помощью имени пользователя/пароля из конфигурации сервера для входа в Linux PAM.", "pveLoginFailed": "Ошибка входа. Невозможно аутентифицироваться с помощью имени пользователя/пароля из конфигурации сервера для входа в Linux PAM.",
"pveVersionLow": "Эта функция в настоящее время находится на стадии тестирования и была протестирована только на PVE 8+. Используйте ее с осторожностью.", "pveVersionLow": "Эта функция в настоящее время находится на стадии тестирования и была протестирована только на PVE 8+. Используйте ее с осторожностью.",
"pwd": "Пароль",
"read": "Чтение", "read": "Чтение",
"reboot": "Перезагрузка", "reboot": "Перезагрузка",
"rememberPwdInMem": "Запомнить пароль в памяти", "rememberPwdInMem": "Запомнить пароль в памяти",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Otomatik bağlan", "autoConnect": "Otomatik bağlan",
"autoRun": "Otomatik çalıştır", "autoRun": "Otomatik çalıştır",
"autoUpdateHomeWidget": "Ana ekran bileşenini otomatik güncelle", "autoUpdateHomeWidget": "Ana ekran bileşenini otomatik güncelle",
"backupTip": "Dışa aktarılan veriler zayıf bir şekilde şifrelenmiştir. \nLütfen güvenli bir şekilde saklayın.", "backupTip": "Dışa aktarılan veriler parola ile şifrelenebilir. \nLütfen güvenli bir şekilde saklayın.",
"backupVersionNotMatch": "Yedekleme sürümü eşleşmiyor.", "backupVersionNotMatch": "Yedekleme sürümü eşleşmiyor.",
"backupPassword": "Yedekleme parolası",
"backupPasswordTip": "Yedekleme dosyalarını şifrelemek için bir parola belirleyin. Şifrelemeyi devre dışı bırakmak için boş bırakın.",
"backupPasswordWrong": "Yanlış yedekleme parolası",
"backupEncrypted": "Yedekleme şifrelenmiş",
"backupNotEncrypted": "Yedekleme şifreli değil",
"backupPasswordSet": "Yedekleme parolası ayarlandı",
"backupPasswordRemoved": "Yedekleme parolası kaldırıldı",
"battery": "Pil", "battery": "Pil",
"bgRun": "Arka planda çalıştır", "bgRun": "Arka planda çalıştır",
"bgRunTip": "Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.", "bgRunTip": "Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Etkinleştirilmesi önerilmez, güvenlik risklerine dikkat edin! PVE'den varsayılan sertifikayı kullanıyorsanız, bu seçeneği etkinleştirmeniz gerekir.", "pveIgnoreCertTip": "Etkinleştirilmesi önerilmez, güvenlik risklerine dikkat edin! PVE'den varsayılan sertifikayı kullanıyorsanız, bu seçeneği etkinleştirmeniz gerekir.",
"pveLoginFailed": "Giriş başarısız. Linux PAM girişi için sunucu yapılandırmasındaki kullanıcı adı/şifre ile kimlik doğrulama yapılamadı.", "pveLoginFailed": "Giriş başarısız. Linux PAM girişi için sunucu yapılandırmasındaki kullanıcı adı/şifre ile kimlik doğrulama yapılamadı.",
"pveVersionLow": "Bu özellik şu anda test aşamasında ve yalnızca PVE 8+ üzerinde test edildi. Lütfen dikkatli kullanın.", "pveVersionLow": "Bu özellik şu anda test aşamasında ve yalnızca PVE 8+ üzerinde test edildi. Lütfen dikkatli kullanın.",
"pwd": "Şifre",
"read": "Oku", "read": "Oku",
"reboot": "Yeniden başlat", "reboot": "Yeniden başlat",
"rememberPwdInMem": "Şifreyi bellekte hatırla", "rememberPwdInMem": "Şifreyi bellekte hatırla",

View File

@@ -11,8 +11,15 @@
"autoConnect": "Авто підключення", "autoConnect": "Авто підключення",
"autoRun": "Авто запуск", "autoRun": "Авто запуск",
"autoUpdateHomeWidget": "Автоматичне оновлення віджетів на головному екрані", "autoUpdateHomeWidget": "Автоматичне оновлення віджетів на головному екрані",
"backupTip": "Експортовані дані слабо зашифровані. \nБудь ласка, зберігайте їх у безпеці.", "backupTip": "Експортовані дані можуть бути зашифровані паролем. \nБудь ласка, зберігайте їх у безпеці.",
"backupVersionNotMatch": "Версія резервного копіювання не збіглася.", "backupVersionNotMatch": "Версія резервного копіювання не збіглася.",
"backupPassword": "Пароль резервного копіювання",
"backupPasswordTip": "Встановіть пароль для шифрування файлів резервного копіювання. Залиште порожнім для відключення шифрування.",
"backupPasswordWrong": "Неправильний пароль резервного копіювання",
"backupEncrypted": "Резервна копія зашифрована",
"backupNotEncrypted": "Резервна копія не зашифрована",
"backupPasswordSet": "Пароль резервного копіювання встановлено",
"backupPasswordRemoved": "Пароль резервного копіювання видалено",
"battery": "Акумулятор", "battery": "Акумулятор",
"bgRun": "Запуск у фоновому режимі", "bgRun": "Запуск у фоновому режимі",
"bgRunTip": "Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".", "bgRunTip": "Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "Не рекомендується включати, будьте обережні з ризиками безпеки! Якщо ви використовуєте стандартний сертифікат від PVE, вам потрібно увімкнути цю опцію.", "pveIgnoreCertTip": "Не рекомендується включати, будьте обережні з ризиками безпеки! Якщо ви використовуєте стандартний сертифікат від PVE, вам потрібно увімкнути цю опцію.",
"pveLoginFailed": "Не вдалося увійти. Неможливо пройти аутентифікацію за допомогою імені користувача/пароля з конфігурації сервера для входу Linux PAM.", "pveLoginFailed": "Не вдалося увійти. Неможливо пройти аутентифікацію за допомогою імені користувача/пароля з конфігурації сервера для входу Linux PAM.",
"pveVersionLow": "Ця функція наразі перебуває на стадії тестування та випробувалася лише на PVE 8+. Будь ласка, використовуйте її з обережністю.", "pveVersionLow": "Ця функція наразі перебуває на стадії тестування та випробувалася лише на PVE 8+. Будь ласка, використовуйте її з обережністю.",
"pwd": "Пароль",
"read": "Читати", "read": "Читати",
"reboot": "Перезавантажити", "reboot": "Перезавантажити",
"rememberPwdInMem": "Запам'ятати пароль у пам'яті", "rememberPwdInMem": "Запам'ятати пароль у пам'яті",

View File

@@ -11,8 +11,15 @@
"autoConnect": "自动连接", "autoConnect": "自动连接",
"autoRun": "自动运行", "autoRun": "自动运行",
"autoUpdateHomeWidget": "自动更新桌面小部件", "autoUpdateHomeWidget": "自动更新桌面小部件",
"backupTip": "导出的数据仅进行了简单加密,请妥善保管。", "backupTip": "导出的数据可以使用密码加密,请妥善保管。",
"backupVersionNotMatch": "备份版本不匹配,无法恢复", "backupVersionNotMatch": "备份版本不匹配,无法恢复",
"backupPassword": "备份密码",
"backupPasswordTip": "设置密码以加密备份文件。留空则禁用加密。",
"backupPasswordWrong": "备份密码错误",
"backupEncrypted": "备份已加密",
"backupNotEncrypted": "备份未加密",
"backupPasswordSet": "备份密码已设置",
"backupPasswordRemoved": "备份密码已移除",
"battery": "电池", "battery": "电池",
"bgRun": "后台运行", "bgRun": "后台运行",
"bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI / HyperOS 请修改省电策略为“无限制”。", "bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI / HyperOS 请修改省电策略为“无限制”。",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "不推荐开启,注意安全隐患!如果你使用的 PVE 默认证书,需要开启该选项", "pveIgnoreCertTip": "不推荐开启,注意安全隐患!如果你使用的 PVE 默认证书,需要开启该选项",
"pveLoginFailed": "登录失败。无法使用服务器配置内的用户/密码,以 Linux PAM 方式登录。", "pveLoginFailed": "登录失败。无法使用服务器配置内的用户/密码,以 Linux PAM 方式登录。",
"pveVersionLow": "当前该功能处于测试阶段,仅在 PVE 8+ 上测试过,请谨慎使用", "pveVersionLow": "当前该功能处于测试阶段,仅在 PVE 8+ 上测试过,请谨慎使用",
"pwd": "密码",
"read": "读", "read": "读",
"reboot": "重启", "reboot": "重启",
"rememberPwdInMem": "在内存中记住密码", "rememberPwdInMem": "在内存中记住密码",

View File

@@ -11,8 +11,15 @@
"autoConnect": "自動連線", "autoConnect": "自動連線",
"autoRun": "自動執行", "autoRun": "自動執行",
"autoUpdateHomeWidget": "自動更新桌面小工具", "autoUpdateHomeWidget": "自動更新桌面小工具",
"backupTip": "匯出的資料僅進行了簡單加密,請妥善保管。", "backupTip": "匯出的資料可以使用密碼加密。 \n請妥善保管。",
"backupVersionNotMatch": "備份版本不相符,無法還原", "backupVersionNotMatch": "備份版本不相符,無法還原",
"backupPassword": "備份密碼",
"backupPasswordTip": "設定密碼來加密備份檔案。留空則停用加密。",
"backupPasswordWrong": "備份密碼錯誤",
"backupEncrypted": "備份已加密",
"backupNotEncrypted": "備份未加密",
"backupPasswordSet": "備份密碼已設定",
"backupPasswordRemoved": "備份密碼已移除",
"battery": "電池", "battery": "電池",
"bgRun": "背景執行", "bgRun": "背景執行",
"bgRunTip": "此開關只代表程式會嘗試在背景執行,具體能否在後臺執行取決於是否開啟了權限。 原生 Android 請關閉本 App 的“電池最佳化”MIUI / HyperOS 請修改省電策略為“無限制”。", "bgRunTip": "此開關只代表程式會嘗試在背景執行,具體能否在後臺執行取決於是否開啟了權限。 原生 Android 請關閉本 App 的“電池最佳化”MIUI / HyperOS 請修改省電策略為“無限制”。",
@@ -137,7 +144,6 @@
"pveIgnoreCertTip": "不建議啟用,請注意安全風險!如果您使用的是 PVE 的預設憑證,則需要啟用此選項。", "pveIgnoreCertTip": "不建議啟用,請注意安全風險!如果您使用的是 PVE 的預設憑證,則需要啟用此選項。",
"pveLoginFailed": "登錄失敗。無法使用伺服器配置中的使用者名稱/密碼以 Linux PAM 方式登錄。", "pveLoginFailed": "登錄失敗。無法使用伺服器配置中的使用者名稱/密碼以 Linux PAM 方式登錄。",
"pveVersionLow": "此功能目前處於測試階段,僅在 PVE 8+ 上進行過測試。請謹慎使用。", "pveVersionLow": "此功能目前處於測試階段,僅在 PVE 8+ 上進行過測試。請謹慎使用。",
"pwd": "密碼",
"read": "讀取", "read": "讀取",
"reboot": "重開", "reboot": "重開",
"rememberPwdInMem": "在記憶體中記住密碼", "rememberPwdInMem": "在記憶體中記住密碼",

View File

@@ -8,7 +8,8 @@ import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/sync.dart'; import 'package:server_box/core/sync.dart';
import 'package:server_box/data/model/app/bak/backup2.dart'; import 'package:server_box/data/model/app/bak/backup2.dart';
import 'package:server_box/data/model/app/bak/utils.dart'; import 'package:server_box/data/model/app/bak/backup_service.dart';
import 'package:server_box/data/model/app/bak/backup_source.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:server_box/data/provider/snippet.dart'; import 'package:server_box/data/provider/snippet.dart';
@@ -22,10 +23,7 @@ class BackupPage extends StatefulWidget {
@override @override
State<BackupPage> createState() => _BackupPageState(); State<BackupPage> createState() => _BackupPageState();
static const route = AppRouteNoArg( static const route = AppRouteNoArg(page: BackupPage.new, path: '/backup');
page: BackupPage.new,
path: '/backup',
);
} }
final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveClientMixin { final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveClientMixin {
@@ -40,33 +38,27 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Scaffold(body: _buildBody);
body: _buildBody(context),
);
} }
Widget _buildBody(BuildContext context) { Widget get _buildBody {
return MultiList( return MultiList(
widthDivider: 2, widthDivider: 2,
children: [ children: [
[ [
CenterGreyTitle(libL10n.sync), CenterGreyTitle(libL10n.sync),
_buildTip(), _buildTip,
if (isMacOS || isIOS) _buildIcloud(context), if (isMacOS || isIOS) _buildIcloud,
_buildWebdav(context), _buildWebdav,
_buildFile(context), _buildFile,
_buildClipboard(context), _buildClipboard,
],
[
CenterGreyTitle(libL10n.import),
_buildBulkImportServers(context),
_buildImportSnippet(context),
], ],
[CenterGreyTitle(libL10n.import), _buildBulkImportServers, _buildImportSnippet],
], ],
); );
} }
Widget _buildTip() { Widget get _buildTip {
return CardX( return CardX(
child: ListTile( child: ListTile(
leading: const Icon(Icons.warning), leading: const Icon(Icons.warning),
@@ -76,7 +68,7 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
); );
} }
Widget _buildFile(BuildContext context) { Widget get _buildFile {
return CardX( return CardX(
child: ExpandTile( child: ExpandTile(
leading: const Icon(Icons.file_open), leading: const Icon(Icons.file_open),
@@ -86,22 +78,19 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
ListTile( ListTile(
title: Text(libL10n.backup), title: Text(libL10n.backup),
trailing: const Icon(Icons.save), trailing: const Icon(Icons.save),
onTap: () async { onTap: () => BackupService.backup(context, FileBackupSource())
final path = await BackupV2.backup();
await Pfs.sharePaths(paths: [path]);
},
), ),
ListTile( ListTile(
trailing: const Icon(Icons.restore), trailing: const Icon(Icons.restore),
title: Text(libL10n.restore), title: Text(libL10n.restore),
onTap: () async => _onTapFileRestore(context), onTap: () => BackupService.restore(context, FileBackupSource()),
), ),
], ],
), ),
); );
} }
Widget _buildIcloud(BuildContext context) { Widget get _buildIcloud {
return CardX( return CardX(
child: ListTile( child: ListTile(
leading: const Icon(Icons.cloud), leading: const Icon(Icons.cloud),
@@ -123,7 +112,7 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
); );
} }
Widget _buildWebdav(BuildContext context) { Widget get _buildWebdav {
return CardX( return CardX(
child: ExpandTile( child: ExpandTile(
leading: const Icon(Icons.storage), leading: const Icon(Icons.storage),
@@ -171,33 +160,25 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
), ),
ListTile( ListTile(
title: Text(l10n.manual), title: Text(l10n.manual),
trailing: webdavLoading.listenVal( trailing: webdavLoading.listenVal((loading) {
(loading) { if (loading) return SizedLoading.small;
if (loading) return SizedLoading.small;
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
TextButton( TextButton(onPressed: () async => _onTapWebdavDl(context), child: Text(libL10n.restore)),
onPressed: () async => _onTapWebdavDl(context), UIs.width7,
child: Text(libL10n.restore), TextButton(onPressed: () async => _onTapWebdavUp(context), child: Text(libL10n.backup)),
), ],
UIs.width7, );
TextButton( }),
onPressed: () async => _onTapWebdavUp(context),
child: Text(libL10n.backup),
),
],
);
},
),
), ),
], ],
), ),
); );
} }
Widget _buildClipboard(BuildContext context) { Widget get _buildClipboard {
return CardX( return CardX(
child: ExpandTile( child: ExpandTile(
leading: const Icon(Icons.content_paste), leading: const Icon(Icons.content_paste),
@@ -206,23 +187,19 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
ListTile( ListTile(
title: Text(libL10n.backup), title: Text(libL10n.backup),
trailing: const Icon(Icons.save), trailing: const Icon(Icons.save),
onTap: () async { onTap: () => BackupService.backup(context, ClipboardBackupSource()),
final path = await BackupV2.backup();
Pfs.copy(await File(path).readAsString());
context.showSnackBar(libL10n.success);
},
), ),
ListTile( ListTile(
trailing: const Icon(Icons.restore), trailing: const Icon(Icons.restore),
title: Text(libL10n.restore), title: Text(libL10n.restore),
onTap: () async => _onTapClipboardRestore(context), onTap: () => BackupService.restore(context, ClipboardBackupSource()),
), ),
], ],
), ),
); );
} }
Widget _buildBulkImportServers(BuildContext context) { Widget get _buildBulkImportServers {
return CardX( return CardX(
child: ListTile( child: ListTile(
title: Text(l10n.server), title: Text(l10n.server),
@@ -233,16 +210,13 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
); );
} }
Widget _buildImportSnippet(BuildContext context) { Widget get _buildImportSnippet {
return ListTile( return ListTile(
title: Text(l10n.snippet), title: Text(l10n.snippet),
leading: const Icon(MingCute.code_line), leading: const Icon(MingCute.code_line),
trailing: const Icon(Icons.keyboard_arrow_right), trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () async { onTap: () async {
final data = await context.showImportDialog( final data = await context.showImportDialog(title: l10n.snippet, modelDef: Snippet.example.toJson());
title: l10n.snippet,
modelDef: Snippet.example.toJson(),
);
if (data == null) return; if (data == null) return;
final str = String.fromCharCodes(data); final str = String.fromCharCodes(data);
final (list, _) = await context.showLoadingDialog( final (list, _) = await context.showLoadingDialog(
@@ -275,11 +249,7 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
final snippetNames = snippets.map((e) => e.name).join(', '); final snippetNames = snippets.map((e) => e.name).join(', ');
context.showRoundDialog( context.showRoundDialog(
title: libL10n.attention, title: libL10n.attention,
child: SingleChildScrollView( child: SingleChildScrollView(child: Text(libL10n.askContinue('${libL10n.import} [$snippetNames]'))),
child: Text(
libL10n.askContinue('${libL10n.import} [$snippetNames]'),
),
),
actions: Btn.ok( actions: Btn.ok(
onTap: () { onTap: () {
for (final snippet in snippets) { for (final snippet in snippets) {
@@ -294,33 +264,6 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
).cardx; ).cardx;
} }
Future<void> _onTapFileRestore(BuildContext context) async {
final text = await Pfs.pickFileString();
if (text == null) return;
try {
final (backup, err) = await context.showLoadingDialog(
fn: () => Computer.shared.start(MergeableUtils.fromJsonString, text.trim()),
);
if (err != null || backup == null) return;
await context.showRoundDialog(
title: libL10n.restore,
child: Text(libL10n.askContinue(
'${libL10n.restore} ${libL10n.backup}(${backup.$2})',
)),
actions: Btn.ok(
onTap: () async {
await backup.$1.merge(force: true);
context.pop();
},
).toList,
);
} catch (e, s) {
Loggers.app.warning('Import backup failed', e, s);
context.showErrDialog(e, s, libL10n.restore);
}
}
Future<void> _onTapWebdavDl(BuildContext context) async { Future<void> _onTapWebdavDl(BuildContext context) async {
webdavLoading.value = true; webdavLoading.value = true;
@@ -328,16 +271,12 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
final files = await Webdav.shared.list(); final files = await Webdav.shared.list();
if (files.isEmpty) return context.showSnackBar(l10n.dirEmpty); if (files.isEmpty) return context.showSnackBar(l10n.dirEmpty);
final fileName = await context.showPickSingleDialog( final fileName = await context.showPickSingleDialog(title: libL10n.restore, items: files);
title: libL10n.restore,
items: files,
);
if (fileName == null) return; if (fileName == null) return;
await Webdav.shared.download(relativePath: fileName); await Webdav.shared.download(relativePath: fileName);
final dlFile = await File('${Paths.doc}/$fileName').readAsString(); final dlFile = await File('${Paths.doc}/$fileName').readAsString();
final dlBak = await Computer.shared.start(BackupV2.fromJsonString, dlFile); await BackupService.restoreFromText(context, dlFile);
await dlBak.merge(force: true);
} catch (e, s) { } catch (e, s) {
context.showErrDialog(e, s, libL10n.restore); context.showErrDialog(e, s, libL10n.restore);
Loggers.app.warning('Download webdav backup failed', e, s); Loggers.app.warning('Download webdav backup failed', e, s);
@@ -351,7 +290,8 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
final date = DateTime.now().ymdhms(ymdSep: '-', hmsSep: '-', sep: '-'); final date = DateTime.now().ymdhms(ymdSep: '-', hmsSep: '-', sep: '-');
final bakName = '$date-${Miscs.bakFileName}'; final bakName = '$date-${Miscs.bakFileName}';
try { try {
await BackupV2.backup(bakName); final savedPassword = await Stores.setting.backupasswd.read();
await BackupV2.backup(bakName, savedPassword);
await Webdav.shared.upload(relativePath: bakName); await Webdav.shared.upload(relativePath: bakName);
Loggers.app.info('Upload webdav backup success'); Loggers.app.info('Upload webdav backup success');
} catch (e, s) { } catch (e, s) {
@@ -388,7 +328,7 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodePwd), onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodePwd),
), ),
Input( Input(
label: l10n.pwd, label: libL10n.pwd,
controller: pwd, controller: pwd,
node: nodePwd, node: nodePwd,
suggestion: false, suggestion: false,
@@ -417,42 +357,9 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
} }
} }
void _onTapClipboardRestore(BuildContext context) async {
final text = await Pfs.paste();
if (text == null || text.isEmpty) {
context.showSnackBar(libL10n.empty);
return;
}
try {
final (backup, err) = await context.showLoadingDialog(
fn: () => Computer.shared.start(MergeableUtils.fromJsonString, text.trim()),
);
if (err != null || backup == null) return;
await context.showRoundDialog(
title: libL10n.restore,
child: Text(libL10n.askContinue(
'${libL10n.restore} ${libL10n.backup}(${backup.$2})',
)),
actions: Btn.ok(
onTap: () async {
await backup.$1.merge(force: true);
context.pop();
},
).toList,
);
} catch (e, s) {
Loggers.app.warning('Import backup failed', e, s);
context.showErrDialog(e, s, libL10n.restore);
}
}
void _onBulkImportServers(BuildContext context) async { void _onBulkImportServers(BuildContext context) async {
final data = await context.showImportDialog( final data = await context.showImportDialog(title: l10n.server, modelDef: Spix.example.toJson());
title: l10n.server,
modelDef: Spix.example.toJson(),
);
if (data == null) return; if (data == null) return;
final text = String.fromCharCodes(data); final text = String.fromCharCodes(data);
@@ -487,6 +394,11 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
} }
} }
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }

View File

@@ -190,7 +190,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
type: TextInputType.text, type: TextInputType.text,
node: _pwdNode, node: _pwdNode,
obscureText: true, obscureText: true,
label: l10n.pwd, label: libL10n.pwd,
icon: Icons.password, icon: Icons.password,
suggestion: false, suggestion: false,
onSubmitted: (_) => _onTapSave(), onSubmitted: (_) => _onTapSave(),

View File

@@ -208,9 +208,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
controller: _passwordController, controller: _passwordController,
obscureText: true, obscureText: true,
type: TextInputType.text, type: TextInputType.text,
label: l10n.pwd, label: libL10n.pwd,
icon: Icons.password, icon: Icons.password,
hint: l10n.pwd,
suggestion: false, suggestion: false,
onSubmitted: (_) => _onSave(), onSubmitted: (_) => _onSave(),
), ),
@@ -427,9 +426,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
controller: _wolPwdCtrl, controller: _wolPwdCtrl,
type: TextInputType.text, type: TextInputType.text,
obscureText: true, obscureText: true,
label: l10n.pwd, label: libL10n.pwd,
icon: Icons.password, icon: Icons.password,
hint: l10n.pwd,
suggestion: false, suggestion: false,
), ),
], ],

View File

@@ -497,8 +497,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.324" ref: "v1.0.327"
resolved-ref: e64d5c46844605bbd85322d7c6f250b73977cdde resolved-ref: "5075a679b814b10742f967066858ba4df92ea4ae"
url: "https://github.com/lppcg/fl_lib" url: "https://github.com/lppcg/fl_lib"
source: git source: git
version: "0.0.1" version: "0.0.1"

View File

@@ -63,7 +63,7 @@ dependencies:
fl_lib: fl_lib:
git: git:
url: https://github.com/lppcg/fl_lib url: https://github.com/lppcg/fl_lib
ref: v1.0.324 ref: v1.0.327
dependency_overrides: dependency_overrides:
# webdav_client_plus: # webdav_client_plus: