Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/common/entity/compute_state_display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time";
import { LocalizeFunc } from "../translations/localize";
import { computeStateDomain } from "./compute_state_domain";
import { numberFormat } from "../string/number-format";
import { formatNumber } from "../string/format_number";

export const computeStateDisplay = (
localize: LocalizeFunc,
Expand All @@ -20,7 +20,7 @@ export const computeStateDisplay = (
}

if (stateObj.attributes.unit_of_measurement) {
return `${numberFormat(compareState, language)} ${
return `${formatNumber(compareState, language)} ${
stateObj.attributes.unit_of_measurement
}`;
}
Expand Down
53 changes: 53 additions & 0 deletions src/common/string/format_number.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Formats a number based on the specified language with thousands separator(s) and decimal character for better legibility.
*
* @param num The number to format
* @param language The language to use when formatting the number
*/
export const formatNumber = (
num: string | number,
language: string,
options?: Intl.NumberFormatOptions
): string => {
// Polyfill for Number.isNaN, which is more reliable than the global isNaN()
Number.isNaN =
Number.isNaN ||
function isNaN(input) {
return typeof input === "number" && isNaN(input);
};

if (!Number.isNaN(Number(num)) && Intl) {
return new Intl.NumberFormat(
language,
getDefaultFormatOptions(num, options)
).format(Number(num));
}
return num.toString();
};

/**
* Generates default options for Intl.NumberFormat
* @param num The number to be formatted
* @param options The Intl.NumberFormatOptions that should be included in the returned options
*/
const getDefaultFormatOptions = (
num: string | number,
options?: Intl.NumberFormatOptions
): Intl.NumberFormatOptions => {
let defaultOptions: Intl.NumberFormatOptions = options || {};

// Keep decimal trailing zeros if they are present
if (
!options ||
(!options.minimumFractionDigits && !options.maximumFractionDigits)
) {
const digits =
num.toString().indexOf(".") > -1
? num.toString().split(".")[1].length
: 0;
defaultOptions.minimumFractionDigits = digits;
defaultOptions.maximumFractionDigits = digits;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the use case here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bramkragten the current implementation accepts a number or a string. This ensures it works with state values (which are type string in HassEntityBase) and attributes which can be type number. If a sensor intentionally stores a numeric value with a trailing zero (see sensor.processor_temperature value in #7787), this code would preserve the decimals instead of dropping the trailing zero(s).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we should only apply it on strings?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bramkragten I've updated this function to return early if the value is not a string.


return defaultOptions;
};
22 changes: 0 additions & 22 deletions src/common/string/number-format.ts

This file was deleted.

3 changes: 2 additions & 1 deletion src/components/entity/ha-state-label-badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { timerTimeRemaining } from "../../common/entity/timer_time_remaining";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { formatNumber } from "../../common/string/format_number";

@customElement("ha-state-label-badge")
export class HaStateLabelBadge extends LitElement {
Expand Down Expand Up @@ -115,7 +116,7 @@ export class HaStateLabelBadge extends LitElement {
: state.state === UNKNOWN
? "-"
: state.attributes.unit_of_measurement
? state.state
? formatNumber(state.state, this.hass!.language)
: computeStateDisplay(
this.hass!.localize,
state,
Expand Down
4 changes: 3 additions & 1 deletion src/components/ha-gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export class Gauge extends LitElement {

@property({ type: Number }) public value = 0;

@property({ type: String }) public valueFormatted = this.value.toString();

@property() public label = "";

@internalProperty() private _angle = 0;
Expand Down Expand Up @@ -88,7 +90,7 @@ export class Gauge extends LitElement {
</svg>
<svg class="text">
<text class="value-text">
${this.value} ${this.label}
${this.valueFormatted} ${this.label}
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
</text>
</svg>`;
}
Expand Down
12 changes: 9 additions & 3 deletions src/data/weather.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@mdi/js";
import { css, html, svg, SVGTemplateResult, TemplateResult } from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
import { formatNumber } from "../common/string/format_number";
import "../components/ha-icon";
import "../components/ha-svg-icon";
import type { HomeAssistant, WeatherEntity } from "../types";
Expand Down Expand Up @@ -106,15 +107,19 @@ export const getWind = (
speed: string,
bearing: string
): string => {
const speedText = `${formatNumber(speed, hass!.language)} ${getWeatherUnit(
hass!,
"wind_speed"
)}`;
if (bearing !== null) {
const cardinalDirection = getWindBearing(bearing);
return `${speed} ${getWeatherUnit(hass!, "wind_speed")} (${
return `${speedText} (${
hass.localize(
`ui.card.weather.cardinal_direction.${cardinalDirection.toLowerCase()}`
) || cardinalDirection
})`;
}
return `${speed} ${getWeatherUnit(hass!, "wind_speed")}`;
return speedText;
};

export const getWeatherUnit = (
Expand Down Expand Up @@ -175,7 +180,8 @@ export const getSecondaryWeatherAttribute = (
<ha-svg-icon class="attr-icon" .path=${weatherAttrIcon}></ha-svg-icon>
`
: hass!.localize(`ui.card.weather.attributes.${attribute}`)}
${roundWithOneDecimal(value)} ${getWeatherUnit(hass!, attribute)}
${formatNumber(roundWithOneDecimal(value), hass!.language)}
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
${getWeatherUnit(hass!, attribute)}
`;
};

Expand Down
28 changes: 22 additions & 6 deletions src/dialogs/more-info/controls/more-info-weather.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
mdiWeatherWindy,
mdiWeatherWindyVariant,
} from "@mdi/js";
import { formatNumber } from "../../../common/string/format_number";

const weatherIcons = {
"clear-night": mdiWeatherNight,
Expand Down Expand Up @@ -88,7 +89,10 @@ class MoreInfoWeather extends LitElement {
${this.hass.localize("ui.card.weather.attributes.temperature")}
</div>
<div>
${this.stateObj.attributes.temperature}
${formatNumber(
this.stateObj.attributes.temperature,
this.hass!.language
)}
${getWeatherUnit(this.hass, "temperature")}
</div>
</div>
Expand All @@ -100,7 +104,10 @@ class MoreInfoWeather extends LitElement {
${this.hass.localize("ui.card.weather.attributes.air_pressure")}
</div>
<div>
${this.stateObj.attributes.pressure}
${formatNumber(
this.stateObj.attributes.pressure,
this.hass!.language
)}
${getWeatherUnit(this.hass, "air_pressure")}
</div>
</div>
Expand All @@ -113,7 +120,13 @@ class MoreInfoWeather extends LitElement {
<div class="main">
${this.hass.localize("ui.card.weather.attributes.humidity")}
</div>
<div>${this.stateObj.attributes.humidity} %</div>
<div>
${formatNumber(
this.stateObj.attributes.humidity,
this.hass!.language
)}
%
</div>
</div>
`
: ""}
Expand Down Expand Up @@ -142,7 +155,10 @@ class MoreInfoWeather extends LitElement {
${this.hass.localize("ui.card.weather.attributes.visibility")}
</div>
<div>
${this.stateObj.attributes.visibility}
${formatNumber(
this.stateObj.attributes.visibility,
this.hass!.language
)}
${getWeatherUnit(this.hass, "length")}
</div>
</div>
Expand Down Expand Up @@ -176,13 +192,13 @@ class MoreInfoWeather extends LitElement {
${this.computeDate(item.datetime)}
</div>
<div class="templow">
${item.templow}
${formatNumber(item.templow, this.hass!.language)}
${getWeatherUnit(this.hass, "temperature")}
</div>
`
: ""}
<div class="temp">
${item.temperature}
${formatNumber(item.temperature, this.hass!.language)}
${getWeatherUnit(this.hass, "temperature")}
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/panels/lovelace/cards/hui-entity-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import { HuiErrorCard } from "./hui-error-card";
import { EntityCardConfig } from "./types";
import { computeCardSize } from "../common/compute-card-size";
import { formatNumber } from "../../../common/string/format_number";

@customElement("hui-entity-card")
export class HuiEntityCard extends LitElement implements LovelaceCard {
Expand Down Expand Up @@ -127,7 +128,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
? stateObj.attributes[this._config.attribute!] ??
this.hass.localize("state.default.unknown")
: stateObj.attributes.unit_of_measurement
? stateObj.state
? formatNumber(stateObj.state, this.hass!.language)
: computeStateDisplay(
this.hass.localize,
stateObj,
Expand Down
2 changes: 2 additions & 0 deletions src/panels/lovelace/cards/hui-gauge-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { formatNumber } from "../../../common/string/format_number";
import "../../../components/ha-card";
import "../../../components/ha-gauge";
import type { HomeAssistant } from "../../../types";
Expand Down Expand Up @@ -116,6 +117,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
.min=${this._config.min!}
.max=${this._config.max!}
.value=${state}
.valueFormatted=${formatNumber(state, this.hass!.language)}
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
.label=${this._config!.unit ||
this.hass?.states[this._config!.entity].attributes
.unit_of_measurement ||
Expand Down
48 changes: 41 additions & 7 deletions src/panels/lovelace/cards/hui-thermostat-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { UNIT_F } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { formatNumber } from "../../../common/string/format_number";
import "../../../components/ha-card";
import type { HaCard } from "../../../components/ha-card";
import "../../../components/ha-icon-button";
Expand Down Expand Up @@ -143,7 +144,10 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
text-anchor="middle"
style="font-size: 13px;"
>
${stateObj.attributes.current_temperature}
${formatNumber(
stateObj.attributes.current_temperature,
this.hass!.language
)}
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
${this.hass.config.unit_system.temperature}
</tspan>
Expand All @@ -164,19 +168,49 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
: Array.isArray(this._setTemp)
? this._stepSize === 1
? svg`
${this._setTemp[0].toFixed()} -
${this._setTemp[1].toFixed()}
${formatNumber(
this._setTemp[0].toFixed(),
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
this.hass!.language
)} -
${formatNumber(
this._setTemp[1].toFixed(),
this.hass!.language
)}
`
: svg`
${this._setTemp[0].toFixed(1)} -
${this._setTemp[1].toFixed(1)}
${formatNumber(
this._setTemp[0].toFixed(1),
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
this.hass!.language,
{
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}
)} -
${formatNumber(
this._setTemp[1].toFixed(1),
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
this.hass!.language,
{
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}
)}
`
: this._stepSize === 1
? svg`
${this._setTemp.toFixed()}
${formatNumber(
this._setTemp.toFixed(),
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
this.hass!.language
)}
`
: svg`
${this._setTemp.toFixed(1)}
${formatNumber(
this._setTemp.toFixed(1),
Comment thread
joshmcrty marked this conversation as resolved.
Outdated
this.hass!.language,
{
minimumFractionDigits: 1,
maximumFractionDigits: 1,
}
)}
`
}
</text>
Expand Down
Loading