Skip to content

Commit 533180e

Browse files
Copilotbendera
authored andcommitted
feat: add i18n/a11y localisation properties to select components
1 parent 853763d commit 533180e

5 files changed

Lines changed: 196 additions & 9 deletions

File tree

‎src/includes/vscode-select/vscode-select-base.ts‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@ export class VscodeSelectBase extends VscElement {
4040
@property({reflect: true})
4141
label = '';
4242

43+
/**
44+
* Aria label for the button that opens the dropdown in combobox mode.
45+
* Override this to localise the button for screen readers.
46+
*/
47+
@property({attribute: 'open-button-aria-label'})
48+
openButtonAriaLabel = 'Open the list of options';
49+
50+
/**
51+
* Text shown in the dropdown when no options match the current filter.
52+
*/
53+
@property({attribute: 'no-options-text'})
54+
noOptionsText = 'No options';
55+
56+
/**
57+
* Prefix text for the "create new option" entry shown in combobox+creatable
58+
* mode. The rendered text will be: `${createOptionPrefix} "${filterPattern}"`.
59+
*/
60+
@property({attribute: 'create-option-prefix'})
61+
createOptionPrefix = 'Add';
62+
4363
/**
4464
* The element cannot be used and is not focusable.
4565
*/
@@ -712,12 +732,12 @@ export class VscodeSelectBase extends VscElement {
712732
})}
713733
@mouseout=${this._onPlaceholderOptionMouseOut}
714734
>
715-
Add "${this._opts.filterPattern}"
735+
${this.createOptionPrefix} "${this._opts.filterPattern}"
716736
</li>`;
717737
} else {
718738
return isListEmpty
719739
? html`<li class="no-options" @click=${this._onNoOptionsClick}>
720-
No options
740+
${this.noOptionsText}
721741
</li>`
722742
: nothing;
723743
}

‎src/vscode-multi-select/vscode-multi-select.test.ts‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,105 @@ describe('vscode-multi-select', () => {
350350
expect(el.value).to.eql(['asdf']);
351351
});
352352

353+
describe('i18n override props', () => {
354+
it('selected-text overrides the badge label', async () => {
355+
const el = await fixture<VscodeMultiSelect>(html`
356+
<vscode-multi-select selected-text="Ausgewählt">
357+
<vscode-option selected>One</vscode-option>
358+
<vscode-option>Two</vscode-option>
359+
</vscode-multi-select>
360+
`);
361+
expect(el.shadowRoot?.querySelector('.select-face-badge')).lightDom.to.eq(
362+
'1 Ausgewählt'
363+
);
364+
});
365+
366+
it('selected-text overrides the badge label when nothing is selected', async () => {
367+
const el = await fixture<VscodeMultiSelect>(html`
368+
<vscode-multi-select selected-text="Ausgewählt">
369+
<vscode-option>One</vscode-option>
370+
<vscode-option>Two</vscode-option>
371+
</vscode-multi-select>
372+
`);
373+
expect(el.shadowRoot?.querySelector('.select-face-badge')).lightDom.to.eq(
374+
'0 Ausgewählt'
375+
);
376+
});
377+
378+
it('open-button-aria-label overrides the combobox button aria-label', async () => {
379+
const el = await fixture<VscodeMultiSelect>(html`
380+
<vscode-multi-select combobox open-button-aria-label="Öffnen">
381+
<vscode-option>One</vscode-option>
382+
</vscode-multi-select>
383+
`);
384+
const btn =
385+
el.shadowRoot?.querySelector<HTMLButtonElement>('.combobox-button');
386+
expect(btn?.getAttribute('aria-label')).to.eq('Öffnen');
387+
});
388+
389+
it('no-options-text is shown when the filter matches nothing', async () => {
390+
const el = await fixture<VscodeMultiSelect>(html`
391+
<vscode-multi-select combobox no-options-text="Keine Optionen">
392+
<vscode-option>Lorem</vscode-option>
393+
</vscode-multi-select>
394+
`);
395+
el.focus();
396+
await el.updateComplete;
397+
await sendKeys({type: 'zzz'});
398+
await el.updateComplete;
399+
const noOpts = el.shadowRoot?.querySelector('.no-options');
400+
expect(noOpts?.textContent?.trim()).to.eq('Keine Optionen');
401+
});
402+
403+
it('create-option-prefix overrides the creatable suggestion text', async () => {
404+
const el = await fixture<VscodeMultiSelect>(html`
405+
<vscode-multi-select
406+
combobox
407+
creatable
408+
create-option-prefix="Hinzufügen"
409+
>
410+
<vscode-option>Lorem</vscode-option>
411+
</vscode-multi-select>
412+
`);
413+
el.focus();
414+
await el.updateComplete;
415+
await sendKeys({type: 'Neu'});
416+
await el.updateComplete;
417+
const placeholder = el.shadowRoot?.querySelector('.option.placeholder');
418+
expect(placeholder?.textContent?.trim()).to.eq('Hinzufügen "Neu"');
419+
});
420+
421+
it('select-all-title overrides the select-all button tooltip', async () => {
422+
const el = await fixture<VscodeMultiSelect>(html`
423+
<vscode-multi-select select-all-title="Alle auswählen">
424+
<vscode-option>One</vscode-option>
425+
<vscode-option>Two</vscode-option>
426+
</vscode-multi-select>
427+
`);
428+
expect(el.selectAllTitle).to.eq('Alle auswählen');
429+
});
430+
431+
it('deselect-all-title overrides the deselect-all button tooltip', async () => {
432+
const el = await fixture<VscodeMultiSelect>(html`
433+
<vscode-multi-select deselect-all-title="Alle abwählen">
434+
<vscode-option>One</vscode-option>
435+
<vscode-option>Two</vscode-option>
436+
</vscode-multi-select>
437+
`);
438+
expect(el.deselectAllTitle).to.eq('Alle abwählen');
439+
});
440+
441+
it('accept-button-text overrides the OK button label', async () => {
442+
const el = await fixture<VscodeMultiSelect>(html`
443+
<vscode-multi-select accept-button-text="Bestätigen">
444+
<vscode-option>One</vscode-option>
445+
<vscode-option>Two</vscode-option>
446+
</vscode-multi-select>
447+
`);
448+
expect(el.acceptButtonText).to.eq('Bestätigen');
449+
});
450+
});
451+
353452
it('selects multiple options with keyboard');
354453
it('selectedIndexes sync with values');
355454
it(

‎src/vscode-multi-select/vscode-multi-select.ts‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,31 @@ export class VscodeMultiSelect
6868
@property({reflect: true})
6969
name: string | undefined = undefined;
7070

71+
/**
72+
* Label suffix used in the selected-count badge (e.g. "3 Selected").
73+
* Override to localise the badge text.
74+
*/
75+
@property({attribute: 'selected-text'})
76+
selectedText = 'Selected';
77+
78+
/**
79+
* Tooltip for the "select all" button in the dropdown controls.
80+
*/
81+
@property({attribute: 'select-all-title'})
82+
selectAllTitle = 'Select all';
83+
84+
/**
85+
* Tooltip for the "deselect all" button in the dropdown controls.
86+
*/
87+
@property({attribute: 'deselect-all-title'})
88+
deselectAllTitle = 'Deselect all';
89+
90+
/**
91+
* Label for the accept/OK button in the dropdown controls.
92+
*/
93+
@property({attribute: 'accept-button-text'})
94+
acceptButtonText = 'OK';
95+
7196
@property({type: Array, attribute: false})
7297
set selectedIndexes(val: number[]) {
7398
this._opts.selectedIndexes = val;
@@ -332,10 +357,10 @@ export class VscodeMultiSelect
332357
private _renderLabel() {
333358
switch (this._opts.selectedIndexes.length) {
334359
case 0:
335-
return html`<span class="select-face-badge no-item">0 Selected</span>`;
360+
return html`<span class="select-face-badge no-item">0 ${this.selectedText}</span>`;
336361
default:
337362
return html`<span class="select-face-badge"
338-
>${this._opts.selectedIndexes.length} Selected</span
363+
>${this._opts.selectedIndexes.length} ${this.selectedText}</span
339364
>`;
340365
}
341366
}
@@ -368,7 +393,7 @@ export class VscodeMultiSelect
368393
@keydown=${this._onComboboxInputSpaceKeyDown}
369394
/>
370395
<button
371-
aria-label="Open the list of options"
396+
aria-label=${this.openButtonAriaLabel}
372397
class="combobox-button"
373398
type="button"
374399
@click=${this._onComboboxButtonClick}
@@ -413,7 +438,7 @@ export class VscodeMultiSelect
413438
<button
414439
type="button"
415440
@click=${this._onMultiSelectAllClick}
416-
title="Select all"
441+
title=${this.selectAllTitle}
417442
class="action-icon"
418443
id="select-all"
419444
>
@@ -422,7 +447,7 @@ export class VscodeMultiSelect
422447
<button
423448
type="button"
424449
@click=${this._onMultiDeselectAllClick}
425-
title="Deselect all"
450+
title=${this.deselectAllTitle}
426451
class="action-icon"
427452
id="select-none"
428453
>
@@ -431,7 +456,7 @@ export class VscodeMultiSelect
431456
<vscode-button
432457
class="button-accept"
433458
@click=${this._onMultiAcceptClick}
434-
>OK</vscode-button
459+
>${this.acceptButtonText}</vscode-button
435460
>
436461
</div>
437462
`

