npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@americana/diplomat

v0.2.1

Published

Internationalization plugin for MapLibre GL JS

Readme

Diplomat

Diplomat prepares your interactive map for an international audience. With a few lines of code, your MapLibre GL JS–powered map will speak the user’s preferred language while informing them about local languages the world over.

| Before | After | | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | | | | | | | | |

Requirements

Diplomat is compatible with applications using MapLibre GL JS v5.13.0 and above.

The stylesheet must use the newer expression syntax; legacy style functions are not supported. The stylesheet’s sources must conform to Diplomat’s schema. Several popular vector tilesets already conform to this schema, including:

With additional configuration, Diplomat supports even more vector tilesets, including:

Installation

This plugin is available as an NPM package. To install it, run the following command:

npm install @americana/diplomat

Alternatively, you can include the plugin as a standalone script from a CDN such as unpkg.

Usage

After creating an instance of maplibregl.Map, register an event listener for the styledata event that localizes the map:

map.once("styledata", (event) => {
  map.localizeStyle();
});

If your stylesheet uses a tileset that formats the name keys differently, such as OpenHistoricalMap or Shortbread, set the format when localizing the layers, for example:

map.once("styledata", (event) => {
  let locales = maplibregl.Diplomat.getLocales();
  map.localizeStyle(locales, {
    localizedNamePropertyFormat: "name_$1",
  });
});

If you set the hash option to a string when creating the Map, you can have this code respond to a language parameter in the URL hash. Add a window event listener for whenever the hash changes, in order to update the layers:

addEventListener("hashchange", (event) => {
  let oldLanguage = maplibregl.Diplomat.getLanguageFromURL(
    new URL(event.oldURL),
  );
  let newLanguage = maplibregl.Diplomat.getLanguageFromURL(
    new URL(event.newURL),
  );

  if (oldLanguage !== newLanguage) {
    map.localizeStyle();
  }
});

Similarly, you can immediately respond to any change the user makes to their browser language preference in real time:

addEventListener("languagechange", (event) => {
  map.localizeStyle();
});

[!NOTE] By default, MapLibre GL JS does not support bidirectional text. Arabic, Hebrew, and other right-to-left languages will be unreadable unless you install the mapbox-gl-rtl-text plugin.

Schema

Diplomat can manipulate any GeoJSON or vector tile source, as long as it includes the following properties on each feature:

  • name (string): The name in the local or official language. You can customize this property by setting the unlocalizedNameProperty option when calling maplibregl.Map.prototype.localizeStyle().
  • name:xyz (string): The name in another language, where xyz is a valid IETF language tag. For example, name:zh for Chinese, name:zh-Hant for Traditional Chinese, name:zh-Hant-TW for Traditional Chinese (Taiwan), and name:zh-Latn-pinyin for Chinese in pinyin. You can customize this format by setting the localizedNamePropertyFormat option when calling maplibregl.Map.prototype.localizeStyle().

For compatibility with the OpenMapTiles schema, name_en and name_de are also recognized as alternatives to name:en and name:de for English and German, respectively, but only in the transportation_name layer. For performance reasons, Diplomat does not look for this format by default for any other language or layer.

Each of the supported properties may be set to a list of values separated by semicolons. For example, if a place speaks both English and French, name should be English Name;French Name. Similarly, if a landmark has three equally common names in Spanish, regardless of dialect, name:es should be Nombre Uno;Nombre Dos;Nombre Tres. In the rare case that a single name contains a semicolon, it should be escaped as a double semicolon (;;).

API

This plugin adds several constants to a maplibregl.Diplomat namespace and adds a single method to each instance of maplibregl.Map.

maplibregl.Diplomat.localizedName

An expression that produces the names in the user's preferred language, each on a separate line.

Use this constant if you are building the entire stylesheet programmatically before initializing a maplibregl.Map and want more fine-grained control over which layers have which label layout than maplibregl.Map.prototype.localizeStyle() provides.

This expression is appropriate for labeling a type of feature that almost always has a familiar translation in the user’s preferred language, such as the name of a country. It is also appropriate for minor features like points of interest, for which an extra local-language gloss would clutter the map.

Example:

map.setLayoutProperty(
  "country-labels",
  "text-field",
  maplibregl.Diplomat.localizedName,
);

maplibregl.Diplomat.localizedNameInline

An expression that produces the names in the user's preferred language, all on the same line.

Use this constant if you are building the entire stylesheet programmatically before initializing a maplibregl.Map and want more fine-grained control over which layers have which label layout than maplibregl.Map.prototype.localizeStyle() provides.

This expression is appropriate for labeling a linear feature, such as a road or waterway. The symbol layer’s symbol-placement layout property should be set to either line or line-center.

Example:

map.setLayoutProperty(
  "road-labels",
  "text-field",
  maplibregl.Diplomat.localizedNameInline,
);

maplibre.Diplomat.localizedNameWithLocalGloss

An expression that produces the name in the user's preferred language, followed by the name in the local language in parentheses if it differs.

