diff --git a/README.md b/README.md index 48d7f564..1be38257 100644 --- a/README.md +++ b/README.md @@ -83,13 +83,19 @@ There are multiple customizations and properties able to be instantiated within ## Options -| JS Property | HTML Attribute | Description | Value | Default | -| ----------- | -------------- | ------------------------------------------------------------------------------- | ------- | ------------ | -| list | data-list | Where to find the list of suggestions. | Array of strings, HTML element, CSS selector (no groups, i.e. no commas), String containing a comma-separated list of items | N/A | -| minChars | data-minchars | Minimum characters the user has to type before the autocomplete popup shows up. | Number | 2 | -| maxItems | data-maxitems | Maximum number of suggestions to display. | Number | 10 | -| autoFirst | data-autofirst | Should the first element be automatically | Boolean | false | -| listLabel | data-listlabel | Denotes a label to be used as aria-label on the generated autocomplete list. | String | Results List | +| JS Property | HTML Attribute | Description | Value | Default | +|----------------------|---------------------------|---------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------| +| list | data-list | Where to find the list of suggestions. | Array of strings, HTML element, CSS selector (no groups, i.e. no commas), String containing a comma-separated list of items | N/A | +| minChars | data-minchars | Minimum characters the user has to type before the autocomplete popup shows up. | Number | 2 | +| maxItems | data-maxitems | Maximum number of suggestions to display. | Number | 10 | +| autoFirst | data-autofirst | Should the first element be automatically | Boolean | false | +| listLabel | data-listlabel | Denotes a label to be used as aria-label on the generated autocomplete list. | String | Results List | +| tStatusResult | data-tstatusresult | Allows for translations of the result list status. | String | ${length} results found | +| tNoResults | data-tnoresults | Allows for translations of the notification that no result has been found. | String | No results found | +| tStatusQueryTooShort | data-tstatusquerytooshort | Text output when not enough chars have been entered | String | Type ${minChars} or more characters for results. | +| tStatusStartTyping | data-tstatusstarttyping | Text output when no chars have been entered | String | Begin typing for results. | +| tListItemText | data-tstatusquerytooshort | Text output for found list items | String | list item ${index} of ${length} | + ## License diff --git a/awesomplete.js b/awesomplete.js index 95188f21..8061e2bb 100644 --- a/awesomplete.js +++ b/awesomplete.js @@ -39,9 +39,13 @@ var _ = function (input, o) { item: _.ITEM, replace: _.REPLACE, tabSelect: false, - listLabel: "Results List" + language: "en", + + + tListItemText: "list item ${index} of ${length}" }, o); + this.index = -1; // Create necessary elements @@ -53,7 +57,7 @@ var _ = function (input, o) { role: "listbox", id: "awesomplete_list_" + this.count, inside: this.container, - "aria-label": this.listLabel + "aria-label":phrase(this, 'list-label') }); this.status = $.create("span", { @@ -62,7 +66,7 @@ var _ = function (input, o) { "aria-live": "assertive", "aria-atomic": true, inside: this.container, - textContent: this.minChars != 0 ? ("Type " + this.minChars + " or more characters for results.") : "Begin typing for results." + textContent: this.minChars != 0 ? phrase(this, 'status-query-too-short', {"${minChars}": this.minChars}) : phrase(this, 'status-start-typing') }); // Bind events @@ -259,7 +263,7 @@ _.prototype = { if (i > -1 && lis.length > 0) { lis[i].setAttribute("aria-selected", "true"); - this.status.textContent = lis[i].textContent + ", list item " + (i + 1) + " of " + lis.length; + this.status.textContent = lis[i].textContent + ", "+ phrase(this, "list-item-text", {"${index}" : (i + 1), "${length}": lis.length}); this.input.setAttribute("aria-activedescendant", this.ul.id + "_item_" + this.index); @@ -328,20 +332,20 @@ _.prototype = { if (this.ul.children.length === 0) { - this.status.textContent = "No results found"; + this.status.textContent = phrase(this, 'no-results'); this.close({ reason: "nomatches" }); } else { this.open(); - this.status.textContent = this.ul.children.length + " results found"; + this.status.textContent = phrase(this, 'status-result', {"${length}": this.ul.children.length}); } } else { this.close({ reason: "nomatches" }); - this.status.textContent = "No results found"; + this.status.textContent = phrase(this, 'no-results'); } } }; @@ -350,6 +354,41 @@ _.prototype = { _.all = []; +_.languages = { + "en": { + "list-label": "Results List", + "status-result": "${length} results found", + "no-results": "No results found", + "status-query-too-short": "Type ${minChars} or more characters for results.", + "status-start-typing": "Begin typing for results.", + "list-item-text": "list item ${index} of ${length}" + }, + "de": { + "list-label": "Ergebnisliste", + "status-result": "${length} Ergebnisse gefunden", + "no-results": "Keine Ergebnisse gefunden", + "status-query-too-short": "Geben Sie ${minChars} oder mehr Zeichen für die Suche ein", + "status-start-typing": "Beginnen Sie mit der Eingabe", + "list-item-text": "Listenelement ${index} von ${length}" + }, + "fr": { + "list-label": "Liste des résultats", + "status-result": "${length} résultats trouvés", + "no-results": "Aucun résultat trouvé", + "status-query-too-short": "Tapez ${minChars} ou plusieurs caractères pour les résultats.", + "status-start-typing": "Commencer à écrire", + "list-item-text": "élément de liste ${index} de ${length}" + }, + "it": { + "list-label": "Elenco dei risultati", + "status-result": "${length} risultati trovati", + "no-results": "Nessun risultato trovato", + "status-query-too-short": "Digita ${minChars} o più caratteri per i risultati.", + "status-start-typing": "Inizia a digitare per i risultati.", + "list-item-text": "elemento dell'elenco ${index} di ${length}" + }, +}; + _.FILTER_CONTAINS = function (text, input) { return RegExp($.regExpEscape(input.trim()), "i").test(text); }; @@ -429,6 +468,19 @@ function configure(instance, properties, o) { } } } +function phrase(instance, key, replacements) { + const translations = _.languages[instance.language]; + var result = translations[key]; + if(result === undefined) { + console.warn(instance.language + "no result found " + key) + } + for (var i in replacements) { + while(result.indexOf(i) >= 0) { + result = result.replace(i, replacements[i]) + } + } + return result; +} // Helpers diff --git a/test/api/evaluateSpec.js b/test/api/evaluateSpec.js index b8107bb3..45f674b8 100644 --- a/test/api/evaluateSpec.js +++ b/test/api/evaluateSpec.js @@ -18,6 +18,7 @@ describe("awesomplete.evaluate", function () { expect(this.subject.close).toHaveBeenCalledWith({ reason: "nomatches" }); + expect(this.subject.status.textContent).toBe("No results found") }); }); @@ -93,4 +94,21 @@ describe("awesomplete.evaluate", function () { expect(this.subject.index).toBe(-1); }); }); + + describe("with no results and German text", function () { + beforeEach(function () { + this.subject.language = "de" + $.type(this.subject.input, "nosuchitem"); + }); + + it("closes completer", function () { + spyOn(this.subject, "close"); + this.subject.evaluate(); + + expect(this.subject.close).toHaveBeenCalledWith({ + reason: "nomatches" + }); + expect(this.subject.status.textContent).toBe("Keine Ergebnisse gefunden") + }); + }); }); diff --git a/test/api/gotoSpec.js b/test/api/gotoSpec.js index 8a31dde5..cd44b333 100644 --- a/test/api/gotoSpec.js +++ b/test/api/gotoSpec.js @@ -54,6 +54,19 @@ describe("awesomplete.goto", function () { }); }); + + describe("with German text and item index > -1", function () { + beforeEach(function () { + this.subject.language = "de" + this.subject.goto(0); + }); + + it("updates status", function () { + expect(this.subject.status.textContent).toBe("item1, Listenelement 1 von 3"); + }); + }); + + describe("with item index = -1", function () { beforeEach(function () { this.subject.goto(0); diff --git a/test/init/optionsSpec.js b/test/init/optionsSpec.js index fc60997c..635109d3 100644 --- a/test/init/optionsSpec.js +++ b/test/init/optionsSpec.js @@ -38,6 +38,10 @@ describe("Constructor options", function () { it("replaces input value with REPLACE", function () { expect(this.subject.replace).toEqual(Awesomplete.REPLACE); }); + + it("uses en as default language", function () { + expect(this.subject.language).toEqual("en"); + }); }); describe("with custom options in constructor", function () { @@ -50,7 +54,8 @@ describe("Constructor options", function () { filter: $.noop, sort: $.noop, item: $.noop, - replace: $.noop + replace: $.noop, + language: 'de' }; }); @@ -58,6 +63,7 @@ describe("Constructor options", function () { expect(this.subject.minChars).toBe(3); expect(this.subject.maxItems).toBe(9); expect(this.subject.autoFirst).toBe(true); + expect(this.subject.language).toBe("de"); }); it("overrides default functions", function () {