diff --git a/pyproject.toml b/pyproject.toml index 801c103f69..01d6a21fe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250106.0" +version = "20250109.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 017a5dab07..92c660dcfe 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -83,11 +83,13 @@ export class HaChartBase extends LitElement { public disconnectedCallback() { super.disconnectedCallback(); + window.removeEventListener("scroll", this._handleScroll, true); this._releaseCanvas(); } public connectedCallback() { super.connectedCallback(); + window.addEventListener("scroll", this._handleScroll, true); if (this.hasUpdated) { this._releaseCanvas(); this._setupChart(); @@ -561,6 +563,10 @@ export class HaChartBase extends LitElement { this.chart?.resetZoom(); } + private _handleScroll = () => { + this._tooltip = undefined; + }; + static get styles(): CSSResultGroup { return css` :host { diff --git a/src/components/ha-button-toggle-group.ts b/src/components/ha-button-toggle-group.ts index 7ec98e8e68..c57b5f0d47 100644 --- a/src/components/ha-button-toggle-group.ts +++ b/src/components/ha-button-toggle-group.ts @@ -75,8 +75,10 @@ export class HaButtonToggleGroup extends LitElement { direction: ltr; } mwc-button { + flex: 1; --mdc-shape-small: 0; --mdc-button-outline-width: 1px 0 1px 1px; + --mdc-button-outline-color: var(--primary-color); } ha-icon-button { border: 1px solid var(--primary-color); @@ -94,7 +96,7 @@ export class HaButtonToggleGroup extends LitElement { width: 100%; height: 100%; position: absolute; - background-color: currentColor; + background-color: var(--primary-color); opacity: 0; pointer-events: none; content: ""; @@ -104,12 +106,22 @@ export class HaButtonToggleGroup extends LitElement { } ha-icon-button[active]::before, mwc-button[active]::before { - opacity: var(--mdc-icon-button-ripple-opacity, 0.12); + opacity: 1; + } + ha-icon-button[active] { + --icon-primary-color: var(--text-primary-color); + } + mwc-button[active] { + --mdc-theme-primary: var(--text-primary-color); } ha-icon-button:first-child, mwc-button:first-child { --mdc-shape-small: 4px 0 0 4px; border-radius: 4px 0 0 4px; + --mdc-button-outline-width: 1px; + } + mwc-button:first-child::before { + border-radius: 4px 0 0 4px; } ha-icon-button:last-child, mwc-button:last-child { @@ -118,6 +130,9 @@ export class HaButtonToggleGroup extends LitElement { --mdc-shape-small: 0 4px 4px 0; --mdc-button-outline-width: 1px; } + mwc-button:last-child::before { + border-radius: 0 4px 4px 0; + } ha-icon-button:only-child, mwc-button:only-child { --mdc-shape-small: 4px; diff --git a/src/components/ha-selector/ha-selector-button-toggle.ts b/src/components/ha-selector/ha-selector-button-toggle.ts index 6578cee61f..727dfaef82 100644 --- a/src/components/ha-selector/ha-selector-button-toggle.ts +++ b/src/components/ha-selector/ha-selector-button-toggle.ts @@ -87,6 +87,16 @@ export class HaButtonToggleSelector extends LitElement { static styles = css` :host { position: relative; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + align-items: center; + } + @media all and (max-width: 600px) { + ha-button-toggle-group { + flex: 1; + } } `; } diff --git a/src/components/map/ha-entity-marker.ts b/src/components/map/ha-entity-marker.ts index 22de169418..c6764015bc 100644 --- a/src/components/map/ha-entity-marker.ts +++ b/src/components/map/ha-entity-marker.ts @@ -8,7 +8,7 @@ import "../ha-state-icon"; class HaEntityMarker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: "entity-id" }) public entityId?: string; + @property({ attribute: "entity-id", reflect: true }) public entityId?: string; @property({ attribute: "entity-name" }) public entityName?: string; diff --git a/src/data/backup.ts b/src/data/backup.ts index d68d74c5ef..d1299a2a7e 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -1,16 +1,16 @@ import { setHours, setMinutes } from "date-fns"; import type { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; -import { formatTime } from "../common/datetime/format_time"; -import type { LocalizeFunc } from "../common/translations/localize"; -import type { HomeAssistant } from "../types"; -import { domainToName } from "./integration"; -import type { FrontendLocaleData } from "./translation"; import { formatDateTime, formatDateTimeNumeric, } from "../common/datetime/format_date_time"; +import { formatTime } from "../common/datetime/format_time"; +import type { LocalizeFunc } from "../common/translations/localize"; +import type { HomeAssistant } from "../types"; import { fileDownload } from "../util/file_download"; +import { domainToName } from "./integration"; +import type { FrontendLocaleData } from "./translation"; export const enum BackupScheduleState { NEVER = "never", @@ -217,10 +217,16 @@ export const uploadBackup = async ( }; export const getPreferredAgentForDownload = (agents: string[]) => { - const localAgents = agents.filter( - (agent) => agent.split(".")[0] === "backup" - ); - return localAgents[0] || agents[0]; + const localAgent = agents.find(isLocalAgent); + if (localAgent) { + return localAgent; + } + const networkMountAgent = agents.find(isNetworkMountAgent); + if (networkMountAgent) { + return networkMountAgent; + } + + return agents[0]; }; export const CORE_LOCAL_AGENT = "backup.local"; @@ -241,7 +247,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 +304,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/automation/automation-rename-dialog/dialog-automation-rename.ts b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts index f9a829d618..d484b75278 100644 --- a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts +++ b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts @@ -323,6 +323,14 @@ class DialogAutomationRename extends LitElement implements HassDialog { ha-dialog { --dialog-content-padding: 0 24px 24px 24px; } + + @media all and (min-width: 500px) { + ha-dialog { + --mdc-dialog-min-width: min(500px, 95vw); + --mdc-dialog-max-width: min(500px, 95vw); + } + } + ha-textfield, ha-textarea, ha-icon-picker, 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 ac476bb2d5..57a7bd3e52 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", + 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, }); @@ -99,8 +103,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); } @@ -226,7 +239,7 @@ export class HaBackupDataPicker extends LitElement { @@ -272,7 +285,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..3f7ccf5545 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.manual", + { 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..df04e12519 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,16 @@ class HaBackupOverviewBackups extends LitElement { if (isOverdue) { return html` - ${description} + ${lastSuccessfulBackupDescription} @@ -220,11 +275,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.title" + )} + - 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..158573bb88 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" + )} +
- + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.size" + )} + + ${bytesToString(this._backup.size)} - Size - - - ${formatDateTime( - new Date(this._backup.date), - this.hass.locale, - this.hass.config - )} - Created - ${this._backup.protected - ? "Encrypted AES-128" - : "Not encrypted"} + ${this.hass.localize( + "ui.panel.config.backup.details.summary.created" + )} + + + ${formatDateTime( + new Date(this._backup.date), + this.hass.locale, + this.hass.config + )} + + + + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.protection" + )} + + + ${this._backup.protected + ? 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" + )} - 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 +229,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 +272,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 +299,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 +353,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 +388,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 +399,7 @@ class HaConfigBackupDetails extends LitElement { } await deleteBackup(this.hass, this._backup!.backup_id); + fireEvent(this, "ha-refresh-backup-info"); navigate("/config/backup"); } @@ -388,6 +435,13 @@ class HaConfigBackupDetails extends LitElement { --mdc-icon-size: 48px; color: var(--primary-text-color); } + ha-md-list.summary ha-md-list-item { + --md-list-item-supporting-text-size: 1rem; + --md-list-item-label-text-size: 0.875rem; + + --md-list-item-label-text-color: var(--secondary-text-color); + --md-list-item-supporting-text-color: var(--primary-text-color); + } .warning { color: var(--error-color); } 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" + )}

