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({});