diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index fd99a022da..1836d5d6a5 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -4,7 +4,7 @@ import { FrontendLocaleData } from "../../data/translation"; import { formatDate } from "../datetime/format_date"; import { formatDateTime } from "../datetime/format_date_time"; import { formatTime } from "../datetime/format_time"; -import { formatNumber } from "../number/format_number"; +import { formatNumber, isNumericState } from "../number/format_number"; import { LocalizeFunc } from "../translations/localize"; import { computeStateDomain } from "./compute_state_domain"; @@ -20,7 +20,8 @@ export const computeStateDisplay = ( return localize(`state.default.${compareState}`); } - if (stateObj.attributes.unit_of_measurement) { + // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` + if (isNumericState(stateObj)) { if (stateObj.attributes.device_class === "monetary") { try { return formatNumber(compareState, locale, { @@ -31,8 +32,10 @@ export const computeStateDisplay = ( // fallback to default } } - return `${formatNumber(compareState, locale)} ${ + return `${formatNumber(compareState, locale)}${ stateObj.attributes.unit_of_measurement + ? " " + stateObj.attributes.unit_of_measurement + : "" }`; } diff --git a/src/common/number/format_number.ts b/src/common/number/format_number.ts index 89ad6da7a6..7cecd1c514 100644 --- a/src/common/number/format_number.ts +++ b/src/common/number/format_number.ts @@ -1,6 +1,15 @@ +import { HassEntity } from "home-assistant-js-websocket"; import { FrontendLocaleData, NumberFormat } from "../../data/translation"; import { round } from "./round"; +/** + * Returns true if the entity is considered numeric based on the attributes it has + * @param stateObj The entity state object + */ +export const isNumericState = (stateObj: HassEntity): boolean => + !!stateObj.attributes.unit_of_measurement || + !!stateObj.attributes.state_class; + export const numberFormatToLocale = ( localeOptions: FrontendLocaleData ): string | string[] | undefined => { diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index 1bdc3b2ea4..19ea56dbcf 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -14,7 +14,10 @@ import secondsToDuration from "../../common/datetime/seconds_to_duration"; import { computeStateDisplay } from "../../common/entity/compute_state_display"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; -import { formatNumber } from "../../common/number/format_number"; +import { + formatNumber, + isNumericState, +} from "../../common/number/format_number"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { timerTimeRemaining } from "../../data/timer"; import { HomeAssistant } from "../../types"; @@ -145,7 +148,7 @@ export class HaStateLabelBadge extends LitElement { return entityState.state === UNKNOWN || entityState.state === UNAVAILABLE ? "-" - : entityState.attributes.unit_of_measurement + : isNumericState(entityState) ? formatNumber(entityState.state, this.hass!.locale) : computeStateDisplay( this.hass!.localize, diff --git a/src/panels/lovelace/cards/hui-entity-card.ts b/src/panels/lovelace/cards/hui-entity-card.ts index 6e3e3d4728..890f95dec2 100644 --- a/src/panels/lovelace/cards/hui-entity-card.ts +++ b/src/panels/lovelace/cards/hui-entity-card.ts @@ -15,7 +15,10 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { isValidEntityId } from "../../../common/entity/valid_entity_id"; -import { formatNumber } from "../../../common/number/format_number"; +import { + formatNumber, + isNumericState, +} from "../../../common/number/format_number"; import { iconColorCSS } from "../../../common/style/icon_color_css"; import "../../../components/ha-card"; import "../../../components/ha-icon"; @@ -143,7 +146,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { stateObj.attributes[this._config.attribute!] ) : this.hass.localize("state.default.unknown") - : stateObj.attributes.unit_of_measurement + : isNumericState(stateObj) ? formatNumber(stateObj.state, this.hass.locale) : computeStateDisplay( this.hass.localize, diff --git a/test/common/entity/compute_state_display.ts b/test/common/entity/compute_state_display.ts index 0ac83da339..4d9e2cca67 100644 --- a/test/common/entity/compute_state_display.ts +++ b/test/common/entity/compute_state_display.ts @@ -96,6 +96,20 @@ describe("computeStateDisplay", () => { ); }); + it("Localizes and formats numeric sensor value with state_class", () => { + const stateObj: any = { + entity_id: "sensor.test", + state: "1234.5", + attributes: { + state_class: "measurement", + }, + }; + assert.strictEqual( + computeStateDisplay(localize, stateObj, localeData), + "1,234.5 m" + ); + }); + it("Localizes unknown sensor value with units", () => { const altLocalize = (message, ...args) => { if (message === "state.sensor.unknown") {