Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions app/components/landing/header/language-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
import i18next from 'i18next'
import { Globe } from 'lucide-react'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useFetcher, useLoaderData } from 'react-router'
import { Button } from '~/components/ui/button'
import { type loader } from '~/root'

export default function LanguageSelector() {
const data = useLoaderData<typeof loader>()
const fetcher = useFetcher()

const [locale, setLocale] = useState(data.locale || 'en')
// When loader locale changes (e.g. after login), sync state
useEffect(() => {
if (!data?.locale) return
const updateLang = async () => {
await i18next.changeLanguage(data.locale)
}
void updateLang()
setLocale(data.locale)
void (async () => {
try {
await i18next.changeLanguage(data.locale)
} catch (e) {

Check warning on line 19 in app/components/landing/header/language-selector.tsx

View workflow job for this annotation

GitHub Actions / ⬣ Lint

'e' is defined but never used
// Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler
}
})()
}, [data.locale])

const toggleLanguage = async () => {
const newLocale = (data?.locale ?? 'en') === 'en' ? 'de' : 'en' // Toggle between "en" and "de"
const toggleLanguage = () => {
const newLocale = locale === 'en' ? 'de' : 'en' // Toggle between "en" and "de"
setLocale(newLocale)
void i18next.changeLanguage(newLocale) // Change the language in the app
void fetcher.submit(
{ language: newLocale },
{ method: 'post', action: '/action/set-language' }, // Persist the new language
)
await i18next.changeLanguage(newLocale) // Change the language in the app
}

return (
Expand All @@ -35,7 +40,7 @@
className="hover:bg-transparent hover:text-black dark:hover:text-white"
>
<Globe />
{(data?.locale ?? 'en') === 'de' ? <p>DE</p> : <p>EN</p>}
{locale === 'de' ? <p>DE</p> : <p>EN</p>}
</Button>
)
}
196 changes: 114 additions & 82 deletions app/components/map/map.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,119 @@
import type { Map as MapboxMap, AnyLayer, MapboxEvent } from "mapbox-gl";
import { forwardRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { type MapProps, type MapRef, NavigationControl, Map as ReactMap } from "react-map-gl";
import {
type Map as MapboxMap,
type AnyLayer,
type MapboxEvent,
} from 'mapbox-gl'
import { forwardRef, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import {
type MapProps,
type MapRef,
NavigationControl,
Map as ReactMap,
} from 'react-map-gl'

const Map = forwardRef<MapRef, MapProps>(
({ children, mapStyle, ...props }, ref) => {
const [theme] = 'light'
const { i18n } = useTranslation()

const updateMapLanguage = useCallback(
(map: MapboxMap, locale: string): void => {
if (!map) return

const Map = forwardRef<MapRef, MapProps>(
(
{ children, mapStyle, ...props },
ref,
) => {
const [theme] = "light";
const [, i18n] = useTranslation();
const updateMapLanguage = (map: MapboxMap, locale: string) => {
if (!map) return;

const style = map.getStyle();
if (!style || !style.layers) return;

style.layers.forEach((layer: AnyLayer) => {
// Uses type guarding instead of casting to any
if (!("layout" in layer)) return;

const layout = layer.layout;
if (layout && typeof layout === "object" && 'text-field' in layout) {
map.setLayoutProperty(layer.id, 'text-field', [
'coalesce',
['get', `name_${locale}`],
['get', 'name'],
]);
}
});
};

const handleMapLoad = (event: MapboxEvent<undefined>) => {
updateMapLanguage(event.target as MapboxMap, i18n.language);
};

// Update language when it changes
useEffect(() => {
if (ref && typeof ref !== 'function' && ref.current) {
updateMapLanguage(ref.current.getMap(), i18n.language);
}
}, [i18n.language, ref]);

return (
<ReactMap
id="osem"
dragRotate={false}
initialViewState={{
longitude: 7.628202,
latitude: 51.961563,
zoom: 2,
}}
mapStyle={
mapStyle || (theme === "dark"
? "mapbox://styles/mapbox/dark-v11"
: "mapbox://styles/mapbox/streets-v12")
}
mapboxAccessToken={ENV.MAPBOX_ACCESS_TOKEN}
pitchWithRotate={false}
projection={{ name: "globe" }}
preserveDrawingBuffer
hash={true}
ref={ref}
style={{
width: "100%",
height: "100%",
position: "fixed",
top: 0,
left: 0,
}}
touchZoomRotate={false}
onLoad={handleMapLoad}
{...props}
>
{children}
<NavigationControl position="bottom-right" showCompass={false} />
</ReactMap>
);
},
);
const style = map.getStyle()
if (!style?.layers) return

const mapboxLocale = locale.split('-')[0]

style.layers.forEach((layer: AnyLayer) => {
if (!('layout' in layer) || !layer.layout) return

const layout = layer.layout as Record<string, unknown>
const textField = layout['text-field']
if (!textField) return

// Skip layers that don't have localized names
if (
layer.id.includes('shield') ||
layer.id.includes('road-number') ||
layer.id.includes('exit') ||
layer.id.includes('ref')
) {
return
}

try {
map.setLayoutProperty(layer.id, 'text-field', [
'coalesce',
['get', `name_${mapboxLocale}`],
['get', 'name_en'],
['get', 'name'],
])
} catch (e) {
console.warn(`Could not set text-field for layer ${layer.id}:`, e)
}
})
},
[],
)

const handleMapLoad = useCallback(
(event: MapboxEvent<undefined>) => {
updateMapLanguage(event.target as MapboxMap, i18n.language)
},
[updateMapLanguage, i18n.language],
)

// Update language when it changes
useEffect(() => {
if (ref && typeof ref !== 'function' && ref.current) {
const map = ref.current.getMap()
if (map.isStyleLoaded()) {
updateMapLanguage(map, i18n.language)
}
}
}, [i18n.language, ref, updateMapLanguage])

return (
<ReactMap
id="osem"
dragRotate={false}
initialViewState={{
longitude: 7.628202,
latitude: 51.961563,
zoom: 2,
}}
mapStyle={
mapStyle ||
(theme === 'dark'
? 'mapbox://styles/mapbox/dark-v11'
: 'mapbox://styles/mapbox/streets-v12')
}
mapboxAccessToken={ENV.MAPBOX_ACCESS_TOKEN}
pitchWithRotate={false}
projection={{ name: 'globe' }}
preserveDrawingBuffer
hash={true}
ref={ref}
style={{
width: '100%',
height: '100%',
position: 'fixed',
top: 0,
left: 0,
}}
touchZoomRotate={false}
onLoad={handleMapLoad}
{...props}
>
{children}
<NavigationControl position="bottom-right" showCompass={false} />
</ReactMap>
)
},
)

Map.displayName = "Map";
Map.displayName = 'Map'

export default Map;
export default Map
Loading