Use this constant if you are building the entire stylesheet programmatically before initializing a maplibregl.Map and want more fine-grained control over which layers have which label layout than maplibregl.Map.prototype.localizeStyle() provides.

This expression is appropriate for labeling a type of feature that is only sometimes translated into user’s preferred language, such as the name of a city or town. The extra local-language gloss respects local customs and keeps the user informed, but it can also risk information overload and crowd out other useful labels.

Example:

map.setLayoutProperty(
  "city-labels",
  "text-field",
  maplibregl.Diplomat.localizedNameWithGloss,
);

maplibregl.Diplomat.getLocalizedCountryNameExpression()

Returns an expression that converts the given country code to a human-readable name in the user's preferred language.

This method is useful for stylesheets powered by OpenMapTiles, which only provides the ISO 3166-1 alpha-3 code of the country on either side of a boundary, but not the full country name in any language.

Parameters:

  • code (string): An expression that evaluates to an ISO 3166-1 alpha-3 country code.

Example:

map.setLayoutProperty(
  "boundary-edge-labels",
  "text-field",
  maplibregl.Diplomat.getLocalizedCountryNameExpression(["get", "adm0_l"]),
);

[!NOTE] Use maplibregl.Diplomat.getGlobalStateForLocalization() to populate the global state required by this expression, then call maplibregl.Map.prototype.localizeStyle(). Otherwise, this expression evaluates to the raw country code.

maplibregl.Diplomat.getGlobalStateForLocalization()

Returns the global state that Diplomat needs to fully localize the style.

If you are building a stylesheet programmatically, you can use this method to populate a state property at the root of the stylesheet before initializing a maplibregl.Map. You can add additional global state properties besides the ones that come from this object, as long as you avoid making a deep clone of the object.

If your stylesheet is powered by OpenMapTiles, you need to set this global state object in order to localizing boundary edge labels that come from the boundary layer. Otherwise, the user will see only ISO 3166-1 alpha-3 codes, because OpenMapTiles only provides these codes instead of the full country name on either side of a boundary.

Parameters:

  • locales (string): The locales for formatting the country names.
  • options.uppercaseCountryNames (boolean): Whether to write the country names in all uppercase, respecting the locale’s case conventions. Enable this option if you intend to display the boundary edge labels in uppercase, because the upcase expression operator is locale-insensitive.

Example:

map.once("styledata", (event) => {
  let localizationState = maplibregl.Diplomat.getGlobalStateForLocalization(
    getLocales(),
    {
      uppercaseCountryNames: true,
    },
  );
  for (let [key, value] of Object.entries(localizationState)) {
    map.setGlobalStateProperty(key, value);
  }
});

maplibregl.Diplomat.getLocales()

Returns the languages that the user prefers.

Example:

maplibregl.Diplomat.getLocales().includes("en");

maplibregl.Map.prototype.localizeStyle()

Updates each style layer's text-field value to match the given locales, upgrading any unlocalizable layer along the way.

This method ugprades unlocalizable layers to localized multiline or inline labels depending on the symbol-placement layout property. To add a dual language label to a layer, set its text-field layout property manually using the maplibregl.Diplomat.localizedNameWithLocalGloss constant.

Parameters:

  • layers ([object]): The style layers to localize.
  • locales ([string]): The locales to insert into each layer, as a comma-separated list of IETF language tags. Uses the language URL hash parameter or browser preferences by default.
  • options (object):
    • unlocalizedNameProperty (string): The name of the property holding the unlocalized name. name by default.
    • localizedNamePropertyFormat (string): "The format of properties holding localized names, where $1 is replaced by an IETF language tag. name:$1 by default.
    • options.uppercaseCountryNames (boolean): Whether to write country names in all uppercase, respecting the locale’s case conventions.

Example:

map.localizeStyle(["eo"], {
  localizedNamePropertyFormat: "name_$1",
});

Caveats

Diplomat only switches between languages that are present in the stylesheet’s data sources. It does not fetch translations from other sources or generate its own transliterations. By convention, OpenStreetMap’s coverage in some regions is largely limited to locally spoken languages. If you need more comprehensive coverage in a given language, consider using a tileset that combines names from OpenStreetMap and Wikidata, such as the OpenStreetMap U.S. Tileservice.

By default, MapLibre GL JS does not support bidirectional text. Arabic, Hebrew, and other right-to-left languages will be unreadable unless you install the mapbox-gl-rtl-text plugin.

Diplomat only performs basic language fallbacks according to the ICU locale fallback algorithm. It makes no attempt to fallback to a related but distinct language code, for example from sr-Cyrl to ru or from nb to no. Instead, the user can set their preferred languages in their browser or operating system settings.

For historical reasons, OpenStreetMap’s coverage in many reasons encodes multiple local names separated by human-readable punctuation. Diplomat makes no attempt to guess which punctuation is part of a name and which punctuation delimits two names.

Acknowledgments

This plugin was spun out of the OpenStreetMap Americana project. It was originally inspired by Mapbox GL Language.