diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index ea5a29326f..452b4bf7c1 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -85,6 +85,11 @@ gulp.task("copy-translations-app", async () => { copyTranslations(staticDir); }); +gulp.task("copy-translations-supervisor", async () => { + const staticDir = paths.hassio_output_static; + copyTranslations(staticDir); +}); + gulp.task("copy-static-app", async () => { const staticDir = paths.app_output_static; // Basic static files diff --git a/build-scripts/gulp/hassio.js b/build-scripts/gulp/hassio.js index c055ba7685..49c5ecd6b6 100644 --- a/build-scripts/gulp/hassio.js +++ b/build-scripts/gulp/hassio.js @@ -10,6 +10,8 @@ require("./gen-icons-json.js"); require("./webpack.js"); require("./compress.js"); require("./rollup.js"); +require("./gather-static.js"); +require("./translations.js"); gulp.task( "develop-hassio", @@ -20,6 +22,8 @@ gulp.task( "clean-hassio", "gen-icons-json", "gen-index-hassio-dev", + "build-supervisor-translations", + "copy-translations-supervisor", env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio" ) ); @@ -32,6 +36,8 @@ gulp.task( }, "clean-hassio", "gen-icons-json", + "build-supervisor-translations", + "copy-translations-supervisor", env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", "gen-index-hassio-prod", ...// Don't compress running tests diff --git a/build-scripts/gulp/translations.js b/build-scripts/gulp/translations.js index 609be106e7..e948a674db 100755 --- a/build-scripts/gulp/translations.js +++ b/build-scripts/gulp/translations.js @@ -266,6 +266,7 @@ gulp.task(taskName, function () { TRANSLATION_FRAGMENTS.forEach((fragment) => { delete data.ui.panel[fragment]; }); + delete data.supervisor; return data; }) ) @@ -342,6 +343,62 @@ gulp.task( } ); +gulp.task("build-translation-fragment-supervisor", function () { + return gulp + .src(fullDir + "/*.json") + .pipe(transform((data) => data.supervisor)) + .pipe(gulp.dest(workDir + "/supervisor")); +}); + +gulp.task("build-translation-flatten-supervisor", function () { + return gulp + .src(workDir + "/supervisor/*.json") + .pipe( + transform(function (data) { + // Polymer.AppLocalizeBehavior requires flattened json + return flatten(data); + }) + ) + .pipe(gulp.dest(outDir)); +}); + +gulp.task("build-translation-write-metadata", function writeMetadata() { + return gulp + .src( + [ + path.join(paths.translations_src, "translationMetadata.json"), + workDir + "/testMetadata.json", + workDir + "/translationFingerprints.json", + ], + { allowEmpty: true } + ) + .pipe(merge({})) + .pipe( + transform(function (data) { + const newData = {}; + Object.entries(data).forEach(([key, value]) => { + // Filter out translations without native name. + if (value.nativeName) { + newData[key] = value; + } else { + console.warn( + `Skipping language ${key}. Native name was not translated.` + ); + } + }); + return newData; + }) + ) + .pipe( + transform((data) => ({ + fragments: TRANSLATION_FRAGMENTS, + translations: data, + })) + ) + .pipe(rename("translationMetadata.json")) + .pipe(gulp.dest(workDir)); +}); + gulp.task( "build-translations", gulp.series( @@ -353,41 +410,20 @@ gulp.task( gulp.parallel(...splitTasks), "build-flattened-translations", "build-translation-fingerprints", - function writeMetadata() { - return gulp - .src( - [ - path.join(paths.translations_src, "translationMetadata.json"), - workDir + "/testMetadata.json", - workDir + "/translationFingerprints.json", - ], - { allowEmpty: true } - ) - .pipe(merge({})) - .pipe( - transform(function (data) { - const newData = {}; - Object.entries(data).forEach(([key, value]) => { - // Filter out translations without native name. - if (value.nativeName) { - newData[key] = value; - } else { - console.warn( - `Skipping language ${key}. Native name was not translated.` - ); - } - }); - return newData; - }) - ) - .pipe( - transform((data) => ({ - fragments: TRANSLATION_FRAGMENTS, - translations: data, - })) - ) - .pipe(rename("translationMetadata.json")) - .pipe(gulp.dest(workDir)); - } + "build-translation-write-metadata" + ) +); + +gulp.task( + "build-supervisor-translations", + gulp.series( + "clean-translations", + "ensure-translations-build-dir", + "build-master-translation", + "build-merged-translations", + "build-translation-fragment-supervisor", + "build-translation-flatten-supervisor", + "build-translation-fingerprints", + "build-translation-write-metadata" ) ); diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index c5790f0b69..2db685721f 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -137,7 +137,12 @@ gulp.task("webpack-watch-hassio", () => { isProdBuild: false, latestBuild: true, }) - ).watch({}, doneHandler()); + ).watch({ ignored: /build-translations/ }, doneHandler()); + + gulp.watch( + path.join(paths.translations_src, "en.json"), + gulp.series("build-supervisor-translations", "copy-translations-supervisor") + ); }); gulp.task("webpack-prod-hassio", () => diff --git a/build-scripts/paths.js b/build-scripts/paths.js index ae613b9b10..da9d8db190 100644 --- a/build-scripts/paths.js +++ b/build-scripts/paths.js @@ -34,6 +34,7 @@ module.exports = { hassio_dir: path.resolve(__dirname, "../hassio"), hassio_output_root: path.resolve(__dirname, "../hassio/build"), + hassio_output_static: path.resolve(__dirname, "../hassio/build/static"), hassio_output_latest: path.resolve( __dirname, "../hassio/build/frontend_latest" diff --git a/hassio/src/addon-store/hassio-addon-store.ts b/hassio/src/addon-store/hassio-addon-store.ts index 803ed6cbc4..2ccac48290 100644 --- a/hassio/src/addon-store/hassio-addon-store.ts +++ b/hassio/src/addon-store/hassio-addon-store.ts @@ -77,13 +77,16 @@ class HassioAddonStore extends LitElement { return html` - Add-on Store + + ${this.supervisor.localize("panel.store")} + { + return ( + this.addon.translations[this.hass.language]?.configuration?.[entry.name] + ?.name || + this.addon.translations.en?.configuration?.[entry.name].name || + entry.name + ); + }; + private _filteredShchema = memoizeOne( (options: Record, schema: HaFormSchema[]) => { return schema.filter((entry) => entry.name in options || entry.required); @@ -102,6 +111,7 @@ class HassioAddonConfig extends LitElement { ? html` ${this.addon.name} -

Add-ons

+

${this.supervisor.localize("dashboard.addons")}

${!this.supervisor.supervisor.addons?.length ? html`
- You don't have any add-ons installed yet. Head over to - to get started!
` @@ -58,10 +56,16 @@ class HassioAddons extends LitElement { ? mdiArrowUpBoldCircle : mdiPuzzle} .iconTitle=${addon.state !== "started" - ? "Add-on is stopped" + ? this.supervisor.localize( + "dashboard.addon_stopped" + ) : addon.update_available! - ? "New version available" - : "Add-on is running"} + ? this.supervisor.localize( + "dashboard.addon_new_version" + ) + : this.supervisor.localize( + "dashboard.addon_running" + )} .iconClass=${addon.update_available ? addon.state === "started" ? "update" diff --git a/hassio/src/dashboard/hassio-dashboard.ts b/hassio/src/dashboard/hassio-dashboard.ts index d2dac17237..1a4c373eda 100644 --- a/hassio/src/dashboard/hassio-dashboard.ts +++ b/hassio/src/dashboard/hassio-dashboard.ts @@ -29,13 +29,16 @@ class HassioDashboard extends LitElement { return html` - Dashboard + + ${this.supervisor.localize("panel.dashboard")} +
{ + return key === "os" ? version : `${key}-${version}`; +}; + @customElement("hassio-update") export class HassioUpdate extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -59,9 +68,12 @@ export class HassioUpdate extends LitElement { return html`

- ${updatesAvailable > 1 - ? "Updates Available 🎉" - : "Update Available 🎉"} + ${this.supervisor.localize( + "dashboard.update_available", + "count", + updatesAvailable + )} + 🎉

${this._renderUpdateCard( @@ -110,14 +122,30 @@ export class HassioUpdate extends LitElement {
-
${name} ${object.version_latest}
-
- You are currently running version ${object.version} -
+
${name}
+ + + ${this.supervisor.localize("common.version")} + + + ${computeVersion(key, object.version!)} + + + + + + ${this.supervisor.localize("common.newest_version")} + + + ${computeVersion(key, object.version_latest!)} + +
- Release notes + + ${this.supervisor.localize("common.release_notes")} + - Update + ${this.supervisor.localize("common.update")}
@@ -141,10 +169,20 @@ export class HassioUpdate extends LitElement { } item.progress = true; const confirmed = await showConfirmationDialog(this, { - title: `Update ${item.name}`, - text: `Are you sure you want to update ${item.name} to version ${item.version}?`, - confirmText: "update", - dismissText: "cancel", + title: this.supervisor.localize( + "confirm.update.title", + "name", + item.name + ), + text: this.supervisor.localize( + "confirm.update.text", + "name", + item.name, + "version", + computeVersion(item.key, item.version) + ), + confirmText: this.supervisor.localize("common.update"), + dismissText: this.supervisor.localize("common.cancel"), }); if (!confirmed) { @@ -152,7 +190,15 @@ export class HassioUpdate extends LitElement { return; } try { - await this.hass.callApi>("POST", item.apiPath); + if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) { + await supervisorApiWsRequest(this.hass.connection, { + method: "post", + endpoint: item.apiPath.replace("hassio", ""), + timeout: null, + }); + } else { + await this.hass.callApi>("POST", item.apiPath); + } fireEvent(this, "supervisor-colllection-refresh", { colllection: item.key, }); @@ -165,7 +211,7 @@ export class HassioUpdate extends LitElement { !ignoredStatusCodes.has(err.status_code) ) { showAlertDialog(this, { - title: "Update failed", + title: this.supervisor.localize("error.update_failed"), text: extractApiErrorMessage(err), }); } @@ -190,9 +236,6 @@ export class HassioUpdate extends LitElement { margin-bottom: 0.5em; color: var(--primary-text-color); } - .warning { - color: var(--secondary-text-color); - } .card-content { height: calc(100% - 47px); box-sizing: border-box; @@ -200,13 +243,13 @@ export class HassioUpdate extends LitElement { .card-actions { text-align: right; } - .errors { - color: var(--error-color); - padding: 16px; - } a { text-decoration: none; } + ha-settings-row { + padding: 0; + --paper-item-body-two-line-min-height: 32px; + } `, ]; } diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index 5283a8cb08..fd05060c94 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -3,7 +3,7 @@ import { atLeastVersion } from "../../src/common/config/version"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { fireEvent } from "../../src/common/dom/fire_event"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; -import { supervisorCollection } from "../../src/data/supervisor/supervisor"; +import { Supervisor } from "../../src/data/supervisor/supervisor"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; import "../../src/layouts/hass-loading-screen"; import { HomeAssistant, Route } from "../../src/types"; @@ -14,6 +14,8 @@ import { SupervisorBaseElement } from "./supervisor-base-element"; export class HassioMain extends SupervisorBaseElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public supervisor!: Supervisor; + @property({ attribute: false }) public panel!: HassioPanelInfo; @property({ type: Boolean }) public narrow!: boolean; @@ -72,18 +74,6 @@ export class HassioMain extends SupervisorBaseElement { } protected render() { - if (!this.supervisor || !this.hass) { - return html``; - } - - if ( - Object.keys(supervisorCollection).some( - (colllection) => !this.supervisor![colllection] - ) - ) { - return html``; - } - return html` `; + } + + if ( + Object.keys(supervisorCollection).some( + (colllection) => !this.supervisor[colllection] + ) + ) { + return html``; + } return html` - Snapshots + + ${this.supervisor.localize("panel.snapshots")} + = { + localize: () => "", + }; @internalProperty() private _unsubs: Record = {}; @@ -49,6 +53,15 @@ export class SupervisorBaseElement extends urlSyncMixin( Collection > = {}; + @internalProperty() private _resources?: Record; + + @internalProperty() private _language = "en"; + + public connectedCallback(): void { + super.connectedCallback(); + this._initializeLocalize(); + } + public disconnectedCallback() { super.disconnectedCallback(); Object.keys(this._unsubs).forEach((unsub) => { @@ -56,15 +69,50 @@ export class SupervisorBaseElement extends urlSyncMixin( }); } + protected updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + + if (changedProperties.has("_language")) { + if (changedProperties.get("_language") !== this._language) { + this._initializeLocalize(); + } + } + } + protected _updateSupervisor(obj: Partial): void { - this.supervisor = { ...this.supervisor!, ...obj }; + this.supervisor = { ...this.supervisor, ...obj }; } protected firstUpdated(changedProps: PropertyValues): void { super.firstUpdated(changedProps); + if (this._language !== this.hass.language) { + this._language = this.hass.language; + } + this._initializeLocalize(); this._initSupervisor(); } + private async _initializeLocalize() { + const { language, data } = await getTranslation( + null, + this._language, + "/api/hassio/app/static/translations" + ); + + this._resources = { + [language]: data, + }; + + this.supervisor = { + ...this.supervisor, + localize: await computeLocalize( + this.constructor.prototype, + this._language, + this._resources + ), + }; + } + private async _handleSupervisorStoreRefreshEvent(ev) { const colllection = ev.detail.colllection; if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) { @@ -104,52 +152,54 @@ export class SupervisorBaseElement extends urlSyncMixin( } }); - if (this.supervisor === undefined) { - Object.keys(this._collections).forEach((collection) => + Object.keys(this._collections).forEach((collection) => { + if ( + this.supervisor === undefined || + this.supervisor[collection] === undefined + ) { this._updateSupervisor({ [collection]: this._collections[collection].state, - }) - ); - } - return; + }); + } + }); + } else { + const [ + addon, + supervisor, + host, + core, + info, + os, + network, + resolution, + store, + ] = await Promise.all([ + fetchHassioAddonsInfo(this.hass), + fetchHassioSupervisorInfo(this.hass), + fetchHassioHostInfo(this.hass), + fetchHassioHomeAssistantInfo(this.hass), + fetchHassioInfo(this.hass), + fetchHassioHassOsInfo(this.hass), + fetchNetworkInfo(this.hass), + fetchHassioResolution(this.hass), + fetchSupervisorStore(this.hass), + ]); + + this.supervisor = { + addon, + supervisor, + host, + core, + info, + os, + network, + resolution, + store, + }; + + this.addEventListener("supervisor-update", (ev) => + this._updateSupervisor(ev.detail) + ); } - - const [ - addon, - supervisor, - host, - core, - info, - os, - network, - resolution, - store, - ] = await Promise.all([ - fetchHassioAddonsInfo(this.hass), - fetchHassioSupervisorInfo(this.hass), - fetchHassioHostInfo(this.hass), - fetchHassioHomeAssistantInfo(this.hass), - fetchHassioInfo(this.hass), - fetchHassioHassOsInfo(this.hass), - fetchNetworkInfo(this.hass), - fetchHassioResolution(this.hass), - fetchSupervisorStore(this.hass), - ]); - - this.supervisor = { - addon, - supervisor, - host, - core, - info, - os, - network, - resolution, - store, - }; - - this.addEventListener("supervisor-update", (ev) => - this._updateSupervisor(ev.detail) - ); } } diff --git a/hassio/src/system/hassio-system.ts b/hassio/src/system/hassio-system.ts index fa9999485e..5604ced62c 100644 --- a/hassio/src/system/hassio-system.ts +++ b/hassio/src/system/hassio-system.ts @@ -32,13 +32,16 @@ class HassioSystem extends LitElement { return html` - System + + ${this.supervisor.localize("panel.system")} +
major || - (Number(haMajor) === major && (patch === undefined - ? Number(haMinor) >= minor - : Number(haMinor) > minor)) || + (Number(haMajor) === major && + (patch === undefined + ? Number(haMinor) >= minor + : Number(haMinor) > minor)) || (patch !== undefined && Number(haMajor) === major && Number(haMinor) === minor && diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index 5ebc579547..99f035527c 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -16,6 +16,10 @@ export type AddonStartup = export type AddonState = "started" | "stopped" | null; export type AddonRepository = "core" | "local" | string; +interface AddonTranslations { + [key: string]: Record>>; +} + export interface HassioAddonInfo { advanced: boolean; available: boolean; @@ -82,6 +86,7 @@ export interface HassioAddonDetails extends HassioAddonInfo { slug: string; startup: AddonStartup; stdin: boolean; + translations: AddonTranslations; watchdog: null | boolean; webui: null | string; } diff --git a/src/data/supervisor/supervisor.ts b/src/data/supervisor/supervisor.ts index 4f1eff928a..1a38c4cbe2 100644 --- a/src/data/supervisor/supervisor.ts +++ b/src/data/supervisor/supervisor.ts @@ -1,5 +1,6 @@ import { Connection, getCollection } from "home-assistant-js-websocket"; import { Store } from "home-assistant-js-websocket/dist/store"; +import { LocalizeFunc } from "../../common/translations/localize"; import { HomeAssistant } from "../../types"; import { HassioAddonsInfo } from "../hassio/addon"; import { HassioHassOSInfo, HassioHostInfo } from "../hassio/host"; @@ -46,6 +47,7 @@ interface supervisorApiRequest { method?: "get" | "post" | "delete" | "put"; force_rest?: boolean; data?: any; + timeout?: number | null; } export interface SupervisorEvent { @@ -65,6 +67,7 @@ export interface Supervisor { os: HassioHassOSInfo; addon: HassioAddonsInfo; store: SupervisorStore; + localize: LocalizeFunc; } export const supervisorApiWsRequest = ( diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index ba4db6151e..dc1d29593a 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -7,7 +7,7 @@ import { computeLocalize } from "../common/translations/localize"; import { DEFAULT_PANEL } from "../data/panel"; import { translationMetadata } from "../resources/translations-metadata"; import { HomeAssistant } from "../types"; -import { getLocalLanguage, getTranslation } from "../util/hass-translation"; +import { getTranslation, getLocalLanguage } from "../util/hass-translation"; import { demoConfig } from "./demo_config"; import { demoPanels } from "./demo_panels"; import { demoServices } from "./demo_services"; diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index f7072e795c..01a32b45d7 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -16,6 +16,7 @@ import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../common/config/is_component_loaded"; import { restoreScroll } from "../common/decorators/restore-scroll"; import { navigate } from "../common/navigate"; +import { LocalizeFunc } from "../common/translations/localize"; import { computeRTL } from "../common/util/compute_rtl"; import "../components/ha-icon"; import "../components/ha-icon-button-arrow-prev"; @@ -40,7 +41,9 @@ export interface PageNavigation { class HassTabsSubpage extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public hassio = false; + @property({ type: Boolean }) public supervisor = false; + + @property({ attribute: false }) public localizeFunc?: LocalizeFunc; @property({ type: String, attribute: "back-path" }) public backPath?: string; @@ -48,9 +51,9 @@ class HassTabsSubpage extends LitElement { @property({ type: Boolean, attribute: "main-page" }) public mainPage = false; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; - @property() public tabs!: PageNavigation[]; + @property({ attribute: false }) public tabs!: PageNavigation[]; @property({ type: Boolean, reflect: true }) public narrow = false; @@ -71,7 +74,8 @@ class HassTabsSubpage extends LitElement { showAdvanced: boolean | undefined, _components, _language, - _narrow + _narrow, + localizeFunc ) => { const shownTabs = tabs.filter( (page) => @@ -91,7 +95,7 @@ class HassTabsSubpage extends LitElement { .active=${page === activeTab} .narrow=${this.narrow} .name=${page.translationKey - ? this.hass.localize(page.translationKey) + ? localizeFunc(page.translationKey) : page.name} > ${page.iconPath @@ -130,7 +134,8 @@ class HassTabsSubpage extends LitElement { this.hass.userData?.showAdvanced, this.hass.config.components, this.hass.language, - this.narrow + this.narrow, + this.localizeFunc || this.hass.localize ); const showTabs = tabs.length > 1 || !this.narrow; return html` @@ -138,7 +143,7 @@ class HassTabsSubpage extends LitElement { ${this.mainPage ? html` diff --git a/src/mixins/lit-localize-lite-mixin.ts b/src/mixins/lit-localize-lite-mixin.ts index f1d2cf072c..cc723a36ac 100644 --- a/src/mixins/lit-localize-lite-mixin.ts +++ b/src/mixins/lit-localize-lite-mixin.ts @@ -1,7 +1,7 @@ import { LitElement, property, PropertyValues } from "lit-element"; import { computeLocalize, LocalizeFunc } from "../common/translations/localize"; import { Constructor, Resources } from "../types"; -import { getLocalLanguage, getTranslation } from "../util/hass-translation"; +import { getTranslation, getLocalLanguage } from "../util/hass-translation"; const empty = () => ""; diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 6c882bb6a3..541ceabd16 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -161,8 +161,8 @@ export class HaConfigDevicePage extends LitElement { const batteryState = batteryEntity ? this.hass.states[batteryEntity.entity_id] : undefined; - const batteryIsBinary = batteryState - && computeStateDomain(batteryState) === "binary_sensor"; + const batteryIsBinary = + batteryState && computeStateDomain(batteryState) === "binary_sensor"; const batteryChargingState = batteryChargingEntity ? this.hass.states[batteryChargingEntity.entity_id] : undefined; diff --git a/src/panels/developer-tools/state/developer-tools-state.js b/src/panels/developer-tools/state/developer-tools-state.js index 5992f3e39f..a170068bc1 100644 --- a/src/panels/developer-tools/state/developer-tools-state.js +++ b/src/panels/developer-tools/state/developer-tools-state.js @@ -1,7 +1,7 @@ import "@material/mwc-button"; import { mdiInformationOutline, - mdiClipboardTextMultipleOutline + mdiClipboardTextMultipleOutline, } from "@mdi/js"; import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-input/paper-input"; @@ -169,7 +169,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { [[localize('ui.panel.developer-tools.tabs.states.state')]] [[localize('ui.panel.developer-tools.tabs.states.attributes')]] - + @@ -285,7 +288,9 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { _showAttributes: { type: Boolean, - value: JSON.parse(localStorage.getItem("devToolsShowAttributes") || true), + value: JSON.parse( + localStorage.getItem("devToolsShowAttributes") || true + ), }, _entities: { diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts index f6f34d0f25..49818e0b8a 100644 --- a/src/panels/lovelace/cards/hui-picture-entity-card.ts +++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts @@ -199,7 +199,10 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard { left: 0; right: 0; bottom: 0; - background-color: var(--ha-picture-card-background-color, rgba(0, 0, 0, 0.3)); + background-color: var( + --ha-picture-card-background-color, + rgba(0, 0, 0, 0.3) + ); padding: 16px; font-size: 16px; line-height: 16px; diff --git a/src/panels/lovelace/cards/hui-picture-glance-card.ts b/src/panels/lovelace/cards/hui-picture-glance-card.ts index dd0999ffa0..3f9a9930bf 100644 --- a/src/panels/lovelace/cards/hui-picture-glance-card.ts +++ b/src/panels/lovelace/cards/hui-picture-glance-card.ts @@ -314,7 +314,10 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { left: 0; right: 0; bottom: 0; - background-color: var(--ha-picture-card-background-color, rgba(0, 0, 0, 0.3)); + background-color: var( + --ha-picture-card-background-color, + rgba(0, 0, 0, 0.3) + ); padding: 4px 8px; font-size: 16px; line-height: 40px; diff --git a/src/state/translations-mixin.ts b/src/state/translations-mixin.ts index 8be391e3a3..cfb99bf845 100644 --- a/src/state/translations-mixin.ts +++ b/src/state/translations-mixin.ts @@ -12,8 +12,8 @@ import { translationMetadata } from "../resources/translations-metadata"; import { Constructor, HomeAssistant } from "../types"; import { storeState } from "../util/ha-pref-storage"; import { - getLocalLanguage, getTranslation, + getLocalLanguage, getUserLanguage, } from "../util/hass-translation"; import { HassBaseEl } from "./hass-base-mixin"; diff --git a/src/translations/en.json b/src/translations/en.json index d293cea316..1df9ec42d1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3417,5 +3417,46 @@ } } } + }, + "supervisor": { + "addon": { + "panel": { + "configuration": "Configuration", + "documentation": "Documentation", + "info": "Info", + "log": "Log" + } + }, + "common": { + "cancel": "Cancel", + "newest_version": "Newest Version", + "release_notes": "Release notes", + "update": "Update", + "version": "Version" + }, + "confirm": { + "update": { + "title": "Update ${name}", + "text": "Are you sure you want to update {name} to version {version}?" + } + }, + "dashboard": { + "addon_new_version": "New version available", + "addon_running": "Add-on is running", + "addon_stopped": "Add-on is stopped", + "addons": "Add-ons", + "no_addons": "You don't have any add-ons installed yet. Head over to the add-on store to get started!", + "update_available": "{count, plural,\n one {Update}\n other {{count} Updates}\n} pending" + }, + "error": { + "unknown": "Unknown error", + "update_failed": "Update failed" + }, + "panel": { + "dashboard": "Dashboard", + "snapshots": "Snapshots", + "store": "Add-on Store", + "system": "System" + } } } diff --git a/src/util/common-translation.ts b/src/util/common-translation.ts new file mode 100644 index 0000000000..7f7e99b3fa --- /dev/null +++ b/src/util/common-translation.ts @@ -0,0 +1,56 @@ +import { translationMetadata } from "../resources/translations-metadata"; + +const DEFAULT_BASE_URL = "/static/translations"; + +// Store loaded translations in memory so translations are available immediately +// when DOM is created in Polymer. Even a cache lookup creates noticeable latency. +const translations = {}; + +async function fetchTranslation(fingerprint: string, base_url: string) { + const response = await fetch(`${base_url}/${fingerprint}`, { + credentials: "same-origin", + }); + if (!response.ok) { + throw new Error( + `Fail to fetch translation ${fingerprint}: HTTP response status is ${response.status}` + ); + } + return response.json(); +} + +export async function getTranslation( + fragment: string | null, + language: string, + base_url?: string +) { + const metadata = translationMetadata.translations[language]; + if (!metadata) { + if (language !== "en") { + return getTranslation(fragment, "en", base_url); + } + throw new Error("Language en is not found in metadata"); + } + + // nl-abcd.jon or logbook/nl-abcd.json + const fingerprint = `${fragment ? fragment + "/" : ""}${language}-${ + metadata.hash + }.json`; + + // Fetch translation from the server + if (!translations[fingerprint]) { + translations[fingerprint] = fetchTranslation( + fingerprint, + base_url || DEFAULT_BASE_URL + ) + .then((data) => ({ language, data })) + .catch((error) => { + delete translations[fingerprint]; + if (language !== "en") { + // Couldn't load selected translation. Try a fall back to en before failing. + return getTranslation(fragment, "en", base_url); + } + return Promise.reject(error); + }); + } + return translations[fingerprint]; +} diff --git a/src/util/hass-translation.ts b/src/util/hass-translation.ts index 67aa62b0c4..e0be32bd0a 100644 --- a/src/util/hass-translation.ts +++ b/src/util/hass-translation.ts @@ -1,6 +1,7 @@ import { fetchTranslationPreferences } from "../data/translation"; import { translationMetadata } from "../resources/translations-metadata"; import { HomeAssistant } from "../types"; +import { getTranslation as commonGetTranslation } from "./common-translation"; const STORAGE = window.localStorage || {}; @@ -93,55 +94,13 @@ export function getLocalLanguage() { return "en"; } -// Store loaded translations in memory so translations are available immediately -// when DOM is created in Polymer. Even a cache lookup creates noticeable latency. -const translations = {}; - -async function fetchTranslation(fingerprint) { - const response = await fetch(`/static/translations/${fingerprint}`, { - credentials: "same-origin", - }); - if (!response.ok) { - throw new Error( - `Fail to fetch translation ${fingerprint}: HTTP response status is ${response.status}` - ); - } - return response.json(); -} - export async function getTranslation( fragment: string | null, language: string ) { - const metadata = translationMetadata.translations[language]; - if (!metadata) { - if (language !== "en") { - return getTranslation(fragment, "en"); - } - throw new Error("Language en is not found in metadata"); - } - - // nl-abcd.jon or logbook/nl-abcd.json - const fingerprint = `${fragment ? fragment + "/" : ""}${language}-${ - metadata.hash - }.json`; - - // Fetch translation from the server - if (!translations[fingerprint]) { - translations[fingerprint] = fetchTranslation(fingerprint) - .then((data) => ({ language, data })) - .catch((error) => { - delete translations[fingerprint]; - if (language !== "en") { - // Couldn't load selected translation. Try a fall back to en before failing. - return getTranslation(fragment, "en"); - } - return Promise.reject(error); - }); - } - return translations[fingerprint]; + return commonGetTranslation(fragment, language); } // Load selected translation into memory immediately so it is ready when Polymer // initializes. -getTranslation(null, getLocalLanguage()); +commonGetTranslation(null, getLocalLanguage());