Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 14 additions & 1 deletion src/components/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import OSMIcon from '@/images/osm_icon.png';
import { ToolButton } from './parts/tool-button';

import { MapStyleControl } from './map-style-control';
import { updateMapLabels } from './map-language-control';
import { getInitialMapStyle, getCustomStyle, getMapStyleUrl } from './utils';
import {
CLICK_DELAY_MS,
Expand Down Expand Up @@ -787,7 +788,19 @@ export const MapComponent = () => {
{...viewState}
onMove={(evt) => setViewState(evt.viewState)}
onMoveEnd={handleMoveEnd}
onLoad={() => setMapReady(true)}
onLoad={() => {
setMapReady(true);
const lang = localStorage.getItem('directions_language');
if (lang) {
updateMapLabels(mapRef.current?.getMap(), lang);
}
}}
onStyleData={() => {
const lang = localStorage.getItem('directions_language');
if (lang) {
updateMapLabels(mapRef.current?.getMap(), lang);
}
}}
onClick={handleMapClick}
onDblClick={handleMapDblClick}
onContextMenu={handleMapContextMenu}
Expand Down
56 changes: 56 additions & 0 deletions src/components/map/map-language-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
function getMapLanguageKey(directionsLanguage: string): string | null {
const base = directionsLanguage.split('-')[0];
if (base === 'en' || base === 'de') {
Comment on lines +2 to +3
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return base;
}
return null;
}

export function updateMapLabels(
map: maplibregl.Map | undefined,
directionsLanguage: string
) {
if (!map) return;

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

const langKey = getMapLanguageKey(directionsLanguage);

for (const layer of style.layers) {
if (layer.type !== 'symbol') continue;

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

// Shortbread: simple string like "{name}", "{name_en}", "{name_de}"
if (typeof textField === 'string') {
if (!textField.match(/\{name(_[a-z]{2})?\}/)) continue;

const newTextField = langKey ? `{name_${langKey}}` : '{name}';
map.setLayoutProperty(layer.id, 'text-field', newTextField);
continue;
}

// Alidade Smooth: expression-based, e.g. ["get", "name:latin"]
// or ["coalesce", ["get", "name:en"], ["get", "name:latin"]]
if (Array.isArray(textField)) {
const json = JSON.stringify(textField);
if (!json.includes('"name:') && !json.includes('"name"')) continue;
Comment on lines +25 to +38
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these ifs look a bit "smelly". I'm not sure if somehow making this more dependent on the MVT endpoint instead of field types (which is still a provider dependency, any MVT source can do anything to support languages) or name or whatever would make it more or less "smelly". wdyt @mustaphaturhan ?


if (langKey) {
map.setLayoutProperty(layer.id, 'text-field', [
'coalesce',
['get', `name:${langKey}`],
['get', 'name:latin'],
['get', 'name'],
]);
} else {
map.setLayoutProperty(layer.id, 'text-field', [
'coalesce',
['get', 'name:latin'],
['get', 'name'],
]);
}
}
}
}
19 changes: 13 additions & 6 deletions src/components/settings-panel/settings-panel.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ vi.mock('@/stores/common-store', () => ({
}),
}));

vi.mock('react-map-gl/maplibre', () => ({
useMap: vi.fn(() => ({ mainMap: { getMap: vi.fn() } })),
}));

vi.mock('@/components/map/map-language-control', () => ({
updateMapLabels: vi.fn(),
}));

