diff --git a/src/data/backup.ts b/src/data/backup.ts index d68d74c5ef..5133787f03 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -241,7 +241,7 @@ export const computeBackupAgentName = ( agentIds?: string[] ) => { if (isLocalAgent(agentId)) { - return "This system"; + return localize("ui.panel.config.backup.agents.local_agent"); } const [domain, name] = agentId.split("."); @@ -298,23 +298,22 @@ export const generateEmergencyKit = ( encryptionKey: string ) => "data:text/plain;charset=utf-8," + - encodeURIComponent(`Home Assistant Backup Emergency Kit + encodeURIComponent(`${hass.localize("ui.panel.config.backup.emergency_kit_file.title")} -This emergency kit contains your backup encryption key. You need this key -to be able to restore your Home Assistant backups. +${hass.localize("ui.panel.config.backup.emergency_kit_file.description")} -Date: ${formatDateTime(new Date(), hass.locale, hass.config)} +${hass.localize("ui.panel.config.backup.emergency_kit_file.date")} ${formatDateTime(new Date(), hass.locale, hass.config)} -Instance: +${hass.localize("ui.panel.config.backup.emergency_kit_file.instance")} ${hass.config.location_name} -URL: +${hass.localize("ui.panel.config.backup.emergency_kit_file.url")} ${hass.auth.data.hassUrl} -Encryption key: +${hass.localize("ui.panel.config.backup.emergency_kit_file.encryption_key")} ${encryptionKey} -For more information visit: https://www.home-assistant.io/more-info/backup-emergency-kit`); +${hass.localize("ui.panel.config.backup.emergency_kit_file.more_info", { link: "https://www.home-assistant.io/more-info/backup-emergency-kit" })}`); export const geneateEmergencyKitFileName = ( hass: HomeAssistant, diff --git a/src/panels/config/backup/components/config/ha-backup-config-agents.ts b/src/panels/config/backup/components/config/ha-backup-config-agents.ts index 3a1706f3d4..09216f54e3 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-agents.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-agents.ts @@ -52,12 +52,18 @@ class HaBackupConfigAgents extends LitElement { private _description(agentId: string) { if (agentId === CLOUD_AGENT) { if (this.cloudStatus.logged_in && !this.cloudStatus.active_subscription) { - return "You currently do not have an active Home Assistant Cloud subscription."; + return this.hass.localize( + "ui.panel.config.backup.agents.cloud_agent_no_subcription" + ); } - return "Note: It stores only one backup with a maximum size of 5 GB, regardless of your settings."; + return this.hass.localize( + "ui.panel.config.backup.agents.cloud_agent_description" + ); } if (isNetworkMountAgent(agentId)) { - return "Network storage"; + return this.hass.localize( + "ui.panel.config.backup.agents.network_mount_agent_description" + ); } return ""; } @@ -107,7 +113,7 @@ class HaBackupConfigAgents extends LitElement { slot="start" /> `} -
${name}
+
${name}
${description ? html`
${description}
` : nothing} @@ -124,7 +130,9 @@ class HaBackupConfigAgents extends LitElement { })} ` - : html`

No sync agents configured

`} + : html`

+ ${this.hass.localize("ui.panel.config.backup.agents.no_agents")} +