‎src/vscode-single-select/vscode-single-select.test.ts‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,49 @@ describe('vscode-single-select', () => {
12591259
expect(el.value).to.eql('asdf');
12601260
});
12611261

1262+
describe('i18n override props', () => {
1263+
it('no-options-text is shown when the filter matches nothing', async () => {
1264+
const el = await fixture<VscodeSingleSelect>(html`
1265+
<vscode-single-select combobox no-options-text="Keine Optionen">
1266+
<vscode-option>Lorem</vscode-option>
1267+
<vscode-option>Ipsum</vscode-option>
1268+
</vscode-single-select>
1269+
`);
1270+
await clickOnElement(el);
1271+
await sendKeys({type: 'zzz'});
1272+
await el.updateComplete;
1273+
const noOpts = el.shadowRoot?.querySelector('.no-options');
1274+
expect(noOpts?.textContent?.trim()).to.eq('Keine Optionen');
1275+
});
1276+
1277+
it('create-option-prefix overrides the creatable suggestion text', async () => {
1278+
const el = await fixture<VscodeSingleSelect>(html`
1279+
<vscode-single-select combobox creatable create-option-prefix="Hinzufügen">
1280+
<vscode-option>Lorem</vscode-option>
1281+
<vscode-option>Ipsum</vscode-option>
1282+
<vscode-option>Dolor</vscode-option>
1283+
</vscode-single-select>
1284+
`);
1285+
el.focus();
1286+
await el.updateComplete;
1287+
await sendKeys({type: 'Neu'});
1288+
await el.updateComplete;
1289+
const placeholder = el.shadowRoot?.querySelector('.option.placeholder');
1290+
expect(placeholder?.textContent?.trim()).to.eq('Hinzufügen "Neu"');
1291+
});
1292+
1293+
it('open-button-aria-label overrides the combobox button aria-label', async () => {
1294+
const el = await fixture<VscodeSingleSelect>(html`
1295+
<vscode-single-select combobox open-button-aria-label="Öffnen">
1296+
<vscode-option>Lorem</vscode-option>
1297+
</vscode-single-select>
1298+
`);
1299+
const btn =
1300+
el.shadowRoot?.querySelector<HTMLButtonElement>('.combobox-button');
1301+
expect(btn?.getAttribute('aria-label')).to.eq('Öffnen');
1302+
});
1303+
});
1304+
12621305
//keyboard navigation
12631306
it('selects previous option with keyboard');
12641307
it('selects next option with keyboard');

‎src/vscode-single-select/vscode-single-select.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export class VscodeSingleSelect
394394
@keydown=${this._onComboboxInputSpaceKeyDown}
395395
/>
396396
<button
397-
aria-label="Open the list of options"
397+
aria-label=${this.openButtonAriaLabel}
398398
class="combobox-button"
399399
type="button"
400400
@click=${this._onComboboxButtonClick}

0 commit comments

Comments
 (0)