mapbox / mapbox/mapbox-gl-js

Add expression operators for locale matching (system languages)

Open
#6,197 9 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

cross-platform :tv: feature :green_apple:
Dominant language
TypeScript
Stars
12.4k
Forks
2.4k
PR merge metrics
No merged PRs in 30d

Description

There should be a simple way for the style author to specify that a `text-field` should be set to the `name_*` feature property that best fits the system’s preferred languages. Secondarily, it would be great if the most appropriate locale could be used on its own in expressions.

## Motivation

[Localizing a style’s labels](https://www.mapbox.com/help/change-language/) currently entails iterating over all the layers, manually replacing references to `name_*` feature properties within each `text-field` value. If these values are expressions, replacing the references can be an involved, recursive step. The iOS and macOS map SDKs have a built-in option, `MGLStyle.localizesLabels`, that applies these changes automatically based on the system language and region preferences. There’s [a plugin for GL JS](https://github.com/mapbox/mapbox-gl-language/) and a forthcoming plugin for the Android map SDK (mapbox/mapbox-plugins-android#74) that do likewise.

While this approach is effective, it operates at such a high level that the localizing code doesn’t have a good way to reason about the style author’s intentions. Should `{name} ({name_en})` be replaced by `{name_es} ({name_en})` or just `{name}`? The style’s author has no opportunity to react to changes that could radically alter the style’s appearance, for instance by increasing the font size when the system language is Chinese. Moreover, the localization feature implicitly opts the map into runtime styling–specific behaviors like disabling automatic style refreshes.

## Design

The style specification would be extended with two expression operators:

* `user-locales` takes no arguments and evaluates to an array of locale identifiers corresponding to the user’s preferences.
* `match-locales` has the signature `["match-locales", inputLocales, availableLocales]` and evaluates to the item in `availableLocales` (an unordered array of locale identifiers) that corresponds to the first item in `inputLocales` (e.g., `user-locales`) that matches one of `availableLocales`.

For the purposes of these operators, a locale identifier could include a language code, script code, or region code, or some combination thereof. I would be in favor of specifying BCP 47 as the locale identifier standard to follow.

In typical usage, a style author would opt into localization by setting `text-field` to a value such as:

```json
[
"let",
"streets-languages", ["ar", "de", "en", "es", "fr", "pt", "ru", "zh", "zh-Hans"],
[
"coalesce",
["concat", "name_", ["match-locales", ["user-locales"], ["var", "streets-languages"]]],
"name"
]
]
```

Meanwhile, `["at", 0, ["user-locales"]]` could be used on its own as part of a number formatting operator (#4119) and a case- and diacritic-folding string comparison operator (#4136).

## Design alternatives

It’s unfortunate that `streets-languages` would have to be hard-coded and duplicated on every symbol layer. However, I don’t see a good way around that unless the vector tile source formally declares its language-specific `name` fields (perhaps via mapbox/tilejson-spec#14) or we encapsulate that array in a third expression operator, `mapbox-streets-languages`.

It might be tempting to rely on `match` as an alternative to `match-locales`; however, locale identifier matching rules are rather complicated. For example, for the set of languages supported by the Streets source, `en-US` should resolve to `en`, `zh-TW` should resolve to `zh`, and `zh-Hans-TW` should resolve to `zh-Hans`.

## Implementation

* In GL JS, `user-locales` would be implemented by returning [`navigator.languages`](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages). iOS/macOS would use `+[NSLocale preferredLanguages]`.
* For `match-locales`, GL JS could use [locale-utils](https://github.com/mapbox/locale-utils/) for this purpose. iOS/macOS would use `+[NSBundle preferredLocalizationsFromArray:forPreferences:]`.

/ref https://github.com/mapbox/mapbox-gl-native/issues/10713#issuecomment-366382036
/cc @mapbox/gl-core @fabian-guerra @tobrun @langsmith @nickidlugash @bsudekum

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating GL JS's expression operator entry points and review locale-utils for matching BCP 47 identifiers; the issue names navigator.languages as the browser source for preferences. Done means user-locales and match-locales work in expressions, including the stated locale fallback behavior and style-localization use case.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend, internationalization
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.