使用 i18n 更改语言时使用 react-leaflet 实时更新工具提示
Update in real time tooltip with react-leaflet when changing language with i18n
由于 react-leaflet,我目前正在显示一个带有 GeoJSON 组件的地图。我还在某些国家和城市上悬停时显示了一些工具提示(例如,当我将鼠标悬停在法国时,工具提示显示 "France")。我也在使用 i18n 进行国际化。
国际化适用于国家工具提示,它们是实时更新的。
我有一个函数 updateDisplay
,它在缩放更改时在国家的 GeoJson 组件或城市的标记列表之间切换。
问题是,当我切换语言时,整个页面都可以正常工作,但城市工具提示却不行。它们仅在我缩放时更新(因此在调用 updateDisplay 时)。
我会有预期的行为:无论缩放,我希望城市工具提示在我切换语言时实时更新。
我希望我已经说清楚了
这是我的代码:
/**
* Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
*/
export default function CustomMap(): ReactElement {
const { t }: { t: TFunction } = useTranslation();
const countryToString = (countries: string[]): string => countries.map(c => t(c)).join(", ");
// Contains the json containing the polygons of the countries
const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
let geoJson: JSX.Element = <GeoJSON
key='my-geojson'
data={data}
style={() => ({
color: '#4a83ec',
weight: 1,
fillColor: "#1a1d62",
fillOpacity: 0.25,
})}
onEachFeature={(feature: geojson.Feature<geojson.GeometryObject>, layer: Layer) => {
layer.on({
'mouseover': (e: LeafletMouseEvent) => {
const country = countries[e.target.feature.properties.adm0_a3];
layer.bindTooltip(countryToString(country.tooltip as string[]));
layer.openTooltip(country.latlng);
},
'mouseout': () => {
layer.unbindTooltip();
layer.closeTooltip();
},
});
}}
/>
// Contains a list of marker for the cities
const cityMarkers: JSX.Element[] = cities.map(
(
c: position,
i: number
) => {
return (
// Here are the tooltips that doesn't update in real time, when we switch language
// FIX ME
<Marker key={c.latlng.lat + c.latlng.lng} position={c.latlng}>
<Tooltip>{t(c.tooltip as string)}</Tooltip>
</Marker>
);
}
);
const [state, setState] = useState<state>({
zoom: 3,
display: geoJson,
});
// 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 cityMarkers;
} else {
return 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>
);
}
您也可以在这里查看:https://github.com/TheTisiboth/WebCV/blob/WIP/src/components/customMap.tsx
它在分支 WIP
您可以执行以下操作来解决此问题:
- 如果添加了标记,则创建一个布尔标志以保留在内存中
使用本机传单代码而不是 react'leaflet 的包装器在地图上添加标记。
- 如果添加了标记并且缩放 >= 4,则将标志设置为 true
- 如果 zoom < 4 删除标记以便能够显示国家,将标志设置为 false
更改语言时,如果缩放比例大于 4,并且添加了标记,请删除之前的标记,使用新工具提示添加新标记
您可以通过持有对地图实例的引用来实现所有这些。
这是您需要的全部代码,(部分城市,标记已删除):
import React, { useState, ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { Map, Marker, TileLayer, GeoJSON } from "react-leaflet";
import geoJsonData from "../assets/geoJsonData.json";
import { LatLngLiteral, Layer, LeafletMouseEvent } from "leaflet";
import geojson from "geojson";
import { TFunction } from "i18next";
import L from "leaflet";
interface position {
latlng: LatLngLiteral;
tooltip: string;
}
interface state {
markers: position[];
zoom: number;
display: position[] | any;
geoJson: JSX.Element;
countries: { [key: string]: position };
}
/**
* Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
*/
export default function CustomMap(): ReactElement {
const mapRef: any = React.useRef();
const { t, i18n }: { t: TFunction; i18n: any } = useTranslation();
const [markersAdded, setMarkersAdded] = useState(false);
i18n.on("languageChanged", (lng: any) => {
if (lng) {
const map = mapRef.current;
if (map && map.leafletElement.getZoom() >= 4 && markersAdded) {
map.leafletElement.eachLayer(function (layer: L.Layer) {
if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
});
state.markers.map((c: position, i: number) => {
L.marker(c.latlng)
.addTo(map.leafletElement)
.bindTooltip(t(c.tooltip));
});
}
}
});
// 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: "travel.germany",
},
CZE: {
latlng: {
lat: 49.667628,
lng: 15.326962,
},
tooltip: "travel.tchequie",
},
BEL: {
latlng: {
lat: 50.6402809,
lng: 4.6667145,
},
tooltip: "travel.belgium",
},
};
// List of position and tooltip for the cities Markers
const cities: position[] = [
{
latlng: {
lat: 48.13825988769531,
lng: 11.584508895874023,
},
tooltip: "travel.munich",
},
{
latlng: {
lat: 52.51763153076172,
lng: 13.40965747833252,
},
tooltip: "travel.berlin",
},
{
// greece
latlng: {
lat: 37.99076843261719,
lng: 23.74122428894043,
},
tooltip: "travel.athens",
},
{
// greece
latlng: {
lat: 37.938621520996094,
lng: 22.92695426940918,
},
tooltip: "travel.corinth",
},
];
// Contains the json containing the polygons of the countries
const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
let geoJson: JSX.Element = (
<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(t(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 {
const map = mapRef.current;
if (zoom >= 4) {
return state.markers.map((c: position, i: number) => {
console.log(t(c.tooltip));
if (map && !markersAdded) {
console.log(map.leafletElement);
L.marker(c.latlng)
.addTo(map.leafletElement)
.bindTooltip(t(c.tooltip));
setMarkersAdded(true);
}
});
} else {
map.leafletElement.eachLayer(function (layer: L.Layer) {
if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
});
setMarkersAdded(false);
return state.geoJson;
}
}
return (
<Map
ref={mapRef}
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>
);
}
英语:
"travel": {
"germany": "Munich, Berlin, Hambourg, Münster, Allemagne",
"munich": "Munchen",
"berlin": "Berlin",
"tchequie": "Tchéquie, Prague",
"belgium": "Belgique",
"athens": "Athènes",
"corinth": "Corinthe",
...
}
神父:
"travel": {
"germany": "Munich, Berlin, Hamburg, Münster, Germany",
"munich": "Munich",
"berlin": "Berlin",
"tchequie": "Czech Republic, Prague",
"belgium": "Belgium",
"athens": "Athens",
"corinth": "Corinth",
...
}
您可以通过分别重复使用标记删除代码块和标记添加代码块来使其更干净。
由于 react-leaflet,我目前正在显示一个带有 GeoJSON 组件的地图。我还在某些国家和城市上悬停时显示了一些工具提示(例如,当我将鼠标悬停在法国时,工具提示显示 "France")。我也在使用 i18n 进行国际化。 国际化适用于国家工具提示,它们是实时更新的。
我有一个函数 updateDisplay
,它在缩放更改时在国家的 GeoJson 组件或城市的标记列表之间切换。
问题是,当我切换语言时,整个页面都可以正常工作,但城市工具提示却不行。它们仅在我缩放时更新(因此在调用 updateDisplay 时)。
我会有预期的行为:无论缩放,我希望城市工具提示在我切换语言时实时更新。
我希望我已经说清楚了
这是我的代码:
/**
* Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
*/
export default function CustomMap(): ReactElement {
const { t }: { t: TFunction } = useTranslation();
const countryToString = (countries: string[]): string => countries.map(c => t(c)).join(", ");
// Contains the json containing the polygons of the countries
const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
let geoJson: JSX.Element = <GeoJSON
key='my-geojson'
data={data}
style={() => ({
color: '#4a83ec',
weight: 1,
fillColor: "#1a1d62",
fillOpacity: 0.25,
})}
onEachFeature={(feature: geojson.Feature<geojson.GeometryObject>, layer: Layer) => {
layer.on({
'mouseover': (e: LeafletMouseEvent) => {
const country = countries[e.target.feature.properties.adm0_a3];
layer.bindTooltip(countryToString(country.tooltip as string[]));
layer.openTooltip(country.latlng);
},
'mouseout': () => {
layer.unbindTooltip();
layer.closeTooltip();
},
});
}}
/>
// Contains a list of marker for the cities
const cityMarkers: JSX.Element[] = cities.map(
(
c: position,
i: number
) => {
return (
// Here are the tooltips that doesn't update in real time, when we switch language
// FIX ME
<Marker key={c.latlng.lat + c.latlng.lng} position={c.latlng}>
<Tooltip>{t(c.tooltip as string)}</Tooltip>
</Marker>
);
}
);
const [state, setState] = useState<state>({
zoom: 3,
display: geoJson,
});
// 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 cityMarkers;
} else {
return 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>
);
}
您也可以在这里查看:https://github.com/TheTisiboth/WebCV/blob/WIP/src/components/customMap.tsx
它在分支 WIP
您可以执行以下操作来解决此问题:
- 如果添加了标记,则创建一个布尔标志以保留在内存中
使用本机传单代码而不是 react'leaflet 的包装器在地图上添加标记。
- 如果添加了标记并且缩放 >= 4,则将标志设置为 true
- 如果 zoom < 4 删除标记以便能够显示国家,将标志设置为 false
更改语言时,如果缩放比例大于 4,并且添加了标记,请删除之前的标记,使用新工具提示添加新标记
您可以通过持有对地图实例的引用来实现所有这些。
这是您需要的全部代码,(部分城市,标记已删除):
import React, { useState, ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { Map, Marker, TileLayer, GeoJSON } from "react-leaflet";
import geoJsonData from "../assets/geoJsonData.json";
import { LatLngLiteral, Layer, LeafletMouseEvent } from "leaflet";
import geojson from "geojson";
import { TFunction } from "i18next";
import L from "leaflet";
interface position {
latlng: LatLngLiteral;
tooltip: string;
}
interface state {
markers: position[];
zoom: number;
display: position[] | any;
geoJson: JSX.Element;
countries: { [key: string]: position };
}
/**
* Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
*/
export default function CustomMap(): ReactElement {
const mapRef: any = React.useRef();
const { t, i18n }: { t: TFunction; i18n: any } = useTranslation();
const [markersAdded, setMarkersAdded] = useState(false);
i18n.on("languageChanged", (lng: any) => {
if (lng) {
const map = mapRef.current;
if (map && map.leafletElement.getZoom() >= 4 && markersAdded) {
map.leafletElement.eachLayer(function (layer: L.Layer) {
if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
});
state.markers.map((c: position, i: number) => {
L.marker(c.latlng)
.addTo(map.leafletElement)
.bindTooltip(t(c.tooltip));
});
}
}
});
// 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: "travel.germany",
},
CZE: {
latlng: {
lat: 49.667628,
lng: 15.326962,
},
tooltip: "travel.tchequie",
},
BEL: {
latlng: {
lat: 50.6402809,
lng: 4.6667145,
},
tooltip: "travel.belgium",
},
};
// List of position and tooltip for the cities Markers
const cities: position[] = [
{
latlng: {
lat: 48.13825988769531,
lng: 11.584508895874023,
},
tooltip: "travel.munich",
},
{
latlng: {
lat: 52.51763153076172,
lng: 13.40965747833252,
},
tooltip: "travel.berlin",
},
{
// greece
latlng: {
lat: 37.99076843261719,
lng: 23.74122428894043,
},
tooltip: "travel.athens",
},
{
// greece
latlng: {
lat: 37.938621520996094,
lng: 22.92695426940918,
},
tooltip: "travel.corinth",
},
];
// Contains the json containing the polygons of the countries
const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
let geoJson: JSX.Element = (
<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(t(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 {
const map = mapRef.current;
if (zoom >= 4) {
return state.markers.map((c: position, i: number) => {
console.log(t(c.tooltip));
if (map && !markersAdded) {
console.log(map.leafletElement);
L.marker(c.latlng)
.addTo(map.leafletElement)
.bindTooltip(t(c.tooltip));
setMarkersAdded(true);
}
});
} else {
map.leafletElement.eachLayer(function (layer: L.Layer) {
if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
});
setMarkersAdded(false);
return state.geoJson;
}
}
return (
<Map
ref={mapRef}
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>
);
}
英语:
"travel": {
"germany": "Munich, Berlin, Hambourg, Münster, Allemagne",
"munich": "Munchen",
"berlin": "Berlin",
"tchequie": "Tchéquie, Prague",
"belgium": "Belgique",
"athens": "Athènes",
"corinth": "Corinthe",
...
}
神父:
"travel": {
"germany": "Munich, Berlin, Hamburg, Münster, Germany",
"munich": "Munich",
"berlin": "Berlin",
"tchequie": "Czech Republic, Prague",
"belgium": "Belgium",
"athens": "Athens",
"corinth": "Corinth",
...
}
您可以通过分别重复使用标记删除代码块和标记添加代码块来使其更干净。