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} />