Skip to content

Commit 29545e5

Browse files
committed
Streamlined configuration
Replaced localizeLayers with a more encapsulated localizeStyle that can automatically manipulate all the layers and upgrade unlocalizable layers. Added the rest of the country code conversion functionality from OSM Americana.
1 parent 74db2b2 commit 29545e5

File tree

4 files changed

+516
-134
lines changed

4 files changed

+516
-134
lines changed

README.md

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -39,36 +39,18 @@ After creating an instance of `maplibregl.Map`, register an event listener for t
3939

4040
```js
4141
map.once("styledata", (event) => {
42-
// Prepare layers to be localized.
43-
map.setLayoutProperty(
44-
"country-labels",
45-
"text-field",
46-
maplibregl.Diplomat.localizedName,
47-
);
48-
map.setLayoutProperty(
49-
"city-labels",
50-
"text-field",
51-
maplibregl.Diplomat.localizedNameWithGloss,
52-
);
53-
map.setLayoutProperty(
54-
"road-labels",
55-
"text-field",
56-
maplibregl.Diplomat.localizedNameInline,
57-
);
58-
59-
// Localize the layers.
60-
const locales = maplibregl.Diplomat.getLocales();
61-
const style = map.getStyle();
62-
map.localizeLayers(style.layers, locales);
63-
map.setStyle(style);
42+
map.localizeStyle();
6443
});
6544
```
6645

6746
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:
6847

6948
```js
70-
map.localizeLayers(style.layers, locales, {
71-
localizedNamePropertyFormat: "name_$1",
49+
map.once("styledata", (event) => {
50+
let locales = maplibregl.Diplomat.getLocales();
51+
map.localizeStyle(locales, {
52+
localizedNamePropertyFormat: "name_$1",
53+
});
7254
});
7355
```
7456

