diff --git a/hassio/src/addon-store/hassio-addon-repository.ts b/hassio/src/addon-store/hassio-addon-repository.ts index a2eb5157e3..0d22c49ad2 100644 --- a/hassio/src/addon-store/hassio-addon-repository.ts +++ b/hassio/src/addon-store/hassio-addon-repository.ts @@ -29,7 +29,9 @@ class HassioAddonRepositoryEl extends LitElement { if (filter) { return filterAndSort(addons, filter); } - return addons.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)); + return addons.sort((a, b) => + caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) + ); }); protected render(): TemplateResult { diff --git a/hassio/src/dashboard/hassio-addons.ts b/hassio/src/dashboard/hassio-addons.ts index cc855ccc69..87aaa545a3 100644 --- a/hassio/src/dashboard/hassio-addons.ts +++ b/hassio/src/dashboard/hassio-addons.ts @@ -35,7 +35,13 @@ class HassioAddons extends LitElement { ` : this.supervisor.addon.addons - .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) + .sort((a, b) => + caseInsensitiveStringCompare( + a.name, + b.name, + this.hass.locale.language + ) + ) .map( (addon) => html` + ( + showAdvanced: boolean, + hardware: HassioHardwareInfo, + filter: string, + language: string + ) => hardware.devices .filter( (device) => @@ -28,7 +33,7 @@ const _filterDevices = memoizeOne( .toLocaleLowerCase() .includes(filter)) ) - .sort((a, b) => stringCompare(a.name, b.name)) + .sort((a, b) => stringCompare(a.name, b.name, language)) ); @customElement("dialog-hassio-hardware") @@ -56,7 +61,8 @@ class HassioHardwareDialog extends LitElement { const devices = _filterDevices( this.hass.userData?.showAdvanced || false, this._dialogParams.hardware, - (this._filter || "").toLowerCase() + (this._filter || "").toLowerCase(), + this.hass.locale.language ); return html` diff --git a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts index 82325db788..0a11027766 100644 --- a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts +++ b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts @@ -68,7 +68,9 @@ class HassioRepositoriesDialog extends LitElement { repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons repo.slug !== "5c53de3b" // The ESPHome repository ) - .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) + .sort((a, b) => + caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) + ) ); private _filteredUsedRepositories = memoizeOne( diff --git a/src/common/string/compare.ts b/src/common/string/compare.ts index 67ef4c87bd..a14e087ccb 100644 --- a/src/common/string/compare.ts +++ b/src/common/string/compare.ts @@ -1,4 +1,15 @@ -export const stringCompare = (a: string, b: string) => { +import memoizeOne from "memoize-one"; + +const collator = memoizeOne( + (language: string | undefined) => new Intl.Collator(language) +); + +const caseInsensitiveCollator = memoizeOne( + (language: string | undefined) => + new Intl.Collator(language, { sensitivity: "accent" }) +); + +const fallbackStringCompare = (a: string, b: string) => { if (a < b) { return -1; } @@ -9,5 +20,28 @@ export const stringCompare = (a: string, b: string) => { return 0; }; -export const caseInsensitiveStringCompare = (a: string, b: string) => - stringCompare(a.toLowerCase(), b.toLowerCase()); +export const stringCompare = ( + a: string, + b: string, + language: string | undefined = undefined +) => { + // @ts-ignore + if (Intl?.Collator) { + return collator(language).compare(a, b); + } + + return fallbackStringCompare(a, b); +}; + +export const caseInsensitiveStringCompare = ( + a: string, + b: string, + language: string | undefined = undefined +) => { + // @ts-ignore + if (Intl?.Collator) { + return caseInsensitiveCollator(language).compare(a, b); + } + + return fallbackStringCompare(a.toLowerCase(), b.toLowerCase()); +}; diff --git a/src/components/country-datalist.ts b/src/components/country-datalist.ts index b4295f99ad..7692e58c0c 100644 --- a/src/components/country-datalist.ts +++ b/src/components/country-datalist.ts @@ -266,14 +266,16 @@ export const getCountryOptions = memoizeOne((language?: string) => { value: country, label: countryDisplayNames ? countryDisplayNames.of(country)! : country, })); - options.sort((a, b) => caseInsensitiveStringCompare(a.label, b.label)); + options.sort((a, b) => + caseInsensitiveStringCompare(a.label, b.label, language) + ); return options; }); -export const createCountryListEl = () => { +export const createCountryListEl = (language?: string) => { const list = document.createElement("datalist"); list.id = "countries"; - const options = getCountryOptions(); + const options = getCountryOptions(language); for (const country of options) { const option = document.createElement("option"); option.value = country.value; diff --git a/src/components/currency-datalist.ts b/src/components/currency-datalist.ts index 1339c47520..439d7b9211 100644 --- a/src/components/currency-datalist.ts +++ b/src/components/currency-datalist.ts @@ -173,14 +173,16 @@ export const getCurrencyOptions = memoizeOne((language?: string) => { value: currency, label: currencyDisplayNames ? currencyDisplayNames.of(currency)! : currency, })); - options.sort((a, b) => caseInsensitiveStringCompare(a.label, b.label)); + options.sort((a, b) => + caseInsensitiveStringCompare(a.label, b.label, language) + ); return options; }); -export const createCurrencyListEl = () => { +export const createCurrencyListEl = (language: string) => { const list = document.createElement("datalist"); list.id = "currencies"; - for (const currency of getCurrencyOptions()) { + for (const currency of getCurrencyOptions(language)) { const option = document.createElement("option"); option.value = currency.value; option.innerText = currency.label; diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts index 169a49f518..3edee20f5a 100644 --- a/src/components/device/ha-area-devices-picker.ts +++ b/src/components/device/ha-area-devices-picker.ts @@ -189,7 +189,8 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { .sort((a, b) => stringCompare( devicesByArea[a].name || "", - devicesByArea[b].name || "" + devicesByArea[b].name || "", + this.hass.locale.language ) ) .map((key) => devicesByArea[key]); diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index c51032eee9..95a3332fe0 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -231,7 +231,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { return outputDevices; } return outputDevices.sort((a, b) => - stringCompare(a.name || "", b.name || "") + stringCompare(a.name || "", b.name || "", this.hass.locale.language) ); } ); diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 056918602a..e2f2339c1c 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -174,7 +174,8 @@ export class HaEntityPicker extends LitElement { .sort((entityA, entityB) => caseInsensitiveStringCompare( entityA.friendly_name, - entityB.friendly_name + entityB.friendly_name, + this.hass.locale.language ) ); } @@ -205,7 +206,8 @@ export class HaEntityPicker extends LitElement { .sort((entityA, entityB) => caseInsensitiveStringCompare( entityA.friendly_name, - entityB.friendly_name + entityB.friendly_name, + this.hass.locale.language ) ); diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index a19ee9e011..59c942d95e 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -177,7 +177,9 @@ export class HaStatisticPicker extends LitElement { } if (output.length > 1) { - output.sort((a, b) => stringCompare(a.name || "", b.name || "")); + output.sort((a, b) => + stringCompare(a.name || "", b.name || "", this.hass.locale.language) + ); } output.push({ diff --git a/src/components/ha-addon-picker.ts b/src/components/ha-addon-picker.ts index cd8b7c4bf5..955bb6fefb 100644 --- a/src/components/ha-addon-picker.ts +++ b/src/components/ha-addon-picker.ts @@ -80,7 +80,9 @@ class HaAddonPicker extends LitElement { const addonsInfo = await fetchHassioAddonsInfo(this.hass); this._addons = addonsInfo.addons .filter((addon) => addon.version) - .sort((a, b) => stringCompare(a.name, b.name)); + .sort((a, b) => + stringCompare(a.name, b.name, this.hass.locale.language) + ); } else { showAlertDialog(this, { title: this.hass.localize( diff --git a/src/components/ha-blueprint-picker.ts b/src/components/ha-blueprint-picker.ts index 58a0f03f4a..0dfc64c3a9 100644 --- a/src/components/ha-blueprint-picker.ts +++ b/src/components/ha-blueprint-picker.ts @@ -46,7 +46,9 @@ class HaBluePrintPicker extends LitElement { ...(blueprint as Blueprint).metadata, path, })); - return result.sort((a, b) => stringCompare(a.name, b.name)); + return result.sort((a, b) => + stringCompare(a.name, b.name, this.hass!.locale.language) + ); }); protected render(): TemplateResult { diff --git a/src/components/ha-config-entry-picker.ts b/src/components/ha-config-entry-picker.ts index 733598b003..7bd4301bfe 100644 --- a/src/components/ha-config-entry-picker.ts +++ b/src/components/ha-config-entry-picker.ts @@ -121,7 +121,8 @@ class HaConfigEntryPicker extends LitElement { .sort((conf1, conf2) => caseInsensitiveStringCompare( conf1.localized_domain_name + conf1.title, - conf2.localized_domain_name + conf2.title + conf2.localized_domain_name + conf2.title, + this.hass.locale.language ) ); }); diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 30c4959335..1173b0c7a7 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -87,7 +87,8 @@ const panelSorter = ( reverseSort: string[], defaultPanel: string, a: PanelInfo, - b: PanelInfo + b: PanelInfo, + language: string ) => { const indexA = reverseSort.indexOf(a.url_path); const indexB = reverseSort.indexOf(b.url_path); @@ -97,13 +98,14 @@ const panelSorter = ( } return -1; } - return defaultPanelSorter(defaultPanel, a, b); + return defaultPanelSorter(defaultPanel, a, b, language); }; const defaultPanelSorter = ( defaultPanel: string, a: PanelInfo, - b: PanelInfo + b: PanelInfo, + language: string ) => { // Put all the Lovelace at the top. const aLovelace = a.component_name === "lovelace"; @@ -117,7 +119,7 @@ const defaultPanelSorter = ( } if (aLovelace && bLovelace) { - return stringCompare(a.title!, b.title!); + return stringCompare(a.title!, b.title!, language); } if (aLovelace && !bLovelace) { return -1; @@ -139,7 +141,7 @@ const defaultPanelSorter = ( return 1; } // both not built in, sort by title - return stringCompare(a.title!, b.title!); + return stringCompare(a.title!, b.title!, language); }; const computePanels = memoizeOne( @@ -147,7 +149,8 @@ const computePanels = memoizeOne( panels: HomeAssistant["panels"], defaultPanel: HomeAssistant["defaultPanel"], panelsOrder: string[], - hiddenPanels: string[] + hiddenPanels: string[], + locale: HomeAssistant["locale"] ): [PanelInfo[], PanelInfo[]] => { if (!panels) { return [[], []]; @@ -171,8 +174,12 @@ const computePanels = memoizeOne( const reverseSort = [...panelsOrder].reverse(); - beforeSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b)); - afterSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b)); + beforeSpacer.sort((a, b) => + panelSorter(reverseSort, defaultPanel, a, b, locale.language) + ); + afterSpacer.sort((a, b) => + panelSorter(reverseSort, defaultPanel, a, b, locale.language) + ); return [beforeSpacer, afterSpacer]; } @@ -374,7 +381,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { this.hass.panels, this.hass.defaultPanel, this._panelOrder, - this._hiddenPanels + this._hiddenPanels, + this.hass.locale ); // Show the supervisor as beeing part of configuration diff --git a/src/components/user/ha-user-picker.ts b/src/components/user/ha-user-picker.ts index 6bbc3c1af3..50ec6b4611 100644 --- a/src/components/user/ha-user-picker.ts +++ b/src/components/user/ha-user-picker.ts @@ -30,7 +30,9 @@ class HaUserPicker extends LitElement { return users .filter((user) => !user.system_generated) - .sort((a, b) => stringCompare(a.name, b.name)); + .sort((a, b) => + stringCompare(a.name, b.name, this.hass!.locale.language) + ); }); protected render(): TemplateResult { diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index a026221819..bc1437adfa 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -123,9 +123,12 @@ export const subscribeDeviceRegistry = ( onChange ); -export const sortDeviceRegistryByName = (entries: DeviceRegistryEntry[]) => +export const sortDeviceRegistryByName = ( + entries: DeviceRegistryEntry[], + language: string +) => entries.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name || "", entry2.name || "") + caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language) ); export const getDeviceEntityLookup = ( diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 44e1b3679c..c402f2afd7 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -164,9 +164,12 @@ export const subscribeEntityRegistry = ( onChange ); -export const sortEntityRegistryByName = (entries: EntityRegistryEntry[]) => +export const sortEntityRegistryByName = ( + entries: EntityRegistryEntry[], + language: string +) => entries.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name || "", entry2.name || "") + caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language) ); export const entityRegistryById = memoizeOne( diff --git a/src/data/update.ts b/src/data/update.ts index 802c1d0554..cc352c5116 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -68,7 +68,10 @@ export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) => entity_id: entityId, }); -export const filterUpdateEntities = (entities: HassEntities) => +export const filterUpdateEntities = ( + entities: HassEntities, + language?: string +) => ( Object.values(entities).filter( (entity) => computeStateDomain(entity) === "update" @@ -94,7 +97,8 @@ export const filterUpdateEntities = (entities: HassEntities) => } return caseInsensitiveStringCompare( a.attributes.title || a.attributes.friendly_name || "", - b.attributes.title || b.attributes.friendly_name || "" + b.attributes.title || b.attributes.friendly_name || "", + language ); }); @@ -110,7 +114,7 @@ export const checkForEntityUpdates = async ( element: HTMLElement, hass: HomeAssistant ) => { - const entities = filterUpdateEntities(hass.states).map( + const entities = filterUpdateEntities(hass.states, hass.locale.language).map( (entity) => entity.entity_id ); diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 8b62007c9a..730ef8ad68 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -484,7 +484,11 @@ export class QuickBar extends LitElement { }; }) .sort((a, b) => - caseInsensitiveStringCompare(a.primaryText, b.primaryText) + caseInsensitiveStringCompare( + a.primaryText, + b.primaryText, + this.hass.locale.language + ) ); } @@ -494,7 +498,11 @@ export class QuickBar extends LitElement { ...this._generateServerControlCommands(), ...(await this._generateNavigationCommands()), ].sort((a, b) => - caseInsensitiveStringCompare(a.strings.join(" "), b.strings.join(" ")) + caseInsensitiveStringCompare( + a.strings.join(" "), + b.strings.join(" "), + this.hass.locale.language + ) ); } diff --git a/src/onboarding/onboarding-core-config.ts b/src/onboarding/onboarding-core-config.ts index d1d732d51c..3aa5b91549 100644 --- a/src/onboarding/onboarding-core-config.ts +++ b/src/onboarding/onboarding-core-config.ts @@ -271,7 +271,9 @@ class OnboardingCoreConfig extends LitElement { "[name=currency]" ) as HaTextField; curInput.updateComplete.then(() => { - curInput.shadowRoot!.appendChild(createCurrencyListEl()); + curInput.shadowRoot!.appendChild( + createCurrencyListEl(this.hass.locale.language) + ); curInput.formElement.setAttribute("list", "currencies"); }); @@ -279,7 +281,9 @@ class OnboardingCoreConfig extends LitElement { "[name=country]" ) as HaTextField; countryInput.updateComplete.then(() => { - countryInput.shadowRoot!.appendChild(createCountryListEl()); + countryInput.shadowRoot!.appendChild( + createCountryListEl(this.hass.locale.language) + ); countryInput.formElement.setAttribute("list", "countries"); }); diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index 263ce23108..e35cdf15e2 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -117,7 +117,7 @@ class OnboardingIntegrations extends LitElement { } ); const content = [...entries, ...discovered] - .sort((a, b) => stringCompare(a[0], b[0])) + .sort((a, b) => stringCompare(a[0], b[0], this.hass.locale.language)) .map((item) => item[1]); return html` diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index e4e3911d2d..2b5627e180 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -192,13 +192,13 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { devices.forEach((entry) => { entry.name = computeDeviceName(entry, this.hass); }); - sortDeviceRegistryByName(devices); + sortDeviceRegistryByName(devices, this.hass.locale.language); } if (entities) { entities.forEach((entry) => { entry.name = computeEntityRegistryName(this.hass, entry); }); - sortEntityRegistryByName(entities); + sortEntityRegistryByName(entities, this.hass.locale.language); } // Group entities by domain @@ -507,7 +507,11 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } }); groupedEntities.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name!, entry2.name!) + caseInsensitiveStringCompare( + entry1.name!, + entry2.name!, + this.hass.locale.language + ) ); } if (relatedEntityIds?.length) { @@ -521,7 +525,11 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) { } }); relatedEntities.sort((entry1, entry2) => - caseInsensitiveStringCompare(entry1.name!, entry2.name!) + caseInsensitiveStringCompare( + entry1.name!, + entry2.name!, + this.hass.locale.language + ) ); } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 6c4827a633..a35fef3b91 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -299,7 +299,7 @@ export default class HaAutomationAction extends LitElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/action/types/ha-automation-action-condition.ts b/src/panels/config/automation/action/types/ha-automation-action-condition.ts index a21b4bb9d5..6123678139 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-condition.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-condition.ts @@ -66,7 +66,7 @@ export class HaConditionAction extends LitElement implements ActionElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); private _conditionChanged(ev: CustomEvent) { diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 2b0bd7e1c2..4d318d09cd 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -328,7 +328,7 @@ export default class HaAutomationCondition extends LitElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 74534e457a..c53738ef86 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -302,7 +302,7 @@ export default class HaAutomationTrigger extends LitElement { icon, ] as [string, string, string] ) - .sort((a, b) => stringCompare(a[1], b[1])) + .sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language)) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts index 4d09932c75..e9c13c24fb 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts @@ -54,7 +54,11 @@ export class HaTagTrigger extends LitElement implements TriggerElement { private async _fetchTags() { this._tags = (await fetchTags(this.hass)).sort((a, b) => - caseInsensitiveStringCompare(a.name || a.id, b.name || b.id) + caseInsensitiveStringCompare( + a.name || a.id, + b.name || b.id, + this.hass.locale.language + ) ); } diff --git a/src/panels/config/cloud/alexa/cloud-alexa.ts b/src/panels/config/cloud/alexa/cloud-alexa.ts index 0ac078e360..ad9edd2bdf 100644 --- a/src/panels/config/cloud/alexa/cloud-alexa.ts +++ b/src/panels/config/cloud/alexa/cloud-alexa.ts @@ -349,7 +349,8 @@ class CloudAlexa extends SubscribeMixin(LitElement) { const stateB = this.hass.states[b.entity_id]; return stringCompare( stateA ? computeStateName(stateA) : a.entity_id, - stateB ? computeStateName(stateB) : b.entity_id + stateB ? computeStateName(stateB) : b.entity_id, + this.hass.locale.language ); }); this._entities = entities; diff --git a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts index da22c0fc0b..75d14227cc 100644 --- a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts +++ b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts @@ -402,7 +402,8 @@ class CloudGoogleAssistant extends SubscribeMixin(LitElement) { const stateB = this.hass.states[b.entity_id]; return stringCompare( stateA ? computeStateName(stateA) : a.entity_id, - stateB ? computeStateName(stateB) : b.entity_id + stateB ? computeStateName(stateB) : b.entity_id, + this.hass.locale.language ); }); this._entities = entities; diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts index 462f23fd9a..900b5ccdac 100644 --- a/src/panels/config/core/ha-config-section-general.ts +++ b/src/panels/config/core/ha-config-section-general.ts @@ -304,7 +304,11 @@ class HaConfigSectionGeneral extends LitElement { } this._languages = Object.entries(this.hass.translationMetadata.translations) .sort((a, b) => - caseInsensitiveStringCompare(a[1].nativeName, b[1].nativeName) + caseInsensitiveStringCompare( + a[1].nativeName, + b[1].nativeName, + this.hass.locale.language + ) ) .map(([value, metaData]) => ({ value, diff --git a/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts b/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts index 3a0c276d6f..b437c42938 100644 --- a/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-via-devices-card.ts @@ -31,7 +31,8 @@ export class HaDeviceViaDevicesCard extends LitElement { .sort((d1, d2) => caseInsensitiveStringCompare( computeDeviceName(d1, this.hass), - computeDeviceName(d2, this.hass) + computeDeviceName(d2, this.hass), + this.hass.locale.language ) ) ); diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 143f2a8f51..db7d20c342 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -157,7 +157,8 @@ export class HaConfigDevicePage extends LitElement { .sort((ent1, ent2) => stringCompare( ent1.stateName || `zzz${ent1.entity_id}`, - ent2.stateName || `zzz${ent2.entity_id}` + ent2.stateName || `zzz${ent2.entity_id}`, + this.hass.locale.language ) ) ); diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 615485d399..2bd09c640b 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -1191,7 +1191,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { domain: entry, label: domainToName(localize, entry), })) - .sort((a, b) => stringCompare(a.label, b.label)) + .sort((a, b) => + stringCompare(a.label, b.label, this.hass.locale.language) + ) ); private _deviceClassesSorted = memoizeOne( @@ -1203,7 +1205,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { `ui.dialogs.entity_registry.editor.device_classes.${domain}.${entry}` ), })) - .sort((a, b) => stringCompare(a.label, b.label)) + .sort((a, b) => + stringCompare(a.label, b.label, this.hass.locale.language) + ) ); static get styles(): CSSResultGroup { diff --git a/src/panels/config/hardware/dialog-hardware-available.ts b/src/panels/config/hardware/dialog-hardware-available.ts index aee0f828e7..d99a15c723 100644 --- a/src/panels/config/hardware/dialog-hardware-available.ts +++ b/src/panels/config/hardware/dialog-hardware-available.ts @@ -20,7 +20,12 @@ import { haStyle, haStyleDialog } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; const _filterDevices = memoizeOne( - (showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) => + ( + showAdvanced: boolean, + hardware: HassioHardwareInfo, + filter: string, + language: string + ) => hardware.devices .filter( (device) => @@ -33,7 +38,7 @@ const _filterDevices = memoizeOne( .toLocaleLowerCase() .includes(filter)) ) - .sort((a, b) => stringCompare(a.name, b.name)) + .sort((a, b) => stringCompare(a.name, b.name, language)) ); @customElement("ha-dialog-hardware-available") @@ -70,7 +75,8 @@ class DialogHardwareAvailable extends LitElement implements HassDialog { const devices = _filterDevices( this.hass.userData?.showAdvanced || false, this._hardware, - (this._filter || "").toLowerCase() + (this._filter || "").toLowerCase(), + this.hass.locale.language ); return html` diff --git a/src/panels/config/integrations/dialog-add-integration.ts b/src/panels/config/integrations/dialog-add-integration.ts index fb8367ee15..15b52126d4 100644 --- a/src/panels/config/integrations/dialog-add-integration.ts +++ b/src/panels/config/integrations/dialog-add-integration.ts @@ -147,7 +147,13 @@ class AddIntegrationDialog extends LitElement { is_built_in: true, is_add: true, })) - .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)); + .sort((a, b) => + caseInsensitiveStringCompare( + a.name, + b.name, + this.hass.locale.language + ) + ); const integrations: IntegrationListItem[] = []; const yamlIntegrations: IntegrationListItem[] = []; @@ -242,7 +248,11 @@ class AddIntegrationDialog extends LitElement { return [ ...addDeviceRows, ...integrations.sort((a, b) => - caseInsensitiveStringCompare(a.name || "", b.name || "") + caseInsensitiveStringCompare( + a.name || "", + b.name || "", + this.hass.locale.language + ) ), ]; } diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 3ba289ae99..aaea690b57 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -230,7 +230,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { (conf1, conf2) => caseInsensitiveStringCompare( conf1.localized_domain_name + conf1.title, - conf2.localized_domain_name + conf2.title + conf2.localized_domain_name + conf2.title, + this.hass.locale.language ) ); }, diff --git a/src/panels/config/integrations/ha-domain-integrations.ts b/src/panels/config/integrations/ha-domain-integrations.ts index 21f75f28d3..32a281247f 100644 --- a/src/panels/config/integrations/ha-domain-integrations.ts +++ b/src/panels/config/integrations/ha-domain-integrations.ts @@ -121,7 +121,8 @@ class HaDomainIntegrations extends LitElement { } return caseInsensitiveStringCompare( a[1].name || domainToName(this.hass.localize, a[0]), - b[1].name || domainToName(this.hass.localize, b[0]) + b[1].name || domainToName(this.hass.localize, b[0]), + this.hass.locale.language ); }) .map( diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts index f357a9bdf9..ab15c017d6 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-card.ts @@ -50,7 +50,8 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) { .sort((ent1, ent2) => stringCompare( ent1.stateName || `zzz${ent1.entity_id}`, - ent2.stateName || `zzz${ent2.entity_id}` + ent2.stateName || `zzz${ent2.entity_id}`, + this.hass.locale.language ) ) ); diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index afa5d71126..e055214469 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -230,7 +230,9 @@ export class HaConfigLovelaceDashboards extends LitElement { result.push( ...dashboards - .sort((a, b) => stringCompare(a.title, b.title)) + .sort((a, b) => + stringCompare(a.title, b.title, this.hass.locale.language) + ) .map((dashboard) => ({ filename: "", ...dashboard, @@ -342,7 +344,12 @@ export class HaConfigLovelaceDashboards extends LitElement { createDashboard: async (values: LovelaceDashboardCreateParams) => { const created = await createDashboard(this.hass!, values); this._dashboards = this._dashboards!.concat(created).sort( - (res1, res2) => stringCompare(res1.url_path, res2.url_path) + (res1, res2) => + stringCompare( + res1.url_path, + res2.url_path, + this.hass.locale.language + ) ); }, updateDashboard: async (values) => { diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index b93afcc17d..411d288fbe 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -143,7 +143,7 @@ export class HaConfigLovelaceRescources extends LitElement { createResource: async (values) => { const created = await createResource(this.hass!, values); this._resources = this._resources!.concat(created).sort((res1, res2) => - stringCompare(res1.url, res2.url) + stringCompare(res1.url, res2.url, this.hass!.locale.language) ); loadLovelaceResources([created], this.hass!.auth.data.hassUrl); }, diff --git a/src/panels/config/person/ha-config-person.ts b/src/panels/config/person/ha-config-person.ts index 87d16caf94..ffd68efeec 100644 --- a/src/panels/config/person/ha-config-person.ts +++ b/src/panels/config/person/ha-config-person.ts @@ -156,10 +156,10 @@ class HaConfigPerson extends LitElement { const personData = await fetchPersons(this.hass!); this._storageItems = personData.storage.sort((ent1, ent2) => - stringCompare(ent1.name, ent2.name) + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); this._configItems = personData.config.sort((ent1, ent2) => - stringCompare(ent1.name, ent2.name) + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); this._openDialogIfPersonSpecifiedInRoute(); } @@ -221,7 +221,8 @@ class HaConfigPerson extends LitElement { createEntry: async (values) => { const created = await createPerson(this.hass!, values); this._storageItems = this._storageItems!.concat(created).sort( - (ent1, ent2) => stringCompare(ent1.name, ent2.name) + (ent1, ent2) => + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); }, updateEntry: async (values) => { diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index 7089e7735a..d46e313ff4 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -296,7 +296,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { private async _fetchData() { this._storageItems = (await fetchZones(this.hass!)).sort((ent1, ent2) => - stringCompare(ent1.name, ent2.name) + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); this._getStates(); } @@ -411,7 +411,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { private async _createEntry(values: ZoneMutableParams) { const created = await createZone(this.hass!, values); this._storageItems = this._storageItems!.concat(created).sort( - (ent1, ent2) => stringCompare(ent1.name, ent2.name) + (ent1, ent2) => + stringCompare(ent1.name, ent2.name, this.hass!.locale.language) ); if (this.narrow) { return; diff --git a/src/panels/developer-tools/event/events-list.js b/src/panels/developer-tools/event/events-list.js index 89b07d6933..a6893d269e 100644 --- a/src/panels/developer-tools/event/events-list.js +++ b/src/panels/developer-tools/event/events-list.js @@ -58,7 +58,9 @@ class EventsList extends EventsMixin(LocalizeMixin(PolymerElement)) { connectedCallback() { super.connectedCallback(); this.hass.callApi("GET", "events").then((events) => { - this.events = events.sort((e1, e2) => stringCompare(e1.event, e2.event)); + this.events = events.sort((e1, e2) => + stringCompare(e1.event, e2.event, this.hass.locale.language) + ); }); } diff --git a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts index 727325d07c..42765e0d2c 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts @@ -42,7 +42,9 @@ export class HuiViewVisibilityEditor extends LitElement { @state() private _visible!: boolean | ShowViewConfig[]; private _sortedUsers = memoizeOne((users: User[]) => - users.sort((a, b) => stringCompare(a.name, b.name)) + users.sort((a, b) => + stringCompare(a.name, b.name, this.hass.locale.language) + ) ); protected firstUpdated(changedProps: PropertyValues) {