`} `; } @@ -157,6 +165,12 @@ class HaBackupConfigAgents extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } + ha-md-list-item .name { + word-break: break-word; + } ha-md-list-item img { width: 48px; } diff --git a/src/panels/config/backup/components/config/ha-backup-config-data.ts b/src/panels/config/backup/components/config/ha-backup-config-data.ts index 5c3dce968b..7d48cb6906 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-data.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-data.ts @@ -158,11 +158,17 @@ class HaBackupConfigData extends LitElement { - Home Assistant settings + + ${this.hass.localize("ui.panel.config.backup.data.ha_settings")} + ${this.forceHomeAssistant - ? "The bare minimum needed to restore the system. It is always included in automatic backup data." - : "The bare minimum needed to restore your system."} + ? this.hass.localize( + "ui.panel.config.backup.data.ha_settings_included_description" + ) + : this.hass.localize( + "ui.panel.config.backup.data.ha_settings_description" + )} - History + + ${this.hass.localize("ui.panel.config.backup.data.history")} + - Historical data of your sensors, including your energy dashboard. + ${this.hass.localize( + "ui.panel.config.backup.data.history_description" + )} - Media + + ${this.hass.localize("ui.panel.config.backup.data.media")} + - This can include large filesize camera recordings. + ${this.hass.localize( + "ui.panel.config.backup.data.history_description" + )} - Share folder + + ${this.hass.localize( + "ui.panel.config.backup.data.share_folder" + )} + - Folder that is often used by add-ons for advanced or older - configurations. + ${this.hass.localize( + "ui.panel.config.backup.data.share_folder_description" + )} - Local addons folder + + ${this.hass.localize( + "ui.panel.config.backup.data.local_addons" + )} + - Folder that contains the data of your local add-ons. + ${this.hass.localize( + "ui.panel.config.backup.data.local_addons_description" + )} - Add-ons + + ${this.hass.localize( + "ui.panel.config.backup.data.addons" + )} + - Select what add-ons you want to include. + ${this.hass.localize( + "ui.panel.config.backup.data.addons_description" + )} -
All
+
+ ${this.hass.localize( + "ui.panel.config.backup.data.addons_all" + )} +
-
None
+
+ ${this.hass.localize( + "ui.panel.config.backup.data.addons_none" + )} +
-
Custom
+
+ ${this.hass.localize( + "ui.panel.config.backup.data.addons_custom" + )} +
@@ -327,6 +370,9 @@ class HaBackupConfigData extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } ha-md-select { min-width: 210px; } diff --git a/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts b/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts index 4254215d55..f554a04230 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts @@ -26,29 +26,55 @@ class HaBackupConfigEncryptionKey extends LitElement { return html` - Download emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend to save this encryption key somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} - Show my encryption key - - Please keep your encryption key private. + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.show_encryption_key" + )} - Show + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.show_encryption_key_description" + )} + + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.show_encryption_key_action" + )} + - Change encryption key + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.change_encryption_key" + )} + - All next backups will use this encryption key. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.change_encryption_key_description" + )} - Change + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.change_encryption_key_action" + )} @@ -58,11 +84,21 @@ class HaBackupConfigEncryptionKey extends LitElement { return html` - Set encryption key + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.set_encryption_key" + )} - Set an encryption key for your backups. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.set_encryption_key_description" + )} - Set + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.set_encryption_key_action" + )} `; @@ -102,6 +138,9 @@ class HaBackupConfigEncryptionKey extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } .danger { --mdc-theme-primary: var(--error-color); } diff --git a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts index b2402625e8..2e7ba79063 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts @@ -43,6 +43,23 @@ const RETENTION_PRESETS: Record< forever: { type: "days", value: 0 }, }; +const SCHEDULE_OPTIONS = [ + BackupScheduleState.DAILY, + BackupScheduleState.MONDAY, + BackupScheduleState.TUESDAY, + BackupScheduleState.WEDNESDAY, + BackupScheduleState.THURSDAY, + BackupScheduleState.FRIDAY, + BackupScheduleState.SATURDAY, + BackupScheduleState.SUNDAY, +] as const satisfies BackupScheduleState[]; + +const RETENTION_PRESETS_OPTIONS = [ + RetentionPreset.COPIES_3, + RetentionPreset.FOREVER, + RetentionPreset.CUSTOM, +] as const satisfies RetentionPreset[]; + const computeRetentionPreset = ( data: RetentionData ): RetentionPreset | undefined => { @@ -128,7 +145,11 @@ class HaBackupConfigSchedule extends LitElement { return html` - Use automatic backups + + ${this.hass.localize( + "ui.panel.config.backup.schedule.use_automatic_backups" + )} + - Schedule + + ${this.hass.localize( + "ui.panel.config.backup.schedule.schedule" + )} + - How often you want to create a backup. + ${this.hass.localize( + "ui.panel.config.backup.schedule.schedule_description" + )} - -
Daily at ${time}
-
- -
Monday at ${time}
-
- -
Tuesday at ${time}
-
- -
Wednesday at ${time}
-
- -
Thursday at ${time}
-
- -
Friday at ${time}
-
- -
Saturday at ${time}
-
- -
Sunday at ${time}
-
+ ${SCHEDULE_OPTIONS.map( + (option) => html` + +
+ ${this.hass.localize( + `ui.panel.config.backup.schedule.schedule_options.${option}`, + { time } + )} +
+
+ ` + )}
- Backups to keep + + ${this.hass.localize( + `ui.panel.config.backup.schedule.retention` + )} + - Based on the maximum number of backups or how many days they - should be kept. + ${this.hass.localize( + `ui.panel.config.backup.schedule.retention_description` + )} - -
3 backups
-
- -
All backups
-
- -
Custom
-
+ ${RETENTION_PRESETS_OPTIONS.map( + (option) => html` + +
+ ${this.hass.localize( + `ui.panel.config.backup.schedule.retention_presets.${option}` + )} +
+
+ ` + )}
${this._retentionPreset === RetentionPreset.CUSTOM @@ -217,11 +239,17 @@ class HaBackupConfigSchedule extends LitElement { .value=${data.retention.type} id="type" > - -
days
+ +
+ ${this.hass.localize( + "ui.panel.config.backup.schedule.retention_units.days" + )} +
- -
backups
+ + ${this.hass.localize( + "ui.panel.config.backup.schedule.retention_units.copies" + )} @@ -320,6 +348,9 @@ class HaBackupConfigSchedule extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } ha-md-select { min-width: 210px; } diff --git a/src/panels/config/backup/components/ha-backup-data-picker.ts b/src/panels/config/backup/components/ha-backup-data-picker.ts index fbb944322b..4b5cfa86df 100644 --- a/src/panels/config/backup/components/ha-backup-data-picker.ts +++ b/src/panels/config/backup/components/ha-backup-data-picker.ts @@ -77,7 +77,11 @@ export class HaBackupDataPicker extends LitElement { if (data.homeassistant_included) { items.push({ - label: `Settings${data.database_included ? " and history" : ""}`, + label: data.database_included + ? this.hass.localize( + "ui.panel.config.backup.data_picker.settings_and_history" + ) + : this.hass.localize("ui.panel.config.backup.data_picker.settings"), id: "config", version: data.homeassistant_version, }); @@ -93,8 +97,17 @@ export class HaBackupDataPicker extends LitElement { ); private _localizeFolder(folder: string): string { - if (folder === "addons/local") { - return "Local addons"; + switch (folder) { + case "media": + return this.hass.localize("ui.panel.config.backup.data_picker.media"); + case "share": + return this.hass.localize( + "ui.panel.config.backup.data_picker.share_folder" + ); + case "addons/local": + return this.hass.localize( + "ui.panel.config.backup.data_picker.local_addons" + ); } return capitalizeFirstLetter(folder); } @@ -219,7 +232,7 @@ export class HaBackupDataPicker extends LitElement { @@ -265,7 +278,9 @@ export class HaBackupDataPicker extends LitElement { diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts index a667e1a4bc..43b93be39f 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts @@ -54,7 +54,9 @@ class HaBackupOverviewBackups extends LitElement { return html` -
My backups
+
+ ${this.hass.localize("ui.panel.config.backup.overview.backups.title")} +
- ${automaticStats.count} automatic backups + ${this.hass.localize( + "ui.panel.config.backup.overview.backups.automatic", + { count: automaticStats.count } + )}
- ${bytesToString(automaticStats.size, 1)} in total + ${this.hass.localize( + "ui.panel.config.backup.overview.backups.total_size", + { size: bytesToString(automaticStats.size, 1) } + )}
@@ -75,9 +83,17 @@ class HaBackupOverviewBackups extends LitElement { href="/config/backup/backups?type=manual" > -
${manualStats.count} manual backups
+
+ ${this.hass.localize( + "ui.panel.config.backup.overview.backups.automatic", + { count: manualStats.count } + )} +
- ${bytesToString(manualStats.size, 1)} in total + ${this.hass.localize( + "ui.panel.config.backup.overview.backups.total_size", + { size: bytesToString(manualStats.size, 1) } + )}
@@ -85,7 +101,11 @@ class HaBackupOverviewBackups extends LitElement {
diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts index a094cad22d..277c12a8d1 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts @@ -31,17 +31,23 @@ class HaBackupOverviewBackups extends LitElement {
- Set up backups + ${this.hass.localize( + "ui.panel.config.backup.overview.onboarding.title" + )}

- Backups are essential for a reliable smart home. They help protect - the work you've put into setting up your smart home, and if the - worst happens, you can get back up and running quickly. + ${this.hass.localize( + "ui.panel.config.backup.overview.onboarding.description" + )}

- Set up backups + ${this.hass.localize( + "ui.panel.config.backup.overview.onboarding.setup" + )}
`; diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts b/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts index b9bfeffff6..223a351325 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts @@ -11,73 +11,39 @@ export class HaBackupOverviewProgress extends LitElement { @property({ attribute: false }) public manager!: ManagerStateEvent; private get _heading() { - switch (this.manager.manager_state) { - case "create_backup": - return "Creating backup"; - case "restore_backup": - return "Restoring backup"; - case "receive_backup": - return "Receiving backup"; - default: - return ""; + const state = this.manager.manager_state; + if (state === "idle") { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.heading.${state}` + ); } private get _description() { switch (this.manager.manager_state) { case "create_backup": - switch (this.manager.stage) { - case "addon_repositories": - case "addons": - return "Backing up add-ons"; - case "await_addon_restarts": - return "Waiting for add-ons to restart"; - case "docker_config": - return "Backing up Docker configuration"; - case "finishing_file": - return "Finishing backup file"; - case "folders": - return "Backing up folders"; - case "home_assistant": - return "Backing up Home Assistant"; - case "upload_to_agents": - return "Uploading to locations"; - default: - return ""; + if (!this.manager.stage) { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.create_backup.${this.manager.stage}` + ); case "restore_backup": - switch (this.manager.stage) { - case "addon_repositories": - case "addons": - return "Restoring add-ons"; - case "await_addon_restarts": - return "Waiting for add-ons to restart"; - case "await_home_assistant_restart": - return "Waiting for Home Assistant to restart"; - case "check_home_assistant": - return "Checking Home Assistant"; - case "docker_config": - return "Restoring Docker configuration"; - case "download_from_agent": - return "Downloading from location"; - case "folders": - return "Restoring folders"; - case "home_assistant": - return "Restoring Home Assistant"; - case "remove_delta_addons": - return "Removing delta add-ons"; - default: - return ""; + if (!this.manager.stage) { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.restore_backup.${this.manager.stage}` + ); + case "receive_backup": - switch (this.manager.stage) { - case "receive_file": - return "Receiving file"; - case "upload_to_agents": - return "Uploading to locations"; - default: - return ""; + if (!this.manager.stage) { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.receive_backup.${this.manager.stage}` + ); default: return ""; } diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts index 3b3f56cdfb..97d13ca84c 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts @@ -34,42 +34,32 @@ class HaBackupBackupsSummary extends LitElement { const { state: schedule } = config.schedule; if (schedule === BackupScheduleState.NEVER) { - return "Automatic backups are not scheduled"; - } - - let copiesText = "and keep all backups"; - if (copies) { - copiesText = `and keep the latest ${copies} backup(s)`; - } else if (days) { - copiesText = `and keep backups for ${days} day(s)`; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.schedule_never" + ); } const time = getFormattedBackupTime(this.hass.locale, this.hass.config); - let scheduleText = ""; - if (schedule === BackupScheduleState.DAILY) { - scheduleText = `Daily at ${time}`; - } - if (schedule === BackupScheduleState.MONDAY) { - scheduleText = `Weekly on Mondays at ${time}`; - } - if (schedule === BackupScheduleState.TUESDAY) { - scheduleText = `Weekly on Tuesdays at ${time}`; - } - if (schedule === BackupScheduleState.WEDNESDAY) { - scheduleText = `Weekly on Wednesdays at ${time}`; - } - if (schedule === BackupScheduleState.THURSDAY) { - scheduleText = `Weekly on Thursdays at ${time}`; - } - if (schedule === BackupScheduleState.FRIDAY) { - scheduleText = `Weekly on Fridays at ${time}`; - } - if (schedule === BackupScheduleState.SATURDAY) { - scheduleText = `Weekly on Saturdays at ${time}`; - } - if (schedule === BackupScheduleState.SUNDAY) { - scheduleText = `Weekly on Sundays at ${time}`; + const scheduleText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_${schedule}`, + { time } + ); + + let copiesText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_copies_all`, + { time } + ); + if (copies) { + copiesText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_copies_backups`, + { count: copies } + ); + } else if (days) { + copiesText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_copies_days`, + { count: days } + ); } return scheduleText + " " + copiesText; @@ -77,15 +67,23 @@ class HaBackupBackupsSummary extends LitElement { private _addonsDescription(config: BackupConfig): string { if (config.create_backup.include_all_addons) { - return "All add-ons"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.addons_all" + ); } - if (config.create_backup.include_addons?.length) { - return `${config.create_backup.include_addons.length} add-ons`; + const count = config.create_backup.include_addons?.length; + if (count) { + return this.hass.localize( + "ui.panel.config.backup.overview.settings.addons_many", + { count } + ); } - return "No add-ons"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.addons_none" + ); } - private _agentsDescription(config: BackupConfig): string { + private _locationsDescription(config: BackupConfig): string { const hasLocal = config.create_backup.agent_ids.some((a) => isLocalAgent(a) ); @@ -101,14 +99,24 @@ class HaBackupBackupsSummary extends LitElement { offsiteLocations[0], offsiteLocations ); - return `Upload to ${name}`; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_one", + { name } + ); } - return `Upload to ${offsiteLocations.length} off-site locations`; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_many", + { count: offsiteLocations.length } + ); } if (hasLocal) { - return "Local backup only"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_local_only" + ); } - return "No location configured"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_none" + ); } render() { @@ -116,7 +124,11 @@ class HaBackupBackupsSummary extends LitElement { return html` -
Backup settings
+
+ ${this.hass.localize( + "ui.panel.config.backup.overview.settings.title" + )} +
- Automatic backup schedule and retention + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.schedule" + )}
@@ -136,11 +150,17 @@ class HaBackupBackupsSummary extends LitElement {
${this.config.create_backup.include_database - ? "Settings and history" - : "Settings only"} + ? this.hass.localize( + "ui.panel.config.backup.overview.settings.data_settings_history" + ) + : this.hass.localize( + "ui.panel.config.backup.overview.settings.data_settings_only" + )}
- Home Assistant data that is included + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.data" + )}
@@ -154,7 +174,11 @@ class HaBackupBackupsSummary extends LitElement {
${this._addonsDescription(this.config)}
-
Add-ons that are included
+
+ ${this.hass.localize( + "ui.panel.config.backup.overview.settings.addons" + )} +
` @@ -164,9 +188,13 @@ class HaBackupBackupsSummary extends LitElement { href="/config/backup/settings#locations" > -
${this._agentsDescription(this.config)}
+
+ ${this._locationsDescription(this.config)} +
- Locations where backup is uploaded to + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.locations" + )}
@@ -174,7 +202,9 @@ class HaBackupBackupsSummary extends LitElement {
- Configure backup settings + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.configure" + )}
diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index 42ed30d5f5..09c12c0831 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -49,37 +49,17 @@ class HaBackupOverviewBackups extends LitElement { ); }); - private _nextBackupDescription(schedule: BackupScheduleState) { - const time = getFormattedBackupTime(this.hass.locale, this.hass.config); - - switch (schedule) { - case BackupScheduleState.DAILY: - return `Next automatic backup tomorrow at ${time}`; - case BackupScheduleState.MONDAY: - return `Next automatic backup next Monday at ${time}`; - case BackupScheduleState.TUESDAY: - return `Next automatic backup next Thuesday at ${time}`; - case BackupScheduleState.WEDNESDAY: - return `Next automatic backup next Wednesday at ${time}`; - case BackupScheduleState.THURSDAY: - return `Next automatic backup next Thursday at ${time}`; - case BackupScheduleState.FRIDAY: - return `Next automatic backup next Friday at ${time}`; - case BackupScheduleState.SATURDAY: - return `Next automatic backup next Saturday at ${time}`; - case BackupScheduleState.SUNDAY: - return `Next automatic backup next Sunday at ${time}`; - default: - return "No automatic backup scheduled"; - } - } - protected render() { const now = new Date(); if (this.fetching) { return html` - + @@ -96,8 +76,14 @@ class HaBackupOverviewBackups extends LitElement { const lastBackup = this._lastBackup(this.backups); - const nextBackupDescription = this._nextBackupDescription( - this.config.schedule.state + const backupTime = getFormattedBackupTime( + this.hass.locale, + this.hass.config + ); + + const nextBackupDescription = this.hass.localize( + `ui.panel.config.backup.overview.summary.next_backup_description.${this.config.schedule.state}`, + { time: backupTime } ); const lastAttemptDate = this.config.last_attempted_automatic_backup @@ -110,25 +96,50 @@ class HaBackupOverviewBackups extends LitElement { // If last attempt is after last completed backup, show error if (lastAttemptDate > lastCompletedDate) { - const description = `The last automatic backup triggered ${relativeTime(lastAttemptDate, this.hass.locale, now, true)} wasn't successful.`; const lastUploadedBackup = this._lastUploadedBackup(this.backups); - const secondaryDescription = lastUploadedBackup - ? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.` - : nextBackupDescription; return html` - ${description} + + ${this.hass.localize( + "ui.panel.config.backup.overview.summary.last_backup_failed_description", + { + relative_time: relativeTime( + lastAttemptDate, + this.hass.locale, + now, + true + ), + } + )} + - ${secondaryDescription} + + ${lastUploadedBackup + ? this.hass.localize( + "ui.panel.config.backup.overview.summary.last_successful_backup_description", + { + relative_time: relativeTime( + new Date(lastUploadedBackup.date), + this.hass.locale, + now, + true + ), + count: lastUploadedBackup.agent_ids?.length ?? 0, + } + ) + : nextBackupDescription} + @@ -137,16 +148,21 @@ class HaBackupOverviewBackups extends LitElement { // If no backups yet, show warning if (!lastBackup) { - const description = "You have no automatic backups yet."; return html` - ${description} + + ${this.hass.localize( + "ui.panel.config.backup.overview.summary.no_backup_description" + )} + @@ -161,32 +177,68 @@ class HaBackupOverviewBackups extends LitElement { // If last backup if (lastBackup.failed_agent_ids?.length) { - const description = `The last automatic backup created ${relativeTime(lastBackupDate, this.hass.locale, now, true)} wasn't stored in all locations.`; const lastUploadedBackup = this._lastUploadedBackup(this.backups); - const secondaryDescription = lastUploadedBackup - ? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.` - : nextBackupDescription; return html` - ${description} + + ${this.hass.localize( + "ui.panel.config.backup.overview.summary.last_backup_failed_locations_description", + { + relative_time: relativeTime( + lastAttemptDate, + this.hass.locale, + now, + true + ), + } + )} + - ${secondaryDescription} + + ${lastUploadedBackup + ? this.hass.localize( + "ui.panel.config.backup.overview.summary.last_successful_backup_description", + { + relative_time: relativeTime( + new Date(lastUploadedBackup.date), + this.hass.locale, + now, + true + ), + count: lastUploadedBackup.agent_ids?.length ?? 0, + } + ) + : nextBackupDescription} + `; } - const description = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored in ${lastBackup.agent_ids?.length} locations.`; + const lastSuccessfulBackupDescription = this.hass.localize( + "ui.panel.config.backup.overview.summary.last_successful_backup_description", + { + relative_time: relativeTime( + new Date(lastBackup.date), + this.hass.locale, + now, + true + ), + count: lastBackup.agent_ids?.length ?? 0, + } + ); const numberOfDays = differenceInDays( // Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving) @@ -202,13 +254,15 @@ class HaBackupOverviewBackups extends LitElement { if (isOverdue) { return html` - ${description} + ${lastSuccessfulBackupDescription} @@ -220,11 +274,16 @@ class HaBackupOverviewBackups extends LitElement { } return html` - + - ${description} + ${lastSuccessfulBackupDescription} diff --git a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts index ab66157e46..3944e1316e 100644 --- a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts +++ b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts @@ -224,7 +224,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { @click=${this._done} .disabled=${!this._isStepValid()} > - Save and create backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.save_and_create" + )} ` : html` @@ -232,7 +234,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { @click=${this._nextStep} .disabled=${!this._isStepValid()} > - Next + ${this.hass.localize("ui.common.next")} `} @@ -244,18 +246,14 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { private get _stepTitle(): string { switch (this._step) { - case "welcome": - return ""; case "key": - return "Encryption key"; case "setup": - return "Set up your automatic backups"; case "schedule": - return "Automatic backups"; case "data": - return "Backup data"; case "locations": - return "Locations"; + return this.hass.localize( + `ui.panel.config.backup.dialogs.onboarding.${this._step}.title` + ); default: return ""; } @@ -291,23 +289,24 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { src="/static/images/voice-assistant/hi.png" alt="Casita Home Assistant logo" /> -

Set up backups

+

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.welcome.title" + )} +

- Backups are essential for a reliable smart home. They help protect - the work you've put into setting up your smart home, and if the - worst happens, you can get back up and running quickly. It is - recommended that you create a backup every day. You should keep - three backups in at least two different locations, one of which - should be off-site. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.welcome.description" + )}

`; case "key": return html`

- All your backups are encrypted to keep your data private and secure. - We recommend to save this key somewhere secure. As you can only - restore your data with the backup encryption key. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.key.description" + )}

${this._config.create_backup.password}

@@ -318,13 +317,21 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
- Download emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend to save this encryption key somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} @@ -333,16 +340,28 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { return html` - Recommended settings + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.recommended_heading" + )} + - Backup everything daily, keeping three days of backups + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.recommended_description" + )} - Custom settings + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.custom_heading" + )} + - Select when, where, and what to backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.custom_description" + )} @@ -351,8 +370,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { case "schedule": return html`

- Let Home Assistant take care of your backups by creating a scheduled - backup that also removes older backups. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.schedule.description" + )}

- Choose what data to include in your backups. You can always change - this later. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.data.description" + )}

- Home Assistant will upload to these locations when an automatic - backup is made. You can use all locations for manual backups. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.locations.description" + )}

@@ -119,7 +117,11 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
${this._renderStepContent()}
${this._step === "current" - ? html`Next` + ? html` + + ${this.hass.localize("ui.common.next")} + + ` : this._step === "new" ? html` - Change encryption key + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.actions.change" + )} ` - : html`Done`} + : html` + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.actions.done" + )} + + `}
`; @@ -141,9 +151,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { case "current": return html`

- Make sure you have saved the current encryption key to make sure you - have access to all your current backups. All next backups will use - the new encryption key. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.current.description" + )}

${this._params?.currentKey}

@@ -154,13 +164,21 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
- Download old emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_old_emergency_kit" + )} + - We recommend saving this encryption key file somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_old_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_old_emergency_kit_action" + )} @@ -168,22 +186,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { case "new": return html`

- All next backups will use the new encryption key. Encryption keeps - your backups private and secure. -

-
-

${this._newEncryptionKey}

- -
- `; - case "done": - return html`

- Keep this new encryption key in a safe place, as you will need it to - access your backups, allowing it to be restored. Either record the - characters below or download them as an emergency kit file. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.new.description" + )}

${this._newEncryptionKey}

@@ -194,16 +199,39 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
- Download new emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend saving this encryption key file somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} - `; +
+ `; + case "done": + return html` +
+ Casita Home Assistant logo +

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.done.title" + )} +

+
+ `; } return nothing; } @@ -306,6 +334,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { p { margin-top: 0; } + .done { + text-align: center; + } `, ]; } diff --git a/src/panels/config/backup/dialogs/dialog-generate-backup.ts b/src/panels/config/backup/dialogs/dialog-generate-backup.ts index b8c64c1255..9334d7e191 100644 --- a/src/panels/config/backup/dialogs/dialog-generate-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-generate-backup.ts @@ -164,8 +164,9 @@ class DialogGenerateBackup extends LitElement implements HassDialog { return nothing; } - const dialogTitle = - this._step === "sync" ? "Synchronization" : "Backup data"; + const dialogTitle = this.hass.localize( + `ui.panel.config.backup.dialogs.generate.${this._step}.title` + ); const isFirstStep = this._step === STEPS[0]; const isLastStep = this._step === STEPS[STEPS.length - 1]; @@ -197,7 +198,11 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
${isFirstStep - ? html`Cancel` + ? html` + + ${this.hass.localize("ui.common.cancel")} + + ` : nothing} ${isLastStep ? html` @@ -206,14 +211,19 @@ class DialogGenerateBackup extends LitElement implements HassDialog { .disabled=${this._formData.agents_mode === "custom" && !selectedAgents.length} > - Create backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.actions.create" + )} ` - : html`Next`} + : html` + + ${this.hass.localize("ui.common.next")} + + `}
`; @@ -266,16 +276,24 @@ class DialogGenerateBackup extends LitElement implements HassDialog { return html` - Backup locations + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations" + )} + - What locations you want to automatically backup to. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations_description" + )} -
All (${this._agentIds.length})
+
+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations_options.all", + { count: this._agentIds.length } + )} +
-
Custom
+
+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations_options.custom" + )} +
@@ -299,16 +326,25 @@ class DialogGenerateBackup extends LitElement implements HassDialog { ? html` - Add Home Assistant settings data to synchronize this backup to - Home Assistant Cloud. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.ha_cloud_alert.description" + )} ` : nothing} ${this._formData.agents_mode === "custom" ? html` - + - Backup now + + ${this.hass.localize("ui.panel.config.backup.dialogs.new.title")} +
@@ -76,17 +80,29 @@ class DialogNewBackup extends LitElement implements HassDialog { .disabled=${!this._params.config.create_backup.password} > - Automatic backup + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.automatic.description" + )} + - Create a backup with the data and locations you have configured. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.automatic.description" + )} - Manual backup + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.manual.title" + )} + - Select data and locations for a manual backup. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.manual.description" + )} diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index 588767b1dc..2620cf5ec5 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -122,7 +122,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { return nothing; } - const dialogTitle = "Restore backup"; + const dialogTitle = this.hass.localize( + "ui.panel.config.backup.dialogs.restore.title" + ); return html` @@ -146,7 +148,11 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
${this._error - ? html`Close` + ? html` + + ${this.hass.localize("ui.common.close")} + + ` : this._step === "confirm" || this._step === "encryption" ? this._renderConfirmActions() : nothing} @@ -156,40 +162,71 @@ class DialogRestoreBackup extends LitElement implements HassDialog { } private _renderConfirm() { - return html`

- Your backup will be restored and all current data will be overwritten. - Depending on the size of the backup, this can take a while. -

`; + return html` +

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.confirm.description" + )} +

+ `; + } + + private _renderEncryptionIntro() { + if (this._usedUserInput) { + return html` + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.incorrect_key" + )} + `; + } + if (this._backupEncryptionKey) { + return html` + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.different_key" + )} + ${this._params!.selectedData.homeassistant_included + ? html` + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.warning" + )} + + ` + : nothing} + `; + } + return html` + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.description" + )} + `; } private _renderEncryption() { - return html`${this._usedUserInput - ? "The provided encryption key was incorrect, please try again." - : this._backupEncryptionKey - ? html`The Backup is encrypted with a different encryption key than - that is saved on this system. Please enter the encryption key for - this backup.
- ${this._params!.selectedData.homeassistant_included - ? html`After restoring the backup, your new backups will be - encrypted with the encryption key that was present during - the time of this backup.` - : nothing}` - : "The backup is encrypted. Provide the encryption key to decrypt the backup."} + return html` + ${this._renderEncryptionIntro()} `; + > + `; } private _renderConfirmActions() { - return html`Cancel - Restore`; + return html` + + ${this.hass.localize("ui.common.cancel")} + + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.actions.restore" + )} + + `; } private _renderProgress() { @@ -198,7 +235,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog {

${this.hass.connected ? this._restoreState() - : "Restarting Home Assistant"} + : this.hass.localize( + "ui.panel.config.backup.dialogs.restore.progress.restarting" + )}

`; } @@ -245,7 +284,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this.closeDialog(); } if (event.state === "failed") { - this._error = "Backup restore failed"; + this._error = this.hass.localize( + "ui.panel.config.backup.dialogs.restore.restore_failed" + ); } if (event.state === "in_progress") { this._stage = event.stage; @@ -263,29 +304,14 @@ class DialogRestoreBackup extends LitElement implements HassDialog { } private _restoreState() { - switch (this._stage) { - case "addon_repositories": - return "Restoring add-on repositories"; - case "addons": - return "Restoring add-ons"; - case "await_addon_restarts": - return "Waiting for add-ons to restart"; - case "await_home_assistant_restart": - return "Waiting for Home Assistant to restart"; - case "check_home_assistant": - return "Checking Home Assistant configuration"; - case "docker_config": - return "Restoring Docker configuration"; - case "download_from_agent": - return "Downloading backup"; - case "folders": - return "Restoring folders"; - case "home_assistant": - return "Restoring Home Assistant"; - case "remove_delta_addons": - return "Removing add-ons that are no longer in the backup"; + if (!this._stage) { + return this.hass.localize( + "ui.panel.config.backup.dialogs.restore.progress.restoring" + ); } - return "Restoring backup"; + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.restore_backup.${this._stage}` + ); } private async _doRestoreBackup(password?: string) { diff --git a/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts index 956b969dc1..03ed6c0b87 100644 --- a/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts +++ b/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts @@ -1,11 +1,13 @@ -import { mdiClose, mdiDownload, mdiKey } from "@mdi/js"; +import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { copyToClipboard } from "../../../../common/util/copy-clipboard"; import "../../../../components/ha-button"; import "../../../../components/ha-dialog-header"; import "../../../../components/ha-icon-button"; +import "../../../../components/ha-icon-button-prev"; import "../../../../components/ha-md-dialog"; import type { HaMdDialog } from "../../../../components/ha-md-dialog"; import "../../../../components/ha-md-list"; @@ -18,9 +20,12 @@ import { import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyle, haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; +import { showToast } from "../../../../util/toast"; import type { SetBackupEncryptionKeyDialogParams } from "./show-dialog-set-backup-encryption-key"; -const STEPS = ["new", "save"] as const; +const STEPS = ["key", "done"] as const; + +type Step = (typeof STEPS)[number]; @customElement("ha-dialog-set-backup-encryption-key") class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { @@ -28,7 +33,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { @state() private _opened = false; - @state() private _step?: "new" | "save"; + @state() private _step?: Step; @state() private _params?: SetBackupEncryptionKeyDialogParams; @@ -36,13 +41,11 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { @state() private _newEncryptionKey?: string; - private _suggestedEncryptionKey?: string; - public showDialog(params: SetBackupEncryptionKeyDialogParams): void { this._params = params; this._step = STEPS[0]; this._opened = true; - this._suggestedEncryptionKey = generateEncryptionKey(); + this._newEncryptionKey = generateEncryptionKey(); } public closeDialog(): void { @@ -56,7 +59,6 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { this._step = undefined; this._params = undefined; this._newEncryptionKey = undefined; - this._suggestedEncryptionKey = undefined; } private _done() { @@ -78,7 +80,11 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { } const dialogTitle = - this._step === "new" ? "Encryption key" : "Save new encryption key"; + this._step === "key" + ? this.hass.localize( + `ui.panel.config.backup.dialogs.set_encryption_key.key.title` + ) + : ""; return html` @@ -93,18 +99,24 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
${this._renderStepContent()}
- ${this._step === "new" + ${this._step === "key" ? html` - Next + ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.actions.set" + )} ` - : this._step === "save" - ? html`Done` - : nothing} + : html` + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.actions.done" + )} + + `}
`; @@ -112,69 +124,76 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { private _renderStepContent() { switch (this._step) { - case "new": + case "key": return html`

- All your backups are encrypted to keep your data private and secure. - You need this encryption key to restore any backup. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.new.description" + )}

- +
+

${this._newEncryptionKey}

+ +
- - Use suggested encryption key - - ${this._suggestedEncryptionKey} + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} - - Enter + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} + + + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} `; - case "save": + case "done": return html` -

- It’s important that you don’t lose this encryption key. We recommend - to save this key somewhere secure. As you can only restore your data - with the backup encryption key. -

- - - Download emergency kit - - We recommend to save this encryption key somewhere secure. - - - - Download - - - +
+ Casita Home Assistant logo +

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.done.title" + )} +

+
`; } return nothing; } - private _downloadNew() { + private async _copyKeyToClipboard() { + await copyToClipboard( + this._newEncryptionKey, + this.renderRoot.querySelector("div")! + ); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); + } + + private _download() { if (!this._newEncryptionKey) { return; } downloadEmergencyKit(this.hass, this._newEncryptionKey); } - private _encryptionKeyChanged(ev) { - this._newEncryptionKey = ev.target.value; - } - - private _useSuggestedEncryptionKey() { - this._newEncryptionKey = this._suggestedEncryptionKey; - } - private async _submit() { if (!this._newEncryptionKey) { return; @@ -190,7 +209,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { css` ha-md-dialog { width: 90vw; - max-width: 500px; + max-width: 560px; --dialog-content-padding: 8px 24px; } ha-md-list { @@ -198,6 +217,30 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + .encryption-key { + border: 1px solid var(--divider-color); + background-color: var(--primary-background-color); + border-radius: 8px; + padding: 16px; + display: flex; + flex-direction: row; + align-items: center; + gap: 24px; + } + .encryption-key p { + margin: 0; + flex: 1; + font-family: "Roboto Mono", "Consolas", "Menlo", monospace; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 28px; + text-align: center; + } + .encryption-key ha-icon-button { + flex: none; + margin: -16px; + } @media all and (max-width: 450px), all and (max-height: 500px) { ha-md-dialog { max-width: none; @@ -209,6 +252,9 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { p { margin-top: 0; } + .done { + text-align: center; + } `, ]; } diff --git a/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts index 5861ad35a7..283d4fc082 100644 --- a/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts +++ b/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts @@ -55,12 +55,17 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog { .path=${mdiClose} @click=${this._closeDialog} > - Encryption key + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.show_encryption_key.title" + )} +

- Make sure you save the encryption key in a secure place so always - have access to your backups. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.show_encryption_key.description" + )}

${this._params?.currentKey}

@@ -71,19 +76,29 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
- Download emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend saving this encryption key file somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )}
- Close + + ${this.hass.localize("ui.dialogs.generic.close")} +
`; @@ -124,9 +139,6 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } - ha-button.danger { - --mdc-theme-primary: var(--error-color); - } .encryption-key { border: 1px solid var(--divider-color); background-color: var(--primary-background-color); diff --git a/src/panels/config/backup/dialogs/dialog-upload-backup.ts b/src/panels/config/backup/dialogs/dialog-upload-backup.ts index e1258a0f51..09e5e5b5d6 100644 --- a/src/panels/config/backup/dialogs/dialog-upload-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-upload-backup.ts @@ -86,7 +86,9 @@ export class DialogUploadBackup @click=${this.closeDialog} > - Upload backup + + ${this.hass.localize("ui.panel.config.backup.dialogs.upload.title")} +
${this._error @@ -97,15 +99,21 @@ export class DialogUploadBackup .uploading=${this._uploading} .icon=${mdiFolderUpload} accept=${SUPPORTED_FORMAT} - label="Select backup file" - supports="Supports .tar files" + .label=${this.hass.localize( + "ui.panel.config.backup.dialogs.upload.input_label" + )} + .supports=${this.hass.localize( + "ui.panel.config.backup.dialogs.upload.supports_tar" + )} @file-picked=${this._filePicked} >
Cancel - Upload backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.upload.action" + )}
@@ -126,9 +134,13 @@ export class DialogUploadBackup const { file } = this._formData!; if (!file || file.type !== SUPPORTED_FORMAT) { showAlertDialog(this, { - title: "Unsupported file format", - text: "Please choose a Home Assistant backup file (.tar)", - confirmText: "ok", + title: this.hass.localize( + "ui.panel.config.backup.dialogs.upload.unsupported.title" + ), + text: this.hass.localize( + "ui.panel.config.backup.dialogs.upload.unsupported.text" + ), + confirmText: this.hass.localize("ui.common.ok"), }); return; } diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index 18d65557f0..18682cd054 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -17,7 +17,6 @@ import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event"; import { computeDomain } from "../../../common/entity/compute_domain"; import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; import { navigate } from "../../../common/navigate"; -import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter"; import type { LocalizeFunc } from "../../../common/translations/localize"; import type { DataTableColumnContainer, @@ -70,9 +69,9 @@ interface BackupRow extends DataTableRowData, BackupContent { formatted_type: string; } -type BackupType = "automatic" | "manual" | "imported"; +type BackupType = "automatic" | "manual"; -const TYPE_ORDER: Array = ["automatic", "manual", "imported"]; +const TYPE_ORDER: Array = ["automatic", "manual"]; @customElement("ha-config-backup-backups") class HaConfigBackupBackups extends SubscribeMixin(LitElement) { @@ -158,13 +157,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { relativeTime(new Date(backup.date), this.hass.locale), }, formatted_type: { - title: "Type", + title: localize("ui.panel.config.backup.backup_type"), filterable: true, sortable: true, groupable: true, }, locations: { - title: "Locations", + title: localize("ui.panel.config.backup.locations"), showNarrow: true, minWidth: "60px", template: (backup) => html` @@ -246,10 +245,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { }) ); - private _groupOrder = memoizeOne((activeGrouping: string | undefined) => - activeGrouping === "formatted_type" - ? TYPE_ORDER.map((type) => this._formatBackupType(type)) - : undefined + private _groupOrder = memoizeOne( + (activeGrouping: string | undefined, localize: LocalizeFunc) => + activeGrouping === "formatted_type" + ? TYPE_ORDER.map((type) => + localize(`ui.panel.config.backup.type.${type}`) + ) + : undefined ); private _handleGroupingChanged(ev: CustomEvent) { @@ -266,15 +268,11 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { this._selected = ev.detail.value; } - private _formatBackupType(type: BackupType): string { - // Todo translate - return capitalizeFirstLetter(type); - } - private _data = memoizeOne( ( backups: BackupContent[], - filters: DataTableFiltersValues + filters: DataTableFiltersValues, + localize: LocalizeFunc ): BackupRow[] => { const typeFilter = filters["ha-filter-states"] as string[] | undefined; let filteredBackups = backups; @@ -286,12 +284,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { (!backup.with_automatic_settings && typeFilter.includes("manual")) ); } - return filteredBackups.map((backup) => ({ - ...backup, - formatted_type: this._formatBackupType( - backup.with_automatic_settings ? "automatic" : "manual" - ), - })); + return filteredBackups.map((backup) => { + const type = backup.with_automatic_settings ? "automatic" : "manual"; + return { + ...backup, + formatted_type: localize(`ui.panel.config.backup.type.${type}`), + }; + }); } ); @@ -304,7 +303,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { has-fab .tabs=${[ { - name: "My backups", + name: this.hass.localize("ui.panel.config.backup.backups.header"), path: `/config/backup/list`, }, ]} @@ -326,14 +325,17 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { .selected=${this._selected.length} .initialGroupColumn=${this._activeGrouping} .initialCollapsedGroups=${this._activeCollapsed} - .groupOrder=${this._groupOrder(this._activeGrouping)} + .groupOrder=${this._groupOrder( + this._activeGrouping, + this.hass.localize + )} @grouping-changed=${this._handleGroupingChanged} @collapsed-changed=${this._handleCollapseChanged} @selection-changed=${this._handleSelectionChanged} .route=${this.route} @row-click=${this._showBackupDetails} .columns=${this._columns(this.hass.localize)} - .data=${this._data(this.backups, this._filters)} + .data=${this._data(this.backups, this._filters, this.hass.localize)} .noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")} .searchLabel=${this.hass.localize( "ui.panel.config.backup.picker.search" @@ -351,7 +353,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { @request-selected=${this._uploadBackup} > - Upload backup + ${this.hass.localize( + "ui.panel.config.backup.backups.menu.upload_backup" + )} @@ -360,26 +364,32 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { ${!this.narrow ? html` - Delete selected + ${this.hass.localize( + "ui.panel.config.backup.backups.delete_selected" + )} ` : html` - Delete selected + ${this.hass.localize( + "ui.panel.config.backup.backups.delete_selected" + )} `} @@ -404,16 +416,12 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { `; } - private _states = memoizeOne((_localize: LocalizeFunc) => [ - { - value: "automatic", - label: "Automatic", - }, - { - value: "manual", - label: "Manual", - }, - ]); + private _states = memoizeOne((localize: LocalizeFunc) => + TYPE_ORDER.map((type) => ({ + value: type, + label: localize(`ui.panel.config.backup.type.${type}`), + })) + ); private _filterChanged(ev) { const type = ev.target.localName; @@ -489,8 +497,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { private async _deleteBackup(backup: BackupContent): Promise { const confirm = await showConfirmationDialog(this, { - title: "Delete backup", - text: "This backup will be permanently deleted.", + title: this.hass.localize("ui.panel.config.backup.dialogs.delete.title"), + text: this.hass.localize("ui.panel.config.backup.dialogs.delete.text"), confirmText: this.hass.localize("ui.common.delete"), destructive: true, }); @@ -499,17 +507,31 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { return; } - await deleteBackup(this.hass, backup.backup_id); - if (this._selected.includes(backup.backup_id)) { - this._selected = this._selected.filter((id) => id !== backup.backup_id); + try { + await deleteBackup(this.hass, backup.backup_id); + if (this._selected.includes(backup.backup_id)) { + this._selected = this._selected.filter((id) => id !== backup.backup_id); + } + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.backup.dialogs.delete.failed" + ), + text: extractApiErrorMessage(err), + }); + return; } fireEvent(this, "ha-refresh-backup-info"); } private async _deleteSelected() { const confirm = await showConfirmationDialog(this, { - title: "Delete selected backups", - text: "These backups will be permanently deleted.", + title: this.hass.localize( + "ui.panel.config.backup.dialogs.delete_selected.title" + ), + text: this.hass.localize( + "ui.panel.config.backup.dialogs.delete_selected.text" + ), confirmText: this.hass.localize("ui.common.delete"), destructive: true, }); @@ -524,7 +546,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { ); } catch (err: any) { showAlertDialog(this, { - title: "Failed to delete backups", + title: this.hass.localize( + "ui.panel.config.backup.dialogs.delete_selected.failed" + ), text: extractApiErrorMessage(err), }); return; diff --git a/src/panels/config/backup/ha-config-backup-details.ts b/src/panels/config/backup/ha-config-backup-details.ts index 8c42bfe69f..5c8f2ea510 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -41,6 +41,7 @@ import { fileDownload } from "../../../util/file_download"; import { showConfirmationDialog } from "../../lovelace/custom-card-helpers"; import "./components/ha-backup-data-picker"; import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup"; +import { fireEvent } from "../../../common/dom/fire_event"; type Agent = { id: string; @@ -96,7 +97,8 @@ class HaConfigBackupDetails extends LitElement { back-path="/config/backup/backups" .hass=${this.hass} .narrow=${this.narrow} - .header=${this._backup?.name || "Backup"} + .header=${this._backup?.name || + this.hass.localize("ui.panel.config.backup.details.header")} > ${this._error}`} ${this._backup === null ? html` - - Backup matching ${this.backupId} not found + + ${this.hass.localize( + "ui.panel.config.backup.details.not_found_description", + { backupId: this.backupId } + )} ` : !this._backup ? html`` : html` -
Backup
+
+ ${this.hass.localize( + "ui.panel.config.backup.details.summary.title" + )} +
${bytesToString(this._backup.size)} - Size + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.size" + )} + ${formatDateTime( @@ -141,21 +159,37 @@ class HaConfigBackupDetails extends LitElement { this.hass.locale, this.hass.config )} - Created + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.created" + )} + ${this._backup.protected - ? "Encrypted AES-128" - : "Not encrypted"} + ? this.hass.localize( + "ui.panel.config.backup.details.summary.protected_encrypted_aes_128" + ) + : this.hass.localize( + "ui.panel.config.backup.details.summary.protected_not_encrypted" + )} + + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.protected" + )} - Protected
-
Select what to restore
+
+ ${this.hass.localize( + "ui.panel.config.backup.details.restore.title" + )} +
- Restore + ${this.hass.localize( + "ui.panel.config.backup.details.restore.action" + )}
-
Locations
+
+ ${this.hass.localize( + "ui.panel.config.backup.details.locations.title" + )} +
${this._agents.map((agent) => { @@ -187,11 +227,9 @@ class HaConfigBackupDetails extends LitElement { const name = computeBackupAgentName( this.hass.localize, agentId, - this._backup!.agent_ids! + this._backup!.agent_ids ); - const isLocal = isLocalAgent(agentId); - return html` ${isLocalAgent(agentId) @@ -232,10 +270,12 @@ class HaConfigBackupDetails extends LitElement { ${success - ? isLocal - ? "Backup created" - : "Backup uploaded" - : "Backup failed"} + ? this.hass.localize( + "ui.panel.config.backup.details.locations.backup_stored" + ) + : this.hass.localize( + "ui.panel.config.backup.details.locations.backup_failed" + )}
${success @@ -257,7 +297,9 @@ class HaConfigBackupDetails extends LitElement { slot="graphic" .path=${mdiDownload} > - Download from this location + ${this.hass.localize( + "ui.panel.config.backup.details.locations.download" + )}
` : nothing} @@ -309,7 +351,9 @@ class HaConfigBackupDetails extends LitElement { response.backup.failed_agent_ids || [] ); } catch (err: any) { - this._error = err?.message || "Could not fetch backup details"; + this._error = + err?.message || + this.hass.localize("ui.panel.config.backup.details.error"); } } @@ -342,8 +386,8 @@ class HaConfigBackupDetails extends LitElement { private async _deleteBackup(): Promise { const confirm = await showConfirmationDialog(this, { - title: "Delete backup", - text: "This backup will be permanently deleted.", + title: this.hass.localize("ui.panel.config.backup.dialogs.delete.title"), + text: this.hass.localize("ui.panel.config.backup.dialogs.delete.text"), confirmText: this.hass.localize("ui.common.delete"), destructive: true, }); @@ -353,6 +397,7 @@ class HaConfigBackupDetails extends LitElement { } await deleteBackup(this.hass, this._backup!.backup_id); + fireEvent(this, "ha-refresh-backup-info"); navigate("/config/backup"); } diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts index 26e86a2105..e121d62b30 100644 --- a/src/panels/config/backup/ha-config-backup-overview.ts +++ b/src/panels/config/backup/ha-config-backup-overview.ts @@ -130,7 +130,7 @@ class HaConfigBackupOverview extends LitElement { back-path="/config/system" .hass=${this.hass} .narrow=${this.narrow} - .header=${"Backup"} + .header=${this.hass.localize("ui.panel.config.backup.overview.header")} > - Upload backup + ${this.hass.localize( + "ui.panel.config.backup.overview.menu.upload_backup" + )}
@@ -190,7 +192,9 @@ class HaConfigBackupOverview extends LitElement { diff --git a/src/panels/config/backup/ha-config-backup-settings.ts b/src/panels/config/backup/ha-config-backup-settings.ts index f581f5cd92..4bed661988 100644 --- a/src/panels/config/backup/ha-config-backup-settings.ts +++ b/src/panels/config/backup/ha-config-backup-settings.ts @@ -99,7 +99,7 @@ class HaConfigBackupSettings extends LitElement { back-path="/config/backup" .hass=${this.hass} .narrow=${this.narrow} - .header=${"Backup settings"} + .header=${this.hass.localize("ui.panel.config.backup.settings.header")} > ${isComponentLoaded(this.hass, "hassio") ? html` @@ -117,7 +117,9 @@ class HaConfigBackupSettings extends LitElement { slot="graphic" .path=${mdiHarddisk} > - Change default action location + ${this.hass.localize( + "ui.panel.config.backup.settings.menu.change_default_location" + )} ` @@ -125,11 +127,16 @@ class HaConfigBackupSettings extends LitElement {
-
Automatic backups
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.schedule.title" + )} +

- Let Home Assistant take care of your backups by creating a - scheduled backup that also removes older backups. + ${this.hass.localize( + "ui.panel.config.backup.settings.schedule.description" + )}

-
Backup data
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.data.title" + )} +
-
Locations
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.locations.title" + )} +

- Your backup will be stored on these locations when this default - backup is created. You can use all locations for custom backups. + ${this.hass.localize( + "ui.panel.config.backup.settings.locations.description" + )}

${!this._config.create_backup.agent_ids.length - ? html`You have to select at least one location to create a - backup.
` + .title=${this.hass.localize( + "ui.panel.config.backup.settings.locations.no_location" + )} + > + ${this.hass.localize( + "ui.panel.config.backup.settings.locations.no_location_description" + )} + +
+ ` : nothing}
-
Encryption key
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.encryption_key.title" + )} +

- Keep this encryption key in a safe place, as you will need it to - access your backup, allowing it to be restored. Download them as - an emergency kit file and store it somewhere safe. Encryption - keeps your backups private and secure. + ${this.hass.localize( + "ui.panel.config.backup.settings.encryption_key.description" + )}