From 3323c939e2425f9107638f5699f924db99e6e501 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Jun 2026 16:13:24 +0000
Subject: [PATCH] feat: add i18n/a11y localisation properties to select
components
---
.../vscode-select/vscode-select-base.ts | 24 ++++-
.../vscode-multi-select.test.ts | 99 +++++++++++++++++++
.../vscode-multi-select.ts | 37 +++++--
.../vscode-single-select.test.ts | 43 ++++++++
.../vscode-single-select.ts | 2 +-
5 files changed, 196 insertions(+), 9 deletions(-)
diff --git a/src/includes/vscode-select/vscode-select-base.ts b/src/includes/vscode-select/vscode-select-base.ts
index 66ce7ead0..adf0b2e76 100644
--- a/src/includes/vscode-select/vscode-select-base.ts
+++ b/src/includes/vscode-select/vscode-select-base.ts
@@ -40,6 +40,26 @@ export class VscodeSelectBase extends VscElement {
@property({reflect: true})
label = '';
+ /**
+ * Aria label for the button that opens the dropdown in combobox mode.
+ * Override this to localise the button for screen readers.
+ */
+ @property({attribute: 'open-button-aria-label'})
+ openButtonAriaLabel = 'Open the list of options';
+
+ /**
+ * Text shown in the dropdown when no options match the current filter.
+ */
+ @property({attribute: 'no-options-text'})
+ noOptionsText = 'No options';
+
+ /**
+ * Prefix text for the "create new option" entry shown in combobox+creatable
+ * mode. The rendered text will be: `${createOptionPrefix} "${filterPattern}"`.
+ */
+ @property({attribute: 'create-option-prefix'})
+ createOptionPrefix = 'Add';
+
/**
* The element cannot be used and is not focusable.
*/
@@ -712,12 +732,12 @@ export class VscodeSelectBase extends VscElement {
})}
@mouseout=${this._onPlaceholderOptionMouseOut}
>
- Add "${this._opts.filterPattern}"
+ ${this.createOptionPrefix} "${this._opts.filterPattern}"
`;
} else {
return isListEmpty
? html`
- No options
+ ${this.noOptionsText}
`
: nothing;
}
diff --git a/src/vscode-multi-select/vscode-multi-select.test.ts b/src/vscode-multi-select/vscode-multi-select.test.ts
index fa96eb3bc..e4362c431 100644
--- a/src/vscode-multi-select/vscode-multi-select.test.ts
+++ b/src/vscode-multi-select/vscode-multi-select.test.ts
@@ -350,6 +350,105 @@ describe('vscode-multi-select', () => {
expect(el.value).to.eql(['asdf']);
});
+ describe('i18n override props', () => {
+ it('selected-text overrides the badge label', async () => {
+ const el = await fixture(html`
+
+ One
+ Two
+
+ `);
+ expect(el.shadowRoot?.querySelector('.select-face-badge')).lightDom.to.eq(
+ '1 Ausgewählt'
+ );
+ });
+
+ it('selected-text overrides the badge label when nothing is selected', async () => {
+ const el = await fixture(html`
+
+ One
+ Two
+
+ `);
+ expect(el.shadowRoot?.querySelector('.select-face-badge')).lightDom.to.eq(
+ '0 Ausgewählt'
+ );
+ });
+
+ it('open-button-aria-label overrides the combobox button aria-label', async () => {
+ const el = await fixture(html`
+
+ One
+
+ `);
+ const btn =
+ el.shadowRoot?.querySelector('.combobox-button');
+ expect(btn?.getAttribute('aria-label')).to.eq('Öffnen');
+ });
+
+ it('no-options-text is shown when the filter matches nothing', async () => {
+ const el = await fixture(html`
+
+ Lorem
+
+ `);
+ el.focus();
+ await el.updateComplete;
+ await sendKeys({type: 'zzz'});
+ await el.updateComplete;
+ const noOpts = el.shadowRoot?.querySelector('.no-options');
+ expect(noOpts?.textContent?.trim()).to.eq('Keine Optionen');
+ });
+
+ it('create-option-prefix overrides the creatable suggestion text', async () => {
+ const el = await fixture(html`
+
+ Lorem
+
+ `);
+ el.focus();
+ await el.updateComplete;
+ await sendKeys({type: 'Neu'});
+ await el.updateComplete;
+ const placeholder = el.shadowRoot?.querySelector('.option.placeholder');
+ expect(placeholder?.textContent?.trim()).to.eq('Hinzufügen "Neu"');
+ });
+
+ it('select-all-title overrides the select-all button tooltip', async () => {
+ const el = await fixture(html`
+
+ One
+ Two
+
+ `);
+ expect(el.selectAllTitle).to.eq('Alle auswählen');
+ });
+
+ it('deselect-all-title overrides the deselect-all button tooltip', async () => {
+ const el = await fixture(html`
+
+ One
+ Two
+
+ `);
+ expect(el.deselectAllTitle).to.eq('Alle abwählen');
+ });
+
+ it('accept-button-text overrides the OK button label', async () => {
+ const el = await fixture(html`
+
+ One
+ Two
+
+ `);
+ expect(el.acceptButtonText).to.eq('Bestätigen');
+ });
+ });
+
it('selects multiple options with keyboard');
it('selectedIndexes sync with values');
it(
diff --git a/src/vscode-multi-select/vscode-multi-select.ts b/src/vscode-multi-select/vscode-multi-select.ts
index 7e8c523c3..ccee1f959 100644
--- a/src/vscode-multi-select/vscode-multi-select.ts
+++ b/src/vscode-multi-select/vscode-multi-select.ts
@@ -68,6 +68,31 @@ export class VscodeMultiSelect
@property({reflect: true})
name: string | undefined = undefined;
+ /**
+ * Label suffix used in the selected-count badge (e.g. "3 Selected").
+ * Override to localise the badge text.
+ */
+ @property({attribute: 'selected-text'})
+ selectedText = 'Selected';
+
+ /**
+ * Tooltip for the "select all" button in the dropdown controls.
+ */
+ @property({attribute: 'select-all-title'})
+ selectAllTitle = 'Select all';
+
+ /**
+ * Tooltip for the "deselect all" button in the dropdown controls.
+ */
+ @property({attribute: 'deselect-all-title'})
+ deselectAllTitle = 'Deselect all';
+
+ /**
+ * Label for the accept/OK button in the dropdown controls.
+ */
+ @property({attribute: 'accept-button-text'})
+ acceptButtonText = 'OK';
+
@property({type: Array, attribute: false})
set selectedIndexes(val: number[]) {
this._opts.selectedIndexes = val;
@@ -332,10 +357,10 @@ export class VscodeMultiSelect
private _renderLabel() {
switch (this._opts.selectedIndexes.length) {
case 0:
- return html`0 Selected`;
+ return html`0 ${this.selectedText}`;
default:
return html`${this._opts.selectedIndexes.length} Selected${this._opts.selectedIndexes.length} ${this.selectedText}`;
}
}
@@ -368,7 +393,7 @@ export class VscodeMultiSelect
@keydown=${this._onComboboxInputSpaceKeyDown}
/>