From 92d022747b71808a68c602f5f6a5e6029f3e2240 Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Tue, 29 Nov 2022 20:54:18 +0100
Subject: [PATCH] Add core country and language settings (#14478)
---
package.json | 2 +-
src/components/country-datalist.ts | 273 ++++++++++++++++++
src/components/currency-datalist.ts | 12 +-
src/components/ha-textfield.ts | 4 +-
src/components/language-datalist.ts | 15 +
src/components/map/ha-map.ts | 4 +-
src/components/timezone-datalist.ts | 2 +-
src/data/core.ts | 2 +
src/fake_data/demo_config.ts | 2 +
src/onboarding/ha-onboarding.ts | 38 +--
src/onboarding/onboarding-core-config.ts | 123 ++++++--
.../config/core/ha-config-section-general.ts | 63 +++-
src/translations/en.json | 2 +
yarn.lock | 10 +-
14 files changed, 495 insertions(+), 57 deletions(-)
create mode 100644 src/components/country-datalist.ts
create mode 100644 src/components/language-datalist.ts
diff --git a/package.json b/package.json
index e6d2d93726..5f3c81026d 100644
--- a/package.json
+++ b/package.json
@@ -112,7 +112,7 @@
"google-timezones-json": "^1.0.2",
"hammerjs": "^2.0.8",
"hls.js": "^1.2.5",
- "home-assistant-js-websocket": "^8.0.0",
+ "home-assistant-js-websocket": "^8.0.1",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",
diff --git a/src/components/country-datalist.ts b/src/components/country-datalist.ts
new file mode 100644
index 0000000000..c972a77fd6
--- /dev/null
+++ b/src/components/country-datalist.ts
@@ -0,0 +1,273 @@
+export const countries = [
+ "AD",
+ "AE",
+ "AF",
+ "AG",
+ "AI",
+ "AL",
+ "AM",
+ "AO",
+ "AQ",
+ "AR",
+ "AS",
+ "AT",
+ "AU",
+ "AW",
+ "AX",
+ "AZ",
+ "BA",
+ "BB",
+ "BD",
+ "BE",
+ "BF",
+ "BG",
+ "BH",
+ "BI",
+ "BJ",
+ "BL",
+ "BM",
+ "BN",
+ "BO",
+ "BQ",
+ "BR",
+ "BS",
+ "BT",
+ "BV",
+ "BW",
+ "BY",
+ "BZ",
+ "CA",
+ "CC",
+ "CD",
+ "CF",
+ "CG",
+ "CH",
+ "CI",
+ "CK",
+ "CL",
+ "CM",
+ "CN",
+ "CO",
+ "CR",
+ "CU",
+ "CV",
+ "CW",
+ "CX",
+ "CY",
+ "CZ",
+ "DE",
+ "DJ",
+ "DK",
+ "DM",
+ "DO",
+ "DZ",
+ "EC",
+ "EE",
+ "EG",
+ "EH",
+ "ER",
+ "ES",
+ "ET",
+ "FI",
+ "FJ",
+ "FK",
+ "FM",
+ "FO",
+ "FR",
+ "GA",
+ "GB",
+ "GD",
+ "GE",
+ "GF",
+ "GG",
+ "GH",
+ "GI",
+ "GL",
+ "GM",
+ "GN",
+ "GP",
+ "GQ",
+ "GR",
+ "GS",
+ "GT",
+ "GU",
+ "GW",
+ "GY",
+ "HK",
+ "HM",
+ "HN",
+ "HR",
+ "HT",
+ "HU",
+ "ID",
+ "IE",
+ "IL",
+ "IM",
+ "IN",
+ "IO",
+ "IQ",
+ "IR",
+ "IS",
+ "IT",
+ "JE",
+ "JM",
+ "JO",
+ "JP",
+ "KE",
+ "KG",
+ "KH",
+ "KI",
+ "KM",
+ "KN",
+ "KP",
+ "KR",
+ "KW",
+ "KY",
+ "KZ",
+ "LA",
+ "LB",
+ "LC",
+ "LI",
+ "LK",
+ "LR",
+ "LS",
+ "LT",
+ "LU",
+ "LV",
+ "LY",
+ "MA",
+ "MC",
+ "MD",
+ "ME",
+ "MF",
+ "MG",
+ "MH",
+ "MK",
+ "ML",
+ "MM",
+ "MN",
+ "MO",
+ "MP",
+ "MQ",
+ "MR",
+ "MS",
+ "MT",
+ "MU",
+ "MV",
+ "MW",
+ "MX",
+ "MY",
+ "MZ",
+ "NA",
+ "NC",
+ "NE",
+ "NF",
+ "NG",
+ "NI",
+ "NL",
+ "NO",
+ "NP",
+ "NR",
+ "NU",
+ "NZ",
+ "OM",
+ "PA",
+ "PE",
+ "PF",
+ "PG",
+ "PH",
+ "PK",
+ "PL",
+ "PM",
+ "PN",
+ "PR",
+ "PS",
+ "PT",
+ "PW",
+ "PY",
+ "QA",
+ "RE",
+ "RO",
+ "RS",
+ "RU",
+ "RW",
+ "SA",
+ "SB",
+ "SC",
+ "SD",
+ "SE",
+ "SG",
+ "SH",
+ "SI",
+ "SJ",
+ "SK",
+ "SL",
+ "SM",
+ "SN",
+ "SO",
+ "SR",
+ "SS",
+ "ST",
+ "SV",
+ "SX",
+ "SY",
+ "SZ",
+ "TC",
+ "TD",
+ "TF",
+ "TG",
+ "TH",
+ "TJ",
+ "TK",
+ "TL",
+ "TM",
+ "TN",
+ "TO",
+ "TR",
+ "TT",
+ "TV",
+ "TW",
+ "TZ",
+ "UA",
+ "UG",
+ "UM",
+ "US",
+ "UY",
+ "UZ",
+ "VA",
+ "VC",
+ "VE",
+ "VG",
+ "VI",
+ "VN",
+ "VU",
+ "WF",
+ "WS",
+ "YE",
+ "YT",
+ "ZA",
+ "ZM",
+ "ZW",
+];
+
+export const countryDisplayNames =
+ Intl && "DisplayNames" in Intl
+ ? new Intl.DisplayNames(undefined, {
+ type: "region",
+ fallback: "code",
+ })
+ : undefined;
+
+export const createCountryListEl = () => {
+ const list = document.createElement("datalist");
+ list.id = "countries";
+ for (const country of countries) {
+ const option = document.createElement("option");
+ option.value = country;
+ option.innerText = countryDisplayNames
+ ? countryDisplayNames.of(country)!
+ : country;
+ list.appendChild(option);
+ }
+ return list;
+};
diff --git a/src/components/currency-datalist.ts b/src/components/currency-datalist.ts
index 69a69e7222..c725b8258c 100644
--- a/src/components/currency-datalist.ts
+++ b/src/components/currency-datalist.ts
@@ -158,13 +158,23 @@ export const currencies = [
"ZWL",
];
+export const currencyDisplayNames =
+ Intl && "DisplayNames" in Intl
+ ? new Intl.DisplayNames(undefined, {
+ type: "currency",
+ fallback: "code",
+ })
+ : undefined;
+
export const createCurrencyListEl = () => {
const list = document.createElement("datalist");
list.id = "currencies";
for (const currency of currencies) {
const option = document.createElement("option");
option.value = currency;
- option.innerHTML = currency;
+ option.innerText = currencyDisplayNames
+ ? currencyDisplayNames.of(currency)!
+ : currency;
list.appendChild(option);
}
return list;
diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts
index dbfbc1cc38..6264784e81 100644
--- a/src/components/ha-textfield.ts
+++ b/src/components/ha-textfield.ts
@@ -1,7 +1,7 @@
import { TextFieldBase } from "@material/mwc-textfield/mwc-textfield-base";
import { styles } from "@material/mwc-textfield/mwc-textfield.css";
import { TemplateResult, html, PropertyValues, css } from "lit";
-import { customElement, property } from "lit/decorators";
+import { customElement, property, query } from "lit/decorators";
@customElement("ha-textfield")
export class HaTextField extends TextFieldBase {
@@ -17,6 +17,8 @@ export class HaTextField extends TextFieldBase {
@property() public autocomplete?: string;
+ @query("input") public formElement!: HTMLInputElement;
+
override updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (
diff --git a/src/components/language-datalist.ts b/src/components/language-datalist.ts
new file mode 100644
index 0000000000..bac8b44c9c
--- /dev/null
+++ b/src/components/language-datalist.ts
@@ -0,0 +1,15 @@
+import { HomeAssistant } from "../types";
+
+export const createLanguageListEl = (hass: HomeAssistant) => {
+ const list = document.createElement("datalist");
+ list.id = "languages";
+ for (const [language, metadata] of Object.entries(
+ hass.translationMetadata.translations
+ )) {
+ const option = document.createElement("option");
+ option.value = language;
+ option.innerText = metadata.nativeName;
+ list.appendChild(option);
+ }
+ return list;
+};
diff --git a/src/components/map/ha-map.ts b/src/components/map/ha-map.ts
index ce395d170b..d9294bc58c 100644
--- a/src/components/map/ha-map.ts
+++ b/src/components/map/ha-map.ts
@@ -133,11 +133,11 @@ export class HaMap extends ReactiveElement {
if (
!changedProps.has("darkMode") &&
(!changedProps.has("hass") ||
- (oldHass && oldHass.themes.darkMode === this.hass.themes.darkMode))
+ (oldHass && oldHass.themes?.darkMode === this.hass.themes?.darkMode))
) {
return;
}
- const darkMode = this.darkMode ?? this.hass.themes.darkMode;
+ const darkMode = this.darkMode ?? this.hass.themes?.darkMode;
this.shadowRoot!.getElementById("map")!.classList.toggle("dark", darkMode);
}
diff --git a/src/components/timezone-datalist.ts b/src/components/timezone-datalist.ts
index 05bfa1c0ab..1d4a705408 100644
--- a/src/components/timezone-datalist.ts
+++ b/src/components/timezone-datalist.ts
@@ -6,7 +6,7 @@ export const createTimezoneListEl = () => {
Object.keys(timezones).forEach((key) => {
const option = document.createElement("option");
option.value = key;
- option.innerHTML = timezones[key];
+ option.innerText = timezones[key];
list.appendChild(option);
});
return list;
diff --git a/src/data/core.ts b/src/data/core.ts
index 8d5c231e2b..a2b944e3bd 100644
--- a/src/data/core.ts
+++ b/src/data/core.ts
@@ -11,6 +11,8 @@ export interface ConfigUpdateValues {
external_url?: string | null;
internal_url?: string | null;
currency?: string | null;
+ country?: string | null;
+ language?: string | null;
}
export interface CheckConfigResult {
diff --git a/src/fake_data/demo_config.ts b/src/fake_data/demo_config.ts
index ae5a528ff3..e9efe63c65 100644
--- a/src/fake_data/demo_config.ts
+++ b/src/fake_data/demo_config.ts
@@ -32,4 +32,6 @@ export const demoConfig: HassConfig = {
internal_url: "http://homeassistant.local:8123",
external_url: null,
currency: "USD",
+ language: "en",
+ country: "NL",
};
diff --git a/src/onboarding/ha-onboarding.ts b/src/onboarding/ha-onboarding.ts
index 28de65a955..1c4e6ffbe5 100644
--- a/src/onboarding/ha-onboarding.ts
+++ b/src/onboarding/ha-onboarding.ts
@@ -131,21 +131,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
if (window.innerWidth > 450) {
import("./particles");
}
- if (matchMedia("(prefers-color-scheme: dark)").matches) {
- applyThemesOnElement(
- document.documentElement,
- {
- default_theme: "default",
- default_dark_theme: null,
- themes: {},
- darkMode: true,
- theme: "default",
- },
- undefined,
- undefined,
- true
- );
- }
}
protected updated(changedProps: PropertyValues) {
@@ -154,10 +139,25 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
document.querySelector("html")!.setAttribute("lang", this.language!);
}
if (changedProps.has("hass")) {
- this.hassChanged(
- this.hass!,
- changedProps.get("hass") as HomeAssistant | undefined
- );
+ const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
+ this.hassChanged(this.hass!, oldHass);
+ if (oldHass?.themes !== this.hass!.themes) {
+ if (matchMedia("(prefers-color-scheme: dark)").matches) {
+ applyThemesOnElement(
+ document.documentElement,
+ {
+ default_theme: "default",
+ default_dark_theme: null,
+ themes: {},
+ darkMode: true,
+ theme: "default",
+ },
+ undefined,
+ undefined,
+ true
+ );
+ }
+ }
}
}
diff --git a/src/onboarding/onboarding-core-config.ts b/src/onboarding/onboarding-core-config.ts
index 05a996f6fe..50cb216d87 100644
--- a/src/onboarding/onboarding-core-config.ts
+++ b/src/onboarding/onboarding-core-config.ts
@@ -1,6 +1,4 @@
import "@material/mwc-button/mwc-button";
-import "@polymer/paper-input/paper-input";
-import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -25,6 +23,11 @@ import type { HomeAssistant } from "../types";
import "../components/ha-radio";
import "../components/ha-formfield";
import type { HaRadio } from "../components/ha-radio";
+import type { HaTextField } from "../components/ha-textfield";
+import "../components/ha-textfield";
+import { getLocalLanguage } from "../util/common-translation";
+import { createCountryListEl } from "../components/country-datalist";
+import { createLanguageListEl } from "../components/language-datalist";
const amsterdam: [number, number] = [52.3731339, 4.8903147];
const mql = matchMedia("(prefers-color-scheme: dark)");
@@ -50,6 +53,10 @@ class OnboardingCoreConfig extends LitElement {
@state() private _timeZone?: string;
+ @state() private _language?: ConfigUpdateValues["language"];
+
+ @state() private _country?: ConfigUpdateValues["country"];
+
@query("ha-locations-editor", true) private map!: HaLocationsEditor;
protected render(): TemplateResult {
@@ -62,15 +69,15 @@ class OnboardingCoreConfig extends LitElement {
)}
-
+ @change=${this._handleChange}
+ >
@@ -105,19 +112,42 @@ class OnboardingCoreConfig extends LitElement {
+
+
+
+ @change=${this._handleChange}
+ >
-
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)}
-
+
@@ -197,17 +227,16 @@ class OnboardingCoreConfig extends LitElement {
>
-
+ @change=${this._handleChange}
+ >
@@ -224,7 +253,7 @@ class OnboardingCoreConfig extends LitElement {
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
setTimeout(
- () => this.shadowRoot!.querySelector("paper-input")!.focus(),
+ () => this.shadowRoot!.querySelector("ha-textfield")!.focus(),
100
);
this.addEventListener("keypress", (ev) => {
@@ -234,13 +263,35 @@ class OnboardingCoreConfig extends LitElement {
});
const tzInput = this.shadowRoot!.querySelector(
"[name=timeZone]"
- ) as PaperInputElement;
- tzInput.inputElement.appendChild(createTimezoneListEl());
+ ) as HaTextField;
+ tzInput.updateComplete.then(() => {
+ tzInput.shadowRoot!.appendChild(createTimezoneListEl());
+ tzInput.formElement.setAttribute("list", "timezones");
+ });
- const cInput = this.shadowRoot!.querySelector(
+ const curInput = this.shadowRoot!.querySelector(
"[name=currency]"
- ) as PaperInputElement;
- cInput.inputElement.appendChild(createCurrencyListEl());
+ ) as HaTextField;
+ curInput.updateComplete.then(() => {
+ curInput.shadowRoot!.appendChild(createCurrencyListEl());
+ curInput.formElement.setAttribute("list", "currencies");
+ });
+
+ const countryInput = this.shadowRoot!.querySelector(
+ "[name=country]"
+ ) as HaTextField;
+ countryInput.updateComplete.then(() => {
+ countryInput.shadowRoot!.appendChild(createCountryListEl());
+ countryInput.formElement.setAttribute("list", "countries");
+ });
+
+ const langInput = this.shadowRoot!.querySelector(
+ "[name=language]"
+ ) as HaTextField;
+ langInput.updateComplete.then(() => {
+ langInput.shadowRoot!.appendChild(createLanguageListEl(this.hass));
+ langInput.formElement.setAttribute("list", "languages");
+ });
}
private get _nameValue() {
@@ -260,7 +311,15 @@ class OnboardingCoreConfig extends LitElement {
}
private get _timeZoneValue() {
- return this._timeZone;
+ return this._timeZone || "";
+ }
+
+ private get _languageValue() {
+ return this._language || "";
+ }
+
+ private get _countryValue() {
+ return this._country || "";
}
private get _unitSystemValue() {
@@ -283,7 +342,7 @@ class OnboardingCoreConfig extends LitElement {
);
private _handleChange(ev: PolymerChangedEvent) {
- const target = ev.currentTarget as PaperInputElement;
+ const target = ev.currentTarget as HaTextField;
let value = target.value;
@@ -335,6 +394,10 @@ class OnboardingCoreConfig extends LitElement {
if (values.currency) {
this._currency = values.currency;
}
+ if (values.country) {
+ this._country = values.country;
+ }
+ this._language = getLocalLanguage();
} catch (err: any) {
alert(`Failed to detect location information: ${err.message}`);
} finally {
@@ -355,6 +418,8 @@ class OnboardingCoreConfig extends LitElement {
unit_system: this._unitSystemValue,
time_zone: this._timeZoneValue || "UTC",
currency: this._currencyValue || "EUR",
+ country: this._countryValue,
+ language: this._languageValue,
});
const result = await onboardCoreConfigStep(this.hass);
fireEvent(this, "onboarding-step", {
@@ -380,6 +445,10 @@ class OnboardingCoreConfig extends LitElement {
color: var(--secondary-text-color);
}
+ ha-textfield {
+ display: block;
+ }
+
ha-locations-editor {
height: 200px;
}
@@ -389,7 +458,11 @@ class OnboardingCoreConfig extends LitElement {
}
.middle-text {
- margin: 24px 0;
+ margin: 16px 0;
+ }
+
+ .row {
+ margin-top: 16px;
}
.row > * {
diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts
index 67e374fc01..3e93ce8ec3 100644
--- a/src/panels/config/core/ha-config-section-general.ts
+++ b/src/panels/config/core/ha-config-section-general.ts
@@ -8,7 +8,14 @@ import { stopPropagation } from "../../../common/dom/stop_propagation";
import { navigate } from "../../../common/navigate";
import "../../../components/buttons/ha-progress-button";
import type { HaProgressButton } from "../../../components/buttons/ha-progress-button";
-import { currencies } from "../../../components/currency-datalist";
+import {
+ countries,
+ countryDisplayNames,
+} from "../../../components/country-datalist";
+import {
+ currencies,
+ currencyDisplayNames,
+} from "../../../components/currency-datalist";
import "../../../components/ha-card";
import "../../../components/ha-formfield";
import "../../../components/ha-radio";
@@ -36,6 +43,10 @@ class HaConfigSectionGeneral extends LitElement {
@state() private _currency?: string;
+ @state() private _language?: string;
+
+ @state() private _country?: string | null;
+
@state() private _name?: string;
@state() private _elevation?: number;
@@ -179,7 +190,9 @@ class HaConfigSectionGeneral extends LitElement {
${currencies.map(
(currency) =>
html`${currency}${currencyDisplayNames
+ ? currencyDisplayNames.of(currency)
+ : currency}`
)}
@@ -193,6 +206,48 @@ class HaConfigSectionGeneral extends LitElement {
)}
+
+ ${countries.map(
+ (country) =>
+ html`${countryDisplayNames
+ ? countryDisplayNames.of(country)
+ : country}`
+ )}
+
+ ${Object.entries(
+ this.hass.translationMetadata.translations
+ ).map(
+ ([code, metadata]) =>
+ html`${metadata.nativeName}`
+ )}
${this.narrow
? html`
@@ -240,6 +295,8 @@ class HaConfigSectionGeneral extends LitElement {
? "metric"
: "us_customary";
this._currency = this.hass.config.currency;
+ this._country = this.hass.config.country;
+ this._language = this.hass.config.language;
this._elevation = this.hass.config.elevation;
this._timeZone = this.hass.config.time_zone;
this._name = this.hass.config.location_name;
@@ -291,6 +348,8 @@ class HaConfigSectionGeneral extends LitElement {
unit_system: this._unitSystem,
time_zone: this._timeZone,
location_name: this._name,
+ language: this._language,
+ country: this._country,
...locationConfig,
});
button.actionSuccess();
diff --git a/src/translations/en.json b/src/translations/en.json
index ebd580a9de..aa55aa6357 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -1654,6 +1654,8 @@
"elevation": "Elevation",
"elevation_meters": "meters",
"time_zone": "Time Zone",
+ "language": "Language",
+ "country": "Country",
"unit_system": "Unit System",
"unit_system_us_customary": "US customary",
"unit_system_metric": "Metric",
diff --git a/yarn.lock b/yarn.lock
index 8dca31cd85..955af85d37 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9419,7 +9419,7 @@ fsevents@^1.2.7:
gulp-zopfli-green: ^3.0.1
hammerjs: ^2.0.8
hls.js: ^1.2.5
- home-assistant-js-websocket: ^8.0.0
+ home-assistant-js-websocket: ^8.0.1
html-minifier: ^4.0.0
husky: ^8.0.1
idb-keyval: ^5.1.3
@@ -9492,10 +9492,10 @@ fsevents@^1.2.7:
languageName: unknown
linkType: soft
-"home-assistant-js-websocket@npm:^8.0.0":
- version: 8.0.0
- resolution: "home-assistant-js-websocket@npm:8.0.0"
- checksum: 55fecd70e10ab3c74b4c6c78dbf11d56e22e9799e050096a24c74c3844c48d458f42f43c353b9b050d49acecb323a137df8667428206a07a426f030d339a42e7
+"home-assistant-js-websocket@npm:^8.0.1":
+ version: 8.0.1
+ resolution: "home-assistant-js-websocket@npm:8.0.1"
+ checksum: e8b2204d58b2b1fbdf26ca1ad196fcc02ec5d18e6d867179f27246a9f2d4fe5f91de9dbbe7b82806c19dcb0af0e2b77fb48d393668b5c8c0844c201a16832023
languageName: node
linkType: hard