@@ -84,23 +66,28 @@ addEventListener("hashchange", (event) => {
8466
);
8567

8668
if (oldLanguage !== newLanguage) {
87-
let locales = maplibregl.Diplomat.getLocales();
88-
let style = map.getStyle();
89-
map.localizeLayers(style.layers, locales);
90-
map.setStyle(style);
69+
map.localizeStyle();
9170
}
9271
});
9372
```
9473

74+
Similarly, you can immediately respond to any change the user makes to their browser language preference in real time:
75+
76+
```js
77+
addEventListener("languagechange", (event) => {
78+
map.localizeStyle();
79+
});
80+
```
81+
9582
> [!NOTE]
9683
> 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](https://maplibre.org/maplibre-gl-js/docs/examples/add-support-for-right-to-left-scripts/).
9784
9885
## Schema
9986

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

102-
- **`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.localizeLayers()`](#maplibreglmapprototypelocalizelayers).
103-
- **<code>name:<var>xyz</var></code>** (`string`): The name in another language, where <var>xyz</var> is a valid [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag). For example, <code>name:zh</code> for Chinese, <code>name:zh-Hant</code> for Traditional Chinese, <code>name:zh-Hant-TW</code> for Traditional Chinese (Taiwan), and <code>name:zh-Latn-pinyin</code> for Chinese in pinyin. You can customize this format by setting the `localizedNamePropertyFormat` option when calling [`maplibregl.Map.prototype.localizeLayers()`](#maplibreglmapprototypelocalizelayers).
89+
- **`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()`](#maplibreglmapprototypelocalizestyle).
90+
- **<code>name:<var>xyz</var></code>** (`string`): The name in another language, where <var>xyz</var> is a valid [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag). For example, <code>name:zh</code> for Chinese, <code>name:zh-Hant</code> for Traditional Chinese, <code>name:zh-Hant-TW</code> for Traditional Chinese (Taiwan), and <code>name:zh-Latn-pinyin</code> for Chinese in pinyin. You can customize this format by setting the `localizedNamePropertyFormat` option when calling [`maplibregl.Map.prototype.localizeStyle()`](#maplibreglmapprototypelocalizestyle).
10491

10592
For compatibility with the [OpenMapTiles](https://openmaptiles.org/schema/) 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.
10693

@@ -114,6 +101,8 @@ This plugin adds several constants to a `maplibregl.Diplomat` namespace and adds
114101

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

104+
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()`](#maplibreglmapprototypelocalizestyle) provides.
105+
117106
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.
118107

119108
Example:
@@ -130,6 +119,8 @@ map.setLayoutProperty(
130119

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

122+
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()`](#maplibreglmapprototypelocalizestyle) provides.
123+
133124
This expression is appropriate for labeling a linear feature, such as a road or waterway. The symbol layer’s [`symbol-placement`](https://maplibre.org/maplibre-style-spec/layers/#symbol-placement) layout property should be set to either `line` or `line-center`.
134125

135126
Example:
@@ -146,6 +137,8 @@ map.setLayoutProperty(
146137

147138
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.
148139

140+
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()`](#maplibreglmapprototypelocalizestyle) provides.
141+
149142
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](https://en.wikipedia.org/wiki/Information_overload) and crowd out other useful labels.
150143

151144
Example:
@@ -158,7 +151,7 @@ map.setLayoutProperty(
158151
);
159152
```
160153

161-
### `maplibregl.Diplomat.getCountryName()`
154+
### `maplibregl.Diplomat.getLocalizedCountryNameExpression()`
162155

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

@@ -174,10 +167,32 @@ Example:
174167
map.setLayoutProperty(
175168
"boundary-edge-labels",
176169
"text-field",
177-
getCountryName(["get", "adm0_l"]),
170+
maplibregl.Diplomat.getLocalizedCountryNameExpression(["get", "adm0_l"]),
178171
);
179172
```
180173

174+
> [!NOTE]
175+
> Use [`maplibregl.Diplomat.getGlobalStateForLocalization()`](#maplibregldiplomatgetglobalstateforlocalization) to populate the global state required by this expression, then call [`maplibregl.Map.prototype.localizeStyle()`](#maplibreglmapprototypelocalizestyle). Otherwise, this expression evaluates to the raw country code.
176+
177+
### `maplibregl.Diplomat.getGlobalStateForLocalization()`
178+
179+
Returns the global state that Diplomat needs to fully localize the style.
180+
181+
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.
182+
183+
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`](https://openmaptiles.org/schema/#boundary) layer. Otherwise, the user will see only ISO&nbsp;3166-1 alpha-3 codes, because OpenMapTiles only provides these codes instead of the full country name on either side of a boundary.
184+
185+
Parameters:
186+
187+
- **`locales`** (`string`): The locales for formatting the country names.
188+
- **`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.
189+
190+
Example:
191+
192+
```js
193+
style.state = maplibregl.getGlobalStateForLocalization(locales, { uppercaseCountryNames: true }),
194+
```
195+
181196
### `maplibregl.Diplomat.getLocales()`
182197

183198
Returns the languages that the user prefers.
@@ -188,9 +203,11 @@ Example:
188203
maplibregl.Diplomat.getLocales().includes("en");
189204
```
190205

191-
### `maplibregl.Map.prototype.localizeLayers()`
206+
### `maplibregl.Map.prototype.localizeStyle()`
192207

193-
Updates localizable variables at the top level of each layer's `text-field` expression based on the given locales.
208+
Updates each style layer's `text-field` value to match the given locales, upgrading any unlocalizable layer along the way.
209+
210+
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`](#maplibrediplomatlocalizednamewithlocalgloss) constant.
194211

195212
Parameters:
196213

@@ -199,18 +216,14 @@ Parameters:
199216
- **`options`** (`object`):
200217
- **`unlocalizedNameProperty`** (`string`): The name of the property holding the unlocalized name. `name` by default.
201218
- **`localizedNamePropertyFormat`** (`string`): "The format of properties holding localized names, where `$1` is replaced by an IETF language tag. `name:$1` by default.
202-
203-
> [!NOTE]
204-
> This method modifies the `layers` structure in place. If it comes from the return value of [`maplibregl.Map.prototype.getStyle()`](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#getstyle), you must manually synchronize the layers with the style afterwards by calling [`maplibregl.Map.prototype.setStyle()`](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#setstyle).
219+
- **`options.uppercaseCountryNames`** (`boolean`): Whether to write country names in all uppercase, respecting the locale’s case conventions.
205220

206221
Example:
207222

208223
```js
209-
const style = map.getStyle();
210-
map.localizeLayers(style.layers, locales, {
224+
map.localizeStyle(["eo"], {
211225
localizedNamePropertyFormat: "name_$1",
212226
});
213-
map.setStyle(style);
214227
```
215228

216229
## Caveats

example/index.js

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,29 @@ addEventListener("load", () => {
2323
"text-font": ["Americana"],
2424
},
2525
},
26+
{
27+
type: "symbol",
28+
id: "waterway-labels",
29+
source: "openmaptiles",
30+
"source-layer": "water_name",
31+
filter: ["==", ["geometry-type"], "LineString"],
32+
layout: {
33+
"symbol-placement": "line",
34+
"text-field": ["get", "name"],
35+
"text-font": ["Americana-Italic"],
36+
},
37+
},
38+
{
39+
type: "symbol",
40+
id: "waterbody-labels",
41+
source: "openmaptiles",
42+
"source-layer": "water_name",
43+
filter: ["!=", ["geometry-type"], "LineString"],
44+
layout: {
45+
"text-field": ["get", "name"],
46+
"text-font": ["Americana-Italic"],
47+
},
48+
},
2649
{
2750
type: "symbol",
2851
id: "place-labels",
@@ -33,6 +56,20 @@ addEventListener("load", () => {
3356
"text-font": ["Americana-Bold"],
3457
},
3558
},
59+
{
60+
type: "symbol",
61+
id: "boundary-edge-labels",
62+
source: "openmaptiles",
63+
"source-layer": "boundary",
64+
layout: {
65+
"symbol-placement": "line",
66+
"text-field": maplibregl.Diplomat.getLocalizedCountryNameExpression(
67+
["get", "adm0_l"],
68+
),
69+
"text-font": ["Americana"],
70+
"text-size": 10,
71+
},
72+
},
3673
],
3774
},
3875
});
@@ -51,10 +88,9 @@ addEventListener("load", () => {
5188
"text-field",
5289
maplibregl.Diplomat.localizedNameWithLocalGloss,
5390
);
54-
let locales = maplibregl.Diplomat.getLocales();
55-
let style = map.getStyle();
56-
map.localizeLayers(style.layers, locales);
57-
map.setStyle(style);
91+
map.localizeStyle(maplibregl.Diplomat.getLocales(), {
92+
uppercaseCountryNames: true,
93+
});
5894
});
5995

6096
addEventListener("hashchange", (event) => {
@@ -65,11 +101,9 @@ addEventListener("load", () => {
65101
new URL(event.newURL),
66102
);
67103
if (oldLanguage !== newLanguage) {
68-
let locales = maplibregl.Diplomat.getLocales();
69-
console.log(`Changed to ${locales}`);
70-
let style = map.getStyle();
71-
map.localizeLayers(style.layers, locales);
72-
map.setStyle(style);
104+
map.localizeStyle(maplibregl.Diplomat.getLocales(), {
105+
uppercaseCountryNames: true,
106+
});
73107
}
74108
});
75109
});

0 commit comments

Comments
 (0)