diff --git a/CHANGELOG.md b/CHANGELOG.md index ecfd7fd..60daa0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Migrate unit tests to Vitest https://github.com/maplibre/maplibre-gl-geocoder/pull/238 - Make the `reverseGeocode` field in `MaplibreGeocoderAPI` optional https://github.com/maplibre/maplibre-gl-geocoder/pull/245 +- Localise error messages https://github.com/maplibre/maplibre-gl-geocoder/pull/258 ### Bug fixes 🐛 diff --git a/lib/index.ts b/lib/index.ts index 65e303a..dab8b0b 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -6,7 +6,7 @@ import {EventEmitter} from "events"; import type {Marker, Popup, Map, FlyToOptions, MarkerOptions, default as MaplibreGl} from "maplibre-gl"; import {exceptions} from "./exceptions"; -import {placeholder as localization} from "./localization"; +import localization from "./localization"; /** * A regular expression to match coordinates. @@ -1052,13 +1052,14 @@ export default class MaplibreGeocoder { _renderError() { const errorMessage = - "
There was an error reaching the server
"; + `
${this._localize("errorConnectionFailed")}
`; this._renderMessage(errorMessage); } _renderNoResults() { const errorMessage = - "
No results found
"; + `
+ ${this._localize("errorNoResults")}
`; this._renderMessage(errorMessage); } @@ -1070,23 +1071,15 @@ export default class MaplibreGeocoder { } /** - * Get the text to use as the search bar placeholder + * Get a localised string for a given key * - * If placeholder is provided in options, then use options.placeholder - * Otherwise, if language is provided in options, then use the localized string of the first language if available - * Otherwise use the default - * - * @returns the value to use as the search bar placeholder - */ - private _getPlaceholderText(): string { - if (this.options.placeholder) return this.options.placeholder; - if (this.options.language) { - const firstLanguage = this.options.language.split(",")[0]; - const language = subtag.language(firstLanguage); - const localizedValue = localization[language]; - if (localizedValue) return localizedValue; - } - return "Search"; + * If language is provided in options, attempt to return localized string (defaults to English) + * @param key - key in the localization object + * @returns localized string + */ + private _localize(key: keyof typeof localization): string { + const language = subtag.language(this.options.language.split(',')[0]); + return this.options.language && localization?.[key][language] ? localization[key][language] : localization[key]['en'] } /** @@ -1245,7 +1238,7 @@ export default class MaplibreGeocoder { * @param placeholder - the text to use as the input element's placeholder */ setPlaceholder(placeholder?: string): this { - this.placeholder = placeholder ? placeholder : this._getPlaceholderText(); + this.placeholder = placeholder ? placeholder : this.options.placeholder || this._localize("placeholder"); this._inputEl.placeholder = this.placeholder; this._inputEl.setAttribute("aria-label", this.placeholder); return this; diff --git a/lib/localization.ts b/lib/localization.ts index dc658f6..3c4f5d7 100644 --- a/lib/localization.ts +++ b/lib/localization.ts @@ -26,10 +26,20 @@ const placeholder = { 'fi': 'Hae',//finnish 'is': 'Leita',//icelandic 'ko': '수색',//korean - 'pl': 'Szukaj', //polish + 'pl': 'Szukaj', //polish 'sl': 'Iskanje', //slovenian 'fa': 'جستجو', //persian(aka farsi) 'ru': 'Поиск'//russian } -export {placeholder}; +const errorNoResults = { + 'en': 'No results found', + 'de': 'Keine Ergebnisse gefunden' +} + +const errorConnectionFailed = { + 'en': 'There was an error reaching the server', + 'de': 'Verbindung fehlgeschlagen' +} + +export default { placeholder, errorNoResults, errorConnectionFailed }; diff --git a/test/unit/geocoder.ui.test.ts b/test/unit/geocoder.ui.test.ts index d624578..3856fb0 100644 --- a/test/unit/geocoder.ui.test.ts +++ b/test/unit/geocoder.ui.test.ts @@ -76,6 +76,36 @@ describe("Geocoder#inputControl", () => { expect(map.getContainer().querySelector(".maplibregl-ctrl-geocoder input").placeholder).toBe("Suche"); } ); + + test("placeholder localization defaults to English when translation is missing",() => { + setup({ language: "non_existant_language" }); + expect(map.getContainer().querySelector(".maplibregl-ctrl-geocoder input").placeholder).toBe("Search"); + } + ); + + test("error message localization", async () => { + setup({language: "de-DE"}); + const resultsPromise = geocoder.once("results"); + geocoder.query("non_existant_place") + await resultsPromise + expect(map.getContainer().querySelector(".maplibre-gl-geocoder--error").textContent.trim()).toBe("Keine Ergebnisse gefunden"); + }); + + test("error message localization with more than one language specified", async () => { + setup({language: "de-DE,lv,fr"}); + const resultsPromise = geocoder.once("results"); + geocoder.query("non_existant_place") + await resultsPromise + expect(map.getContainer().querySelector(".maplibre-gl-geocoder--error").textContent.trim()).toBe("Keine Ergebnisse gefunden"); + }); + + test("error message localization defaults to English when translation is missing", async () => { + setup({language: "non_existant_language"}); + const resultsPromise = geocoder.once("results"); + geocoder.query("non_existant_place") + await resultsPromise + expect(map.getContainer().querySelector(".maplibre-gl-geocoder--error").textContent.trim()).toBe("No results found"); + }); test("clear is not called on keydown (tab), no focus trap", () => { setup({});