0
votes

I am currently displaying a Map, thanks to react-leaflet, with a GeoJSON Component. I'm also displaying some tooltips on hover over some countries (for example, when I hover France, a tooltip display "France"). I'm also using i18n for internationalization. The problem is, that when i'm switching languages, it works fine for the whole page, but not for these tooltips.

I think the probleme is with the function onEachFeature, which is called only one time, when the JSX Element GeoJSON is created, so it does not update when we switch languages. I think so, because the tooltips are in the right language when they appears (if the language is initially set to French, the tooltips are in french, if the language is set to english, they are set to english). But they simply don't update when we change the language after the initialization of the GeoJSON component.

I hope I've made myself clear

Here are my interfaces :

interface position {
  latlng: LatLngLiteral,
  tooltip: string
}

interface state {
  markers: position[],
  zoom: number,
  display: position[] | any,
  geoJson: any,
  countries: { [key: string]: position }
}

Here it is what is at the beginning of my component:

// t is the translation function, that should translate in the selected language. It is dynamic, it means that when we switch languages, it automatically update the translated string
const { t }: { t: TFunction } = useTranslation();

const countryToString = (countries: string[]): string => countries.join(", ");
  // List of position and label of tooltip for the GeoJson object, for each country
const countries: { [key: string]: position } = {
    DEU: {
      latlng: {
        lat: 51.0834196,
        lng: 10.4234469,
      },
      tooltip: countryToString([
        t("travel.germany.munich"),
        t("travel.germany.berlin"),
        t("travel.germany.hamburg"),
        t("travel.germany.munster"),
        t("travel.germany.country"),
      ])
    },
    CZE: {
      latlng: {
        lat: 49.667628,
        lng: 15.326962,
      },
      tooltip: countryToString([
        t("travel.tchequie.prague"),
        t("travel.tchequie.country"),
      ])
    },
...
}

// List of position and tooltip for the cities Markers
const cities: position[] = [
    {
      latlng: {
        lat: 48.13825988769531,
        lng: 11.584508895874023,
      },
      tooltip: t("travel.germany.munich"),
    },
    {
      latlng: {
        lat: 52.51763153076172,
        lng: 13.40965747833252,
      },
      tooltip: t("travel.germany.berlin"),
    },
...

I've got a country list, that i display thanks to the GeoJSON Component, and a city list, that i display thanks to a Marker list.

And then the functionnal part:


  // Contains the json containing the polygons of the countries
  const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
  let geoJson: any = <GeoJSON
    key='my-geojson'
    data={data}
    style={() => ({
      color: '#4a83ec',
      weight: 1,
      fillColor: "#1a1d62",
      fillOpacity: 0.25,
    })}

    // PROBLEM : does not update the tooltips when we switch languages
    // FIX ME
    onEachFeature={(feature: geojson.Feature<geojson.GeometryObject>, layer: Layer) => {
      layer.on({
        'mouseover': (e: LeafletMouseEvent) => {
          const country = state.countries[e.target.feature.properties.adm0_a3];
          layer.bindTooltip(country.tooltip);
          layer.openTooltip(country.latlng);
        },
        'mouseout': () => {
          layer.unbindTooltip();
          layer.closeTooltip();
        },
      });
    }}
  />


  const [state, setState] = useState<state>({
    markers: cities,
    zoom: 3,
    geoJson: geoJson,
    display: geoJson,
    countries: countries
  });

  // Update on zoom change
  function onZoom(e: LeafletMouseEvent): void {
    const zoom = e.target._zoom;
    const newDisplay = updateDisplay(zoom);
    setState({
      ...state,
      zoom,
      display: newDisplay,
    });
  }

  // Called on every zoom change, in order to display either the GeoJson, or the cities Marker
  function updateDisplay(zoom: number): Marker[] | any {
    if (zoom >= 4) {
      return (state.markers.map(
        (
          c: position,
          i: number
        ) => {
          return (
            <Marker key={c.latlng.lat + c.latlng.lng} position={c.latlng}>
              <Tooltip>{c.tooltip}</Tooltip>
            </Marker>
          );
        }
      ));
    } else {
      return state.geoJson;
    }
  }

  return (
    <Map
      style={{ height: "500px" }}
      center={[54.370138916189596, -29.918133437500003]}
      zoom={state.zoom}
      onZoomend={onZoom}
    >
      <TileLayer url="https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw" />
      {state.display}
    </Map>
  );

If you want to see the whole code, you can see it there : https://github.com/TheTisiboth/WebCV/blob/WIP/src/components/customMap.tsx

1

1 Answers

1
votes

Apparently the plugin cannot handle multiple joined strings in the form of "travel.italy.roma, travel.italy.naples, travel.italy.pompei, travel.italy.country concatenated and moreover and more importantly it seems that you need to place the function call in the markup (not 100% sure why cause I am not familiar with this plugin - maybe there is an alternative to that).

 <Map
  style={{ height: "500px" }}
  center={[54.370138916189596, -29.918133437500003]}
  zoom={state.zoom}
  onZoomend={onZoom}
>
  <TileLayer url='https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw' />
  {state.markers.map(
    (c: position, i: number): ReactElement => {
      if (state.zoom >= 5) {
        return (
          <Marker key={i} position={[c.lat, c.lon]}>
            <Tooltip>{t(c.tooltip)}</Tooltip>
          </Marker>
        );
      } else {
        return (
          <CircleMarker
            key={i}
            center={[c.lat, c.lon]}
            color='red'
            radius={state.radius}
          >
            <Tooltip>{t(c.tooltip)}</Tooltip>
          </CircleMarker>
        );
      }
    }
  )}
</Map>

because if you log the countries variable you will see that there is no reference to the function but only printed strings. So the string will be loaded for the first time only and won't be changed because there is no function to call.

So a more ideal way to handle the translation references would be to have a form like this:

English:

"travel": {
    "germany": "Munich, Berlin, Hamburg, Münster, Germany",
    "munich": "Munich",
    "tchequie": "Czech Republic, Prague",

    ...
}

French:

"travel": {
       "germany": "Munich, Berlin, Hambourg, Münster, Allemagne",
       "munich": "Munchen",

       "tchequie": "Tchéquie, Prague",

     ...
}

and in youur custom map:

  let countries: position[] = [
    {
      // germany
      lat: 51.0834196,
      lon: 10.4234469,
      tooltip: "travel.germany",
    },
    {
      // tchequie
      lat: 49.667628,
      lon: 15.326962,
      tooltip: "travel.tchequie",
    },
   ...
   ]

Update: Your cities should be like this:

 let cities: position[] = [
    {
      lat: 48.13825988769531,
      lon: 11.584508895874023,
      tooltip: "travel.munich",
    },
    ...
  ]