Use generic picker for language picker (#27631)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Wendelin
2025-10-27 15:02:13 +01:00
committed by GitHub
parent 8fbd0226fc
commit 453a2ac7f3
5 changed files with 116 additions and 114 deletions

View File

@@ -1,56 +1,58 @@
import type { PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { formatLanguageCode } from "../common/language/format_language";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import type { FrontendLocaleData } from "../data/translation";
import { translationMetadata } from "../resources/translations-metadata";
import type { HomeAssistant } from "../types";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import "./ha-generic-picker";
import "./ha-list-item";
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
import "./ha-select";
import type { HaSelect } from "./ha-select";
export const getLanguageOptions = (
languages: string[],
nativeName: boolean,
noSort: boolean,
locale?: FrontendLocaleData
) => {
let options: { label: string; value: string }[] = [];
): PickerComboBoxItem[] => {
let options: PickerComboBoxItem[] = [];
if (nativeName) {
const translations = translationMetadata.translations;
options = languages.map((lang) => {
let label = translations[lang]?.nativeName;
if (!label) {
let primary = translations[lang]?.nativeName;
if (!primary) {
try {
// this will not work if Intl.DisplayNames is polyfilled, it will return in the language of the user
label = new Intl.DisplayNames(lang, {
primary = new Intl.DisplayNames(lang, {
type: "language",
fallback: "code",
}).of(lang)!;
} catch (_err) {
label = lang;
primary = lang;
}
}
return {
value: lang,
label,
id: lang,
primary,
search_labels: [primary],
};
});
} else if (locale) {
options = languages.map((lang) => ({
value: lang,
label: formatLanguageCode(lang, locale),
id: lang,
primary: formatLanguageCode(lang, locale),
search_labels: [formatLanguageCode(lang, locale)],
}));
}
if (!noSort && locale) {
options.sort((a, b) =>
caseInsensitiveStringCompare(a.label, b.label, locale.language)
caseInsensitiveStringCompare(a.primary, b.primary, locale.language)
);
}
return options;
@@ -80,115 +82,69 @@ export class HaLanguagePicker extends LitElement {
@state() _defaultLanguages: string[] = [];
@query("ha-select") private _select!: HaSelect;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._computeDefaultLanguageOptions();
}
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
const localeChanged =
changedProperties.has("hass") &&
this.hass &&
changedProperties.get("hass") &&
changedProperties.get("hass").locale.language !==
this.hass.locale.language;
if (
changedProperties.has("languages") ||
changedProperties.has("value") ||
localeChanged
) {
this._select.layoutOptions();
if (!this.disabled && this._select.value !== this.value) {
fireEvent(this, "value-changed", { value: this._select.value });
}
if (!this.value) {
return;
}
const languageOptions = this._getLanguagesOptions(
this.languages ?? this._defaultLanguages,
this.nativeName,
this.noSort,
this.hass?.locale
);
const selectedItemIndex = languageOptions.findIndex(
(option) => option.value === this.value
);
if (selectedItemIndex === -1) {
this.value = undefined;
}
if (localeChanged) {
this._select.select(selectedItemIndex);
}
}
}
private _getLanguagesOptions = memoizeOne(getLanguageOptions);
private _computeDefaultLanguageOptions() {
this._defaultLanguages = Object.keys(translationMetadata.translations);
}
protected render() {
const languageOptions = this._getLanguagesOptions(
private _getItems = () =>
this._getLanguagesOptions(
this.languages ?? this._defaultLanguages,
this.nativeName,
this.noSort,
this.hass?.locale
);
private _valueRenderer = (value) => {
const language = this._getItems().find(
(lang) => lang.id === value
)?.primary;
return html`<span slot="headline">${language ?? value}</span> `;
};
protected render() {
const value =
this.value ??
(this.required && !this.disabled
? languageOptions[0]?.value
: this.value);
(this.required && !this.disabled ? this._getItems()[0].id : this.value);
return html`
<ha-select
.label=${this.label ??
<ha-generic-picker
.hass=${this.hass}
.autofocus=${this.autofocus}
popover-placement="bottom-end"
.notFoundLabel=${this.hass?.localize(
"ui.components.language-picker.no_match"
)}
.placeholder=${this.label ??
(this.hass?.localize("ui.components.language-picker.language") ||
"Language")}
.value=${value || ""}
.required=${this.required}
.value=${value}
.valueRenderer=${this._valueRenderer}
.disabled=${this.disabled}
@selected=${this._changed}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
.inlineArrow=${this.inlineArrow}
>
${languageOptions.length === 0
? html`<ha-list-item value=""
>${this.hass?.localize(
"ui.components.language-picker.no_languages"
) || "No languages"}</ha-list-item
>`
: languageOptions.map(
(option) => html`
<ha-list-item .value=${option.value}
>${option.label}</ha-list-item
>
`
)}
</ha-select>
.getItems=${this._getItems}
@value-changed=${this._changed}
hide-clear-icon
></ha-generic-picker>
`;
}
static styles = css`
ha-select {
ha-generic-picker {
width: 100%;
min-width: 200px;
display: block;
}
`;
private _changed(ev): void {
const target = ev.target as HaSelect;
if (this.disabled || target.value === "" || target.value === this.value) {
return;
}
this.value = target.value;
private _changed(ev: ValueChangedEvent<string>): void {
ev.stopPropagation();
this.value = ev.detail.value;
fireEvent(this, "value-changed", { value: this.value });
}
}