Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create number formatting options on the profile panel #7925

Merged
merged 51 commits into from
Mar 28, 2021
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
eb25178
Add number format options to profile panel
joshmcrty Dec 7, 2020
3a7ac31
Update frontend to use number format options.
joshmcrty Dec 7, 2020
2fd3b66
Localize number format options on profile page
joshmcrty Dec 7, 2020
2317f6d
Handle space separator for thousands, add fallback locales for formats
joshmcrty Dec 8, 2020
152b76b
Catch error if locale is invalid and use browser default locale
joshmcrty Dec 8, 2020
5ebcf06
Add system and space_comma format options
joshmcrty Dec 8, 2020
05d1901
Show example formatted number in description
joshmcrty Dec 8, 2020
69e7755
Avoid lint error
joshmcrty Dec 8, 2020
bdfe203
Update state-card-display LitElement to use number format options
joshmcrty Jan 25, 2021
0dc92a6
Removed unused CSS
joshmcrty Jan 25, 2021
0e77419
Move number format examples into second line on options
joshmcrty Jan 25, 2021
a045bb9
Remove old state-card-display.js file
joshmcrty Jan 25, 2021
c5746b0
Remove unused imports
joshmcrty Jan 26, 2021
2e95f99
Save number_format to user data language object instead of core object
joshmcrty Jan 26, 2021
a31e19d
Refactor hass.language as an object with language & number_format props
joshmcrty Jan 28, 2021
93dd5fc
Refactor format_date and format_time functions
joshmcrty Jan 29, 2021
4a2d006
Reset devcontainer file
joshmcrty Jan 29, 2021
5f01e21
Minor code comment fix
joshmcrty Jan 29, 2021
59421cc
Only load backend user translation preferences once
joshmcrty Jan 29, 2021
3154b4a
Fix decorator for language property
joshmcrty Jan 29, 2021
9753056
Save entire hass.language object to selectedLanguage
joshmcrty Feb 24, 2021
d2e5c4b
Fix up after rebase
joshmcrty Feb 24, 2021
f349d42
Use hass.locale to store language and number_format preferences
joshmcrty Mar 11, 2021
ca933a7
Add tests for formatNumber
joshmcrty Mar 11, 2021
0d573bb
Use system/browser locale number formatting by default
joshmcrty Mar 12, 2021
750904b
Revert change to auto setting; use language as auto
joshmcrty Mar 16, 2021
7aad56c
Update format_number test
joshmcrty Mar 16, 2021
ed4dfc8
Update other tests
joshmcrty Mar 16, 2021
18e2c36
Update new code to use hass.locale
joshmcrty Mar 16, 2021
e3f6ccc
Remove unneeded function for getting available user language
joshmcrty Mar 18, 2021
ee0baed
Return empty string if format_number is called without a number value
joshmcrty Mar 21, 2021
594d433
Remove unused import
joshmcrty Mar 21, 2021
fd9690c
Fix space before percent symbol
joshmcrty Mar 21, 2021
2ae5df6
Use formatDateTime for persistent notification tooltip
joshmcrty Mar 22, 2021
cb46328
Rerender when any locale option changes
joshmcrty Mar 23, 2021
6051f99
Rename language to locale
joshmcrty Mar 23, 2021
c7f177d
Localize number format label
joshmcrty Mar 23, 2021
135ec59
Revert to using hass.language if possible to simplify PR changes
joshmcrty Mar 23, 2021
5780b33
Use separate event for selecting number_format
joshmcrty Mar 23, 2021
259bfdf
Rename language property to locale
joshmcrty Mar 23, 2021
dd1da99
Iterate over number formats to simplify template
joshmcrty Mar 23, 2021
ec667d7
Use actual formatted number instead of spaces in localized value
joshmcrty Mar 23, 2021
c884814
Revert to original logic for language; update function name
joshmcrty Mar 23, 2021
e6bf46b
Ensure hass.language getter is retained when calling _updateHass()
joshmcrty Mar 23, 2021
425a009
Remove unused code
joshmcrty Mar 23, 2021
df513ff
Fix type errors
joshmcrty Mar 24, 2021
28423b4
Pass individual property to fireEvent for language and number_format
joshmcrty Mar 24, 2021
9f83453
Revert using getter for hass.language
joshmcrty Mar 24, 2021
b14b0c1
Clean up logic
joshmcrty Mar 24, 2021
5299230
Return Partial<FrontendTranslationData> in getUserLocale
joshmcrty Mar 24, 2021
66ac985
Review cleanup
joshmcrty Mar 28, 2021
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
2 changes: 1 addition & 1 deletion cast/src/receiver/layout/hc-lovelace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class HcLovelace extends LitElement {
urlPath: this.urlPath!,
enableFullEditMode: () => undefined,
mode: "storage",
language: "en",
locale: this.hass.locale,
saveConfig: async () => undefined,
deleteConfig: async () => undefined,
setEditMode: () => undefined,
Expand Down
9 changes: 5 additions & 4 deletions src/common/datetime/format_date.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { format } from "fecha";
import { FrontendTranslationData } from "../../data/translation";
import { toLocaleDateStringSupportsOptions } from "./check_options_support";

export const formatDate = toLocaleDateStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleDateString(locales, {
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleDateString(locales.language, {
year: "numeric",
month: "long",
day: "numeric",
})
: (dateObj: Date) => format(dateObj, "longDate");

export const formatDateWeekday = toLocaleDateStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleDateString(locales, {
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleDateString(locales.language, {
weekday: "long",
month: "short",
day: "numeric",
Expand Down
9 changes: 5 additions & 4 deletions src/common/datetime/format_date_time.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { format } from "fecha";
import { FrontendTranslationData } from "../../data/translation";
import { toLocaleStringSupportsOptions } from "./check_options_support";

export const formatDateTime = toLocaleStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleString(locales, {
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleString(locales.language, {
year: "numeric",
month: "long",
day: "numeric",
Expand All @@ -13,8 +14,8 @@ export const formatDateTime = toLocaleStringSupportsOptions
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY, HH:mm");

export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleString(locales, {
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleString(locales.language, {
year: "numeric",
month: "long",
day: "numeric",
Expand Down
13 changes: 7 additions & 6 deletions src/common/datetime/format_time.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { format } from "fecha";
import { FrontendTranslationData } from "../../data/translation";
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";

export const formatTime = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleTimeString(locales, {
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleTimeString(locales.language, {
hour: "numeric",
minute: "2-digit",
})
: (dateObj: Date) => format(dateObj, "shortTime");

export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleTimeString(locales, {
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleTimeString(locales.language, {
hour: "numeric",
minute: "2-digit",
second: "2-digit",
})
: (dateObj: Date) => format(dateObj, "mediumTime");

export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleTimeString(locales, {
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleTimeString(locales.language, {
weekday: "long",
hour: "numeric",
minute: "2-digit",
Expand Down
16 changes: 9 additions & 7 deletions src/common/entity/compute_state_display.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { FrontendTranslationData } from "../../data/translation";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time";
Expand All @@ -10,7 +11,7 @@ import { computeStateDomain } from "./compute_state_domain";
export const computeStateDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
language: string,
locale: FrontendTranslationData,
state?: string
): string => {
const compareState = state !== undefined ? state : stateObj.state;
Expand All @@ -20,7 +21,7 @@ export const computeStateDisplay = (
}

if (stateObj.attributes.unit_of_measurement) {
return `${formatNumber(compareState, language)} ${
return `${formatNumber(compareState, locale)} ${
stateObj.attributes.unit_of_measurement
}`;
}
Expand All @@ -35,7 +36,7 @@ export const computeStateDisplay = (
stateObj.attributes.month - 1,
stateObj.attributes.day
);
return formatDate(date, language);
return formatDate(date, locale);
}
if (!stateObj.attributes.has_date) {
const now = new Date();
Expand All @@ -48,7 +49,7 @@ export const computeStateDisplay = (
stateObj.attributes.hour,
stateObj.attributes.minute
);
return formatTime(date, language);
return formatTime(date, locale);
}

date = new Date(
Expand All @@ -58,7 +59,7 @@ export const computeStateDisplay = (
stateObj.attributes.hour,
stateObj.attributes.minute
);
return formatDateTime(date, language);
return formatDateTime(date, locale);
}

if (domain === "humidifier") {
Expand All @@ -67,8 +68,9 @@ export const computeStateDisplay = (
}
}

if (domain === "counter") {
return formatNumber(compareState, language);
// `counter` and `number` domains do not have a unit of measurement but should still use `formatNumber`
if (domain === "counter" || domain === "number") {
return formatNumber(compareState, locale);
}

return (
Expand Down
54 changes: 45 additions & 9 deletions src/common/string/format_number.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,64 @@
import { FrontendTranslationData, NumberFormat } from "../../data/translation";

/**
* Formats a number based on the specified language with thousands separator(s) and decimal character for better legibility.
* Formats a number based on the user's preference 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
* @param locale The user-selected language and number format, from `hass.locale`
* @param options Intl.NumberFormatOptions to use
*/
export const formatNumber = (
num: string | number,
language: string,
locale?: FrontendTranslationData,
options?: Intl.NumberFormatOptions
): string => {
let format: string | string[] | undefined;

switch (locale?.number_format) {
case NumberFormat.comma_decimal:
format = ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
break;
case NumberFormat.decimal_comma:
format = ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
break;
case NumberFormat.space_comma:
format = ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
break;
case NumberFormat.system:
format = undefined;
break;
default:
format = locale?.language;
}

// 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));
if (
!Number.isNaN(Number(num)) &&
Intl &&
locale?.number_format !== NumberFormat.none
) {
try {
return new Intl.NumberFormat(
format,
getDefaultFormatOptions(num, options)
).format(Number(num));
} catch (error) {
// Don't fail when using "TEST" language
// eslint-disable-next-line no-console
console.error(error);
return new Intl.NumberFormat(
undefined,
getDefaultFormatOptions(num, options)
).format(Number(num));
}
}
return num.toString();
return num ? num.toString() : "";
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/components/entity/ha-chart-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ class HaChartBase extends mixinBehaviors(
return value;
}
const date = new Date(values[index].value);
return formatTime(date, this.hass.language);
return formatTime(date, this.hass.locale);
}

drawChart() {
Expand Down
8 changes: 2 additions & 6 deletions src/components/entity/ha-state-label-badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,8 @@ export class HaStateLabelBadge extends LitElement {
: state.state === UNKNOWN
? "-"
: state.attributes.unit_of_measurement
? formatNumber(state.state, this.hass!.language)
: computeStateDisplay(
this.hass!.localize,
state,
this.hass!.language
);
? formatNumber(state.state, this.hass!.locale)
: computeStateDisplay(this.hass!.localize, state, this.hass!.locale);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/entity/state-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class StateInfo extends LitElement {
}

const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
if (!oldHass || oldHass.locale !== this.hass.locale) {
this.rtl = computeRTL(this.hass);
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/components/ha-climate-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ class HaClimateState extends LitElement {
if (this.stateObj.attributes.current_temperature != null) {
return `${formatNumber(
this.stateObj.attributes.current_temperature,
this.hass!.language
this.hass.locale
)} ${this.hass.config.unit_system.temperature}`;
}

if (this.stateObj.attributes.current_humidity != null) {
return `${formatNumber(
this.stateObj.attributes.current_humidity,
this.hass!.language
this.hass.locale
)} %`;
}

Expand All @@ -78,17 +78,17 @@ class HaClimateState extends LitElement {
) {
return `${formatNumber(
this.stateObj.attributes.target_temp_low,
this.hass!.language
this.hass.locale
)}-${formatNumber(
this.stateObj.attributes.target_temp_high,
this.hass!.language
this.hass.locale
)} ${this.hass.config.unit_system.temperature}`;
}

if (this.stateObj.attributes.temperature != null) {
return `${formatNumber(
this.stateObj.attributes.temperature,
this.hass!.language
this.hass.locale
)} ${this.hass.config.unit_system.temperature}`;
}
if (
Expand All @@ -97,17 +97,17 @@ class HaClimateState extends LitElement {
) {
return `${formatNumber(
this.stateObj.attributes.target_humidity_low,
this.hass!.language
this.hass.locale
)}-${formatNumber(
this.stateObj.attributes.target_humidity_high,
this.hass!.language
this.hass.locale
)} %`;
}

if (this.stateObj.attributes.humidity != null) {
return `${formatNumber(
this.stateObj.attributes.humidity,
this.hass!.language
this.hass.locale
)} %`;
}

Expand Down
6 changes: 3 additions & 3 deletions src/components/ha-date-range-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class HaDateRangePicker extends LitElement {
protected updated(changedProps: PropertyValues) {
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
if (!oldHass || oldHass.locale !== this.hass.locale) {
this._hour24format = this._compute24hourFormat();
this._rtlDirection = computeRTLDirection(this.hass);
}
Expand All @@ -62,7 +62,7 @@ export class HaDateRangePicker extends LitElement {
<div slot="input" class="date-range-inputs">
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
<paper-input
.value=${formatDateTime(this.startDate, this.hass.language)}
.value=${formatDateTime(this.startDate, this.hass.locale)}
.label=${this.hass.localize(
"ui.components.date-range-picker.start_date"
)}
Expand All @@ -71,7 +71,7 @@ export class HaDateRangePicker extends LitElement {
readonly
></paper-input>
<paper-input
.value=${formatDateTime(this.endDate, this.hass.language)}
.value=${formatDateTime(this.endDate, this.hass.locale)}
label=${this.hass.localize(
"ui.components.date-range-picker.end_date"
)}
Expand Down
5 changes: 3 additions & 2 deletions src/components/ha-gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ifDefined } from "lit-html/directives/if-defined";
import { styleMap } from "lit-html/directives/style-map";
import { formatNumber } from "../common/string/format_number";
import { afterNextRender } from "../common/util/render-status";
import { FrontendTranslationData } from "../data/translation";
import { getValueInPercentage, normalize } from "../util/calculate";

const getAngle = (value: number, min: number, max: number) => {
Expand All @@ -29,7 +30,7 @@ export class Gauge extends LitElement {

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

@property({ type: String }) public language = "";
@property() public locale!: FrontendTranslationData;

@property() public label = "";

Expand Down Expand Up @@ -90,7 +91,7 @@ export class Gauge extends LitElement {
</svg>
<svg class="text">
<text class="value-text">
${formatNumber(this.value, this.language)} ${this.label}
${formatNumber(this.value, this.locale)} ${this.label}
</text>
</svg>`;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ha-sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ class HaSidebar extends LitElement {
hass.panelUrl !== oldHass.panelUrl ||
hass.user !== oldHass.user ||
hass.localize !== oldHass.localize ||
hass.language !== oldHass.language ||
hass.locale !== oldHass.locale ||
hass.states !== oldHass.states ||
hass.defaultPanel !== oldHass.defaultPanel
);
Expand Down Expand Up @@ -281,7 +281,7 @@ class HaSidebar extends LitElement {
}

const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
if (!oldHass || oldHass.locale !== this.hass.locale) {
this.rtl = computeRTL(this.hass);
}

Expand Down
Loading