Build a bilingual place search
A reader in Montréal types Montréal. A reader in Toronto types Montreal. Both mean the same
city, and your page should show one result, labelled in the language the reader is using. In unmap
that is one record carrying both names, so you choose the language when you render rather than
searching a different dataset.
import { Unmap } from "@unmap/sdk";
const unmap = new Unmap({ key: "um_live_..." });
const results = await unmap.geocoder.search("Montreal", { limit: 3, lang: "fr" });The code is plain TypeScript. For the React, Next.js, Vue or Nuxt wiring, start from the framework recipes.
One record, both names
lang decides which name comes back as name. names carries every label the record has, so the
other language is already in the response.
const [place] = await unmap.geocoder.search("Montreal", { limit: 1, lang: "fr" });
console.log(place?.name); // "Montréal"
console.log(place?.names?.en, place?.names?.fr); // "Montreal" "Montréal"The query language does not have to match lang. Quebec City with lang: "fr" comes back as
Québec, and Montreal and Montréal find the same locality, because accents are folded the same
way on both sides: Trois-Rivieres finds Trois-Rivières.
name is resolved in a fixed order: the names entry for your lang, then the record's French
name if you asked for fr, then its default name, then names.en. A record with no French label
therefore comes back in English whatever you ask for. That is not an error to handle; it is the
only name the record has.
Pass fr, not fr-CA
The browser reports fr-CA. The map reads that tag by its primary subtag and labels in French. The
geocoder looks lang up in names as you send it, finds no fr-CA key, and falls back to the
default name. Normalize the language once, as siteLang does below, and send the same value to
both.
Label the map in the same language
The map takes the same lang. Pass the normalized value when you create it, and the labels, the
search results and your own interface all agree.
function siteLang(): "en" | "fr" {
return navigator.language.toLowerCase().startsWith("fr") ? "fr" : "en";
}
const lang = siteLang();
const unmap = new Unmap({ key, container: "map", center: [-73.5673, 45.5017], zoom: 12, lang });
const results = await unmap.geocoder.search("vieux-port", { limit: 5, lang });The language is part of the style URL, so switching it later means loading a new style.
unmapStyle builds that URL from only what you pass it, so pass the style, mode and theme the map
was made with, or they reset to the defaults: base, light and no theme. setStyle replaces
every layer, including any you added yourself, so add yours again after the new style loads.
import { unmapStyle } from "@unmap/sdk";
// The style, mode and theme the map was made with (theme only if you set one).
const look = { style: "base", mode: "light" };
function switchMapLanguage(next: "en" | "fr") {
unmap.map?.setStyle(unmapStyle({ key, ...look, lang: next }));
}Show the other name too
A bilingual interface often shows both: Montréal (Montreal). Read the other language from
names, and skip it when it is the same string, which it is for places with one official name in
both languages, such as Trois-Rivières or Iqaluit.
import type { GeocodeResult } from "@unmap/sdk";
function otherName(place: GeocodeResult, lang: "en" | "fr"): string | undefined {
const other = place.names?.[lang === "fr" ? "en" : "fr"];
return other && other !== place.name ? other : undefined;
}names also holds alternate spellings people search by, under keys such as en-alt1 and
fr-alt1. They are why Old Montreal finds Vieux-Montréal, and they are never a label. If you
loop over names to build a language picker, skip keys ending in -alt or -iata, with or
without a trailing digit.
Search categories in either language
Nearby search takes everyday words in English or French and resolves them at the API, so a French interface can send what the reader typed.
const shops = await unmap.geocoder.nearby(["pharmacie", "depanneur"], {
near: [-73.5673, 45.5017],
limit: 5,
lang: "fr",
});Where this gets the wrong answer
Québecis two places. Withlang: "en"the top result is the province. Addlayers: "locality"when you mean the city.- A French street type on an English record still matches, but partially.
rue Slater Ottawafinds Slater Street, markedpartialwithterms_unmatched, because the record saysSlater Standruematched nothing. Show the result; do not treatpartialas a miss. - Most address records carry no name of their own. Their label is composed from the street and the city as the source wrote them, so there is no second language to show.
Related
- Geocoding API for how
langandnameswork. - Maps API for map label languages.
- Build an address autocomplete for the same search as the reader types.