Search for content

Creating a Localized Map App (OSM)

In this tutorial we extend the Map App code sample to display a localized map based on OSM vector tiles from PTV Developer.


Showing a localized map in a browser

All supported languages are listed in the Languages concept. Localized labels are available for countries, states, cities, roads, buildings and backgrounds. This example will show how to display multi names by displaying two lines with the localized label and the original label below.

Map snapshot of Brussels with localized names

HTML code

As an example we provide a simple index.html file where we instantiate the MapLibre renderer and set the displayed language. Use BCP47 country codes in lower case to display the desired language:

<!DOCTYPE html>
   <meta charset='utf-8' />
   <title>Vector Map (OSM) with localized labels</title>
   <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
   <script type="text/javascript" src=""></script>
   <link rel="stylesheet" type="text/css" href="">
       body { margin:0; padding:0; }
       #map { position:absolute; top:0; bottom:0; width:100%; }
<div id='map'></div>
    // Use map.setLayoutProperty() to set the value of a layout property in a style layer.
    // The three arguments are the id of the layer, the name of the layout property,
    // and the new property value.
    function setMultiNameLabel(labelName) {
        map.setLayoutProperty(labelName, 'text-field',
                    ['==', ['get', 'name'], ['get','name_' + language]], // if names are same, show only one
                    ['get', 'name'],
                    ['!=', ['get','name_' + language], null], // if there is a translated name, show both names (the native name is shown a bit smaller)
                            ['get','name_' + language],
                            { 'font-scale': 1.0 },
                            ['get', 'name'],
                            { 'font-scale': 0.8, 'text-font': ['literal', ['Noto Sans Italic']] }
                    ['get', 'name']  // fallback
    function setNameLabel(layerName, foreign) {
        if (foreign) {
            map.setLayoutProperty(layerName, 'text-field',
                ['get','name_' + language],  // if you find a translated name, show it
                ['get', 'name']
        } else {
            map.setLayoutProperty(layerName, 'text-field', ['get', 'name']);

    function alterNames() {

        if (language == "native") {
            setNameLabel('LBL_Country_Big', false);
            setNameLabel('LBL_Country_Medium', false);
            setNameLabel('LBL_Country_Small', false);
            setNameLabel('LBL_State', false);
            setNameLabel('LBL_CityMajorCapital', false);
            setNameLabel('LBL_CityMajorVeryLarge', false);
            setNameLabel('LBL_CityMajorLarge', false);
            setNameLabel('LBL_CityMinorLarge', false);
            setNameLabel('LBL_CityMajorVillage', false);
            setNameLabel('LBL_CityMinorVillage', false);

            setNameLabel('LBL_Road', false);
            setNameLabel('LBL_Water', false);
            setNameLabel('LBL_Water_Linear', false);
            setNameLabel('LBL_WoodlandArea', false);
            setNameLabel('LBL_Park_State', false);
            setNameLabel('LBL_BuiltupArea', false);
        } else {

            if (showMultiNames) {
            } else {
                setNameLabel('LBL_Country_Big', true);
                setNameLabel('LBL_Country_Medium', true);
                setNameLabel('LBL_Country_Small', true);
                setNameLabel('LBL_State', true);
                setNameLabel('LBL_CityMajorCapital', true);
                setNameLabel('LBL_CityMajorVeryLarge', true);
                setNameLabel('LBL_CityMajorLarge', true);
                setNameLabel('LBL_CityMinorLarge', true);
                setNameLabel('LBL_CityMajorVillage', true);
                setNameLabel('LBL_CityMinorVillage', true);
                setNameLabel('LBL_Road', true);
                setNameLabel('LBL_Water', true);
                setNameLabel('LBL_Water_Linear', true);
                setNameLabel('LBL_WoodlandArea', true);
                setNameLabel('LBL_Park_State', true);
                setNameLabel('LBL_BuiltupArea', true);

        true // Lazy load the plugin

    var apiKey = '[YOUR_API_KEY]';
    var map = new maplibregl.Map({
        // You need a customAttribution if you do not use map styles provided by PTV or if you have to add additional copyright attribution.
        // attributionControl: true,
        // customAttribution: '©2023, PTV Group, OpenStreetMap contributors',
        container: 'map',
        zoom: 11,
        pitch: 0,
        minZoom: 2,
        center: [8.4055677, 49.0070036],
        antialias: true,
        hash: true,
        // you can also use your own style "https://<your_servername>/style.json"
        style: "",
        transformRequest: (url, resourceType) => {
            if (resourceType === 'Tile' && url.startsWith('')) {
                return {
                    url: url + '?apiKey=' + apiKey

    map.addControl(new maplibregl.NavigationControl());
    var showMultiNames = true;

    //use 'native' or the desired language code based on BCP47 country codes in lower case.
    //a list of supported languages can be found here:
    var language = "native";
    map.on('styledata', function() {

    map.addControl(new maplibregl.GeolocateControl({
        positionOptions: {
            enableHighAccuracy: true
            trackUserLocation: true