computeDomain(entity.entity_id) === "assist_satellite" + (entity) => + computeDomain(entity.entity_id) === "assist_satellite" && + this.hass.states[entity.entity_id].state !== "unavailable" ).length; } diff --git a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts index fe5624f2a7..41d2a5084f 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts @@ -54,6 +54,7 @@ export class HuiViewBackgroundEditor extends LitElement { }, { name: "size", + required: true, selector: { select: { translation_key: @@ -65,6 +66,7 @@ export class HuiViewBackgroundEditor extends LitElement { }, { name: "alignment", + required: true, selector: { select: { translation_key: @@ -86,6 +88,7 @@ export class HuiViewBackgroundEditor extends LitElement { }, { name: "repeat", + required: true, selector: { select: { translation_key: diff --git a/src/translations/en.json b/src/translations/en.json index b513f6a8b3..6c7587e163 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2200,6 +2200,12 @@ "size": "[%key:supervisor::backup::size%]", "created": "[%key:supervisor::backup::created%]", "no_backups": "[%key:supervisor::backup::no_backups%]", + "backup_type": "Type", + "type": { + "manual": "Manual", + "automatic": "Automatic" + }, + "locations": "Locations", "create": { "title": "Create backup", "description": "Create a backup of your current configuration directory, this will take some time.", @@ -2223,6 +2229,390 @@ "name": "Default location" } } + }, + "delete_selected": { + "title": "Delete selected backups", + "text": "These backups will be permanently deleted.", + "failed": "Failed to delete selected backups" + }, + "delete": { + "title": "Delete backup", + "text": "This backup will be permanently deleted.", + "failed": "Failed to delete backup" + }, + "upload": { + "title": "Upload backup", + "action": "Upload backup", + "input_label": "Select backup file", + "supports_tar": "Supports .tar files", + "unsupported": { + "title": "Unsupported file format", + "text": "Please choose a Home Assistant backup file (.tar)" + } + }, + "generate": { + "sync": { + "title": "Synchonization", + "name": "Backup name", + "locations": "Locations", + "locations_description": "What locations you want to automatically backup to.", + "locations_options": { + "all": "All ({count})", + "custom": "Custom" + }, + "ha_cloud_alert": { + "title": "Home Assistant Cloud cannot synchronize", + "description": "Add Home Assistant settings data to synchronize this backup to Home Assistant Cloud." + } + }, + "data": { + "title": "Backup data" + }, + "actions": { + "create": "Create backup" + } + }, + "new": { + "title": "Backup now", + "options": "Backup options", + "automatic": { + "title": "Automatic backup", + "description": "Create a backup with the data and locations you have configured." + }, + "manual": { + "title": "Manual backup", + "description": "Select data and locations for a manual backup." + } + }, + "restore": { + "title": "Restore backup", + "restore_failed": "Backup restore failed", + "confirm": { + "description": "Your backup will be restored and all current data will be overwritten. Depending on the size of the backup, this can take a while." + }, + "encryption": { + "different_key": "The backup is encrypted. Provide the encryption key to decrypt the backup.", + "incorrect_key": "The provided encryption key was incorrect, please try again.", + "description": "The backup is encrypted with a different encryption key than the one stored on this system. Please enter the encryption key for this backup.", + "warning": "After restoring this backup, all new backups will be encrypted using the same key that was used to restore this backup.", + "input_label": "Encryption key" + }, + "progress": { + "restarting": "Restarting Home Assistant", + "restoring": "Restoring backup" + }, + "actions": { + "restore": "Restore" + } + }, + "onboarding": { + "welcome": { + "title": "[%key:ui::panel::config::backup::overview::onboarding::title%]", + "description": "[%key:ui::panel::config::backup::overview::onboarding::description%]" + }, + "key": { + "title": "Encryption key", + "description": "All your backups are encrypted to keep your data private and secure. We recommend saving this key somewhere secure. As you can only restore your data with this encryption key." + }, + "setup": { + "title": "Set up automatic backups", + "recommended_heading": "Recommended", + "recommended_description": "Backup everything daily, keeping three days of backups", + "custom_heading": "Custom", + "custom_description": "Select when, where, and what to backup" + }, + "schedule": { + "title": "Automatic backups", + "description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups." + }, + "data": { + "title": "Backup data", + "description": "Choose what data to include in your backups. You can always change this later." + }, + "locations": { + "title": "Locations", + "description": "Home Assistant will upload to these locations when an automatic backup is made. You can use all locations for manual backups." + }, + "save_and_create": "Save and create backup" + }, + "change_encryption_key": { + "current": { + "title": "Save current encryption key", + "description": "Backups made before this new encryption key was issued will continue to use the old key. Be sure to save the old key along with the new key to ensure that you can access all backups." + }, + "new": { + "title": "New encryption key", + "description": "All future backups will use the new encryption key. Encryption keeps your backups private and secure." + }, + "done": { + "title": "Encryption key changed" + }, + "actions": { + "change": "Change encryption key", + "done": "Done" + } + }, + "set_encryption_key": { + "key": { + "title": "Set encryption key", + "description": "All your backups are encrypted to keep your data private and secure. We recommend saving this key somewhere secure. As you can only restore your data with the backup encryption key." + }, + "done": { + "title": "Encryption key set" + }, + "actions": { + "set": "Set encryption key", + "done": "[%key:ui::panel::config::backup::dialogs::change_encryption_key::actions::done%]" + } + }, + "show_encryption_key": { + "title": "Encryption key", + "description": "Make sure you save the encryption key in a secure place so you always have access to your backups." + } + }, + "agents": { + "cloud_agent_description": "Note: It stores only the most recent backup, regardless of your retention settings, with a maximum size of 5 GB.", + "cloud_agent_no_subcription": "You currently do not have an active Home Assistant Cloud subscription.", + "network_mount_agent_description": "Network storage", + "no_agents": "No locations configured", + "local_agent": "This system" + }, + "data": { + "ha_settings": "Home Assistant settings", + "ha_settings_description": "The bare minimum needed to restore the system.", + "ha_settings_included_description": "The bare minimum needed to restore the system. It is always included in automatic backup data.", + "history": "History", + "history_description": "This can include large filesize camera recordings.", + "media": "Media", + "media_description": "For example, camera recordings.", + "share_folder": "Share folder", + "share_folder_description": "Folder that is often used by add-ons for advanced or older configurations.", + "local_addons": "Local add-ons folder", + "local_addons_description": "Folder that contains the data of your local add-ons.", + "addons": "Add-ons", + "addons_description": "Select what add-ons you want to include.", + "addons_all": "All", + "addons_none": "None", + "addons_custom": "Custom" + }, + "data_picker": { + "settings": "Settings", + "settings_and_history": "Settings and history", + "media": "Media", + "share_folder": "Share folder", + "local_addons": "Local add-ons folder", + "addons": "Add-ons" + }, + "schedule": { + "use_automatic_backups": "Use automatic backups", + "schedule": "Schedule", + "schedule_description": "How often you want to create a backup.", + "schedule_options": { + "daily": "Daily at {time}", + "mon": "Monday at {time}", + "tue": "Tuesday at {time}", + "wed": "Wednesday at {time}", + "thu": "Thursday at {time}", + "fri": "Friday at {time}", + "sat": "Saturday at {time}", + "sun": "Sunday at {time}" + }, + "retention": "Retention", + "retention_description": "Based on the maximum number of backups or how many days they should be kept.", + "retention_presets": { + "copies_3": "3 backups", + "forever": "Forever", + "custom": "Custom" + }, + "retention_units": { + "copies": "backups", + "days": "days" + } + }, + "encryption_key": { + "download_emergency_kit": "Download emergency kit", + "download_emergency_kit_description": "We recommend to save this encryption key somewhere secure.", + "download_emergency_kit_action": "Download", + "download_old_emergency_kit": "Download old emergency kit", + "download_old_emergency_kit_description": "[%key:ui::panel::config::backup::encryption_key::download_emergency_kit_description%]", + "download_old_emergency_kit_action": "[%key:ui::panel::config::backup::encryption_key::download_emergency_kit_action%]", + "show_encryption_key": "Show my encryption key", + "show_encryption_key_description": "Please keep your encryption key private.", + "show_encryption_key_action": "Show", + "change_encryption_key": "Change encryption key", + "change_encryption_key_description": "All future backups will use this encryption key.", + "change_encryption_key_action": "Change", + "set_encryption_key": "Set encryption key", + "set_encryption_key_description": "Set an encryption key for your backups.", + "set_encryption_key_action": "Set" + }, + "emergency_kit_file": { + "title": "Home Assistant Backup Emergency Kit", + "description": "This emergency kit contains your backup encryption key. You need this key to be able to restore your Home Assistant backups.", + "date": "Date:", + "instance": "Instance:", + "url": "URL:", + "encryption_key": "Encryption key:", + "more_info": "For more information, visit {link}" + }, + "overview": { + "header": "Backup", + "menu": { + "upload_backup": "Upload backup" + }, + "new_backup": "Backup now", + "onboarding": { + "title": "Set up backups", + "description": "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.", + "setup": "Set up backups" + }, + "progress": { + "heading": { + "create_backup": "Creating backup", + "restore_backup": "Restoring backup", + "receive_backup": "Receiving backup" + }, + "description": { + "create_backup": { + "addon_repositories": "Backing up add-ons repositories", + "addons": "Backing up add-ons", + "await_addon_restarts": "Waiting for add-ons to restart", + "docker_config": "Backing up Docker configuration", + "finishing_file": "Finishing backup file", + "folders": "Backing up folders", + "home_assistant": "Backing up Home Assistant", + "upload_to_agents": "Uploading to locations" + }, + "restore_backup": { + "addon_repositories": "Restoring add-ons repositories", + "addons": "Restoring add-ons", + "await_addon_restarts": "Waiting for add-ons to restart", + "await_home_assistant_restart": "Waiting for Home Assistant to restart", + "check_home_assistant": "Checking Home Assistant", + "docker_config": "Restoring Docker configuration", + "download_from_agent": "Downloading from location", + "folders": "Restoring folders", + "home_assistant": "Restoring Home Assistant", + "remove_delta_addons": "Removing delta add-ons" + }, + "receive_backup": { + "receive_file": "Receiving file", + "upload_to_agents": "Uploading to locations" + } + } + }, + "summary": { + "next_backup_description": { + "daily": "Next automatic backup tomorrow at {time}", + "mon": "Next automatic backup next Monday at {time}", + "tue": "Next automatic backup next Tuesday at {time}", + "wed": "Next automatic backup next Wednesday at {time}", + "thu": "Next automatic backup next Thursday at {time}", + "fri": "Next automatic backup next Friday at {time}", + "sat": "Next automatic backup next Saturday at {time}", + "sun": "Next automatic backup next Sunday at {time}", + "never": "No automatic backups scheduled" + }, + "loading": "Loading backups...", + "last_backup_failed_heading": "Last automatic backup failed", + "last_backup_failed_description": "The last automatic backup triggered {relative_time} wasn't successful.", + "last_backup_failed_locations_description": "The last automatic backup created {relative_time} wasn't stored in all locations.", + "last_successful_backup_description": "Last successful backup {relative_time} and stored in {count} {count, plural,\n one {location}\n other {locations}\n}.", + "no_backup_heading": "No automatic backup available", + "no_backup_description": "You have no automatic backups yet.", + "backup_too_old_heading": "No backup for {count} {count, plural,\n one {day}\n other {days}\n}", + "backup_success_heading": "Backed up" + }, + "backups": { + "title": "My backups", + "automatic": "{count} automatic {count, plural,\n one {backup}\n other {backups}\n}", + "manual": "{count} manual {count, plural,\n one {backup}\n other {backups}\n}", + "total_size": "{size} in total", + "show_all": "Show all backups" + }, + "settings": { + "title": "Backup settings", + "configure": "Configure backup settings", + "schedule": "Automatic backup schedule and retention", + "schedule_copies_all": "and keep all backups", + "schedule_copies_backups": "and keep {count} {count, plural,\n one {backup}\n other {backups}\n}", + "schedule_copies_days": "and keep {count} {count, plural,\n one {day}\n other {days}\n}", + "schedule_daily": "Daily at {time}", + "schedule_mon": "Weekly on Mondays at {time}", + "schedule_tue": "Weekly on Tuesdays at {time}", + "schedule_wed": "Weekly on Wednesdays at {time}", + "schedule_thu": "Weekly on Thursdays at {time}", + "schedule_fri": "Weekly on Fridays at {time}", + "schedule_sat": "Weekly on Saturdays at {time}", + "schedule_sun": "Weekly on Sundays at {time}", + "schedule_never": "Automatic backups are not scheduled", + "data": "Home Assistant data that is included", + "data_settings_history": "Settings and history", + "data_settings_only": "Settings only", + "addons": "Add-ons that are included", + "addons_all": "All add-ons", + "addons_many": "{count} {count, plural,\n one {add-on}\n other {add-ons}\n}", + "addons_none": "No add-ons", + "locations": "Locations where backup is stored to", + "locations_one": "Store in {name}", + "locations_many": "Store in {count} off-site {count, plural,\n one {location}\n other {locations}\n}", + "locations_local_only": "Local backup only", + "locations_none": "No locations configured" + } + }, + "backups": { + "header": "My backups", + "menu": { + "upload_backup": "[%key:ui::panel::config::backup::overview::menu::upload_backup%]" + }, + "delete_selected": "Delete selected", + "new_backup": "[%key:ui::panel::config::backup::overview::new_backup%]" + }, + "settings": { + "header": "Backup settings", + "menu": { + "change_default_location": "Change default action location" + }, + "schedule": { + "title": "Automatic backups", + "description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups." + }, + "data": { + "title": "Backup data" + }, + "locations": { + "title": "Locations", + "description": "Your backup will be stored on these locations when this default backup is created. You can use all locations for custom backups.", + "no_location": "No location selected", + "no_location_description": "You have to select at least one location to create a backup." + }, + "encryption_key": { + "title": "Encryption key", + "description": "Keep this encryption key in a safe place, as you will need it to access your backup, allowing it to be restored. Download it as an emergency kit file and store it somewhere safe. Encryption keeps your backups private and secure." + } + }, + "details": { + "header": "Backup", + "not_found": "Not found", + "not_found_description": "Backup matching ''{backupId}'' not found", + "error": "Could not fetch backup details", + "summary": { + "title": "Backup", + "size": "Size", + "created": "Created", + "protection": "Protection", + "protected_encrypted_aes_128": "Encrypted AES-128", + "protected_not_encrypted": "Not encrypted" + }, + "restore": { + "title": "Selected what to restore", + "action": "Restore" + }, + "locations": { + "title": "Locations", + "backup_stored": "Backup stored", + "backup_failed": "Backup failed", + "download": "Download from this location" } } }, @@ -2774,7 +3164,7 @@ }, "wakeword": { "title": "Streaming wake word engine", - "description": " If a device supports streaming wake word engines, you can activate Assist by saying this word.", + "description": "If a device supports streaming wake word engines, you can activate Assist by saying this word.", "note": "Most recent devices support on-device wake word engines and are configured on their device page." } }, @@ -2795,7 +3185,7 @@ }, "remote_access": { "title": "Remote access", - "text": " Secure remote access to your system while supporting the development of Home Assistant." + "text": "Secure remote access to your system while supporting the development of Home Assistant." } }, "and_more": "And more",