vi.mock('@/hooks/use-directions-queries', () => ({
useDirectionsQuery: vi.fn(() => ({
refetch: mockRefetchDirections,
Expand Down Expand Up @@ -558,16 +566,15 @@ describe('SettingsPanel', () => {
});

describe('Language Picker', () => {
it('should render Directions Language section when activeTab is directions', () => {
it('should render Language section on all tabs', () => {
renderWithQueryClient(<SettingsPanel />);
expect(screen.getByText('Directions Language')).toBeInTheDocument();
expect(screen.getByText('Language')).toBeInTheDocument();
expect(screen.getAllByText('Language').length).toBeGreaterThan(0);
});

it('should not render Directions Language section when activeTab is isochrones', () => {
it('should render Language section when activeTab is isochrones', () => {
mockUseParams.mockReturnValue({ activeTab: 'isochrones' });
renderWithQueryClient(<SettingsPanel />);
expect(screen.queryByText('Directions Language')).not.toBeInTheDocument();
expect(screen.getAllByText('Language').length).toBeGreaterThan(0);
});

it('should use system locale when no language is stored', () => {
Expand Down Expand Up @@ -598,7 +605,7 @@ describe('SettingsPanel', () => {

it('should render language description in help tooltip', () => {
renderWithQueryClient(<SettingsPanel />);
expect(screen.getByText('Language')).toBeInTheDocument();
expect(screen.getAllByText('Language').length).toBeGreaterThan(0);
});
});
});
40 changes: 21 additions & 19 deletions src/components/settings-panel/settings-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getDirectionsLanguage,
setDirectionsLanguage,
} from '@/utils/directions-language';
import { updateMapLabels } from '@/components/map/map-language-control';
import type { PossibleSettings } from '@/components/types';

import { SliderSetting } from '@/components/ui/slider-setting';
Expand All @@ -33,6 +34,7 @@ import {
Settings2,
} from 'lucide-react';
import { useParams, useSearch } from '@tanstack/react-router';
import { useMap } from 'react-map-gl/maplibre';
import { useDirectionsQuery } from '@/hooks/use-directions-queries';
import { useIsochronesQuery } from '@/hooks/use-isochrones-queries';
import { CollapsibleSection } from '@/components/ui/collapsible-section';
Expand All @@ -50,6 +52,7 @@ export const SettingsPanel = () => {
const resetSettings = useCommonStore((state) => state.resetSettings);
const toggleSettings = useCommonStore((state) => state.toggleSettings);
const [copied, setCopied] = useState(false);
const { mainMap: map } = useMap();
const { refetch: refetchDirections } = useDirectionsQuery();
const { refetch: refetchIsochrones } = useIsochronesQuery();

Expand All @@ -66,9 +69,10 @@ export const SettingsPanel = () => {
const newLanguage = value as DirectionsLanguage;
setDirectionsLanguage(newLanguage);
setLanguage(newLanguage);
updateMapLabels(map?.getMap(), newLanguage);
refetchDirections();
},
[refetchDirections]
[refetchDirections, map]
);

const handleMakeRequest = useCallback(() => {
Expand Down Expand Up @@ -144,24 +148,22 @@ export const SettingsPanel = () => {
<div className="px-3 space-y-3">
<ServerSettings />

{activeTab === 'directions' && (
<CollapsibleSection
title="Directions Language"
icon={Languages}
open={languageSettingsOpen}
onOpenChange={setLanguageSettingsOpen}
>
<SelectSetting
id="directions-language"
label="Language"
description="The language used for turn-by-turn navigation instructions"
placeholder="Select Language"
value={language}
options={[...languageOptions]}
onValueChange={handleLanguageChange}
/>
</CollapsibleSection>
)}
<CollapsibleSection
title="Language"
icon={Languages}
open={languageSettingsOpen}
onOpenChange={setLanguageSettingsOpen}
>
<SelectSetting
id="directions-language"
label="Language"
description="The language used for navigation instructions and map labels"
placeholder="Select Language"
value={language}
options={[...languageOptions]}
onValueChange={handleLanguageChange}
/>
</CollapsibleSection>

{hasProfileSettings && (
<CollapsibleSection
Expand Down
8 changes: 6 additions & 2 deletions src/utils/directions-language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ export function getDirectionsLanguage(): DirectionsLanguage {
const stored = localStorage.getItem(DIRECTIONS_LANGUAGE_STORAGE_KEY);

if (!stored) {
return getSystemLanguage();
const language = getSystemLanguage();
localStorage.setItem(DIRECTIONS_LANGUAGE_STORAGE_KEY, language);
return language;
}

const isValid = languageOptions.some((opt) => opt.value === stored);

if (!isValid) {
return getSystemLanguage();
const language = getSystemLanguage();
localStorage.setItem(DIRECTIONS_LANGUAGE_STORAGE_KEY, language);
return language;
}

return stored as DirectionsLanguage;
Expand Down
Loading