Skip to content

Commit f7eee12

Browse files
authored
Make localization configurable through settings (#702)
Fixes #697 Make the map localization configurable. Followup on #680 and #691. No page reload needed. Even when changing browser language. The map will automatically reload. ## Settings ### Custom language <img width="838" height="258" alt="image" src="https://github.com/user-attachments/assets/7381074b-d9af-4b09-9e25-a18fa2b01634" /> <img width="331" height="221" alt="image" src="https://github.com/user-attachments/assets/8da421fc-ca91-464a-a854-a63624a8c425" /> ### Automatic <img width="838" height="258" alt="image" src="https://github.com/user-attachments/assets/354fc8f8-0518-4feb-b2f7-1734498d45b6" /> <img width="344" height="278" alt="image" src="https://github.com/user-attachments/assets/747c414d-c2b7-41c5-b5e6-dfdf376c56f7" /> ### Disabled <img width="826" height="182" alt="image" src="https://github.com/user-attachments/assets/b6487928-0f81-4128-9b0f-09d50c8b1631" /> <img width="304" height="254" alt="image" src="https://github.com/user-attachments/assets/2cc07497-7b1a-460d-9bf0-5aba2bf662e1" /> ### Browser language When the user changes browser language, the map is automatically reloaded with the chosen language (if the automatic localization is used). <img width="294" height="119" alt="image" src="https://github.com/user-attachments/assets/f5b5dcc0-9bbe-46ea-bb5b-968f0a2be4b1" />
1 parent 2facf52 commit f7eee12

File tree

2 files changed

+101
-11
lines changed

2 files changed

+101
-11
lines changed

proxy/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,29 @@ <h5 class="modal-title">Map configuration</h5>
205205
Control how stations are labeled on the map for zooms 5 to 10. From zoom 10 onwards, the names are always shown.
206206
</small>
207207
</div>
208+
<div class="mb-3">
209+
<label class="form-label">Localization</label>
210+
<div>
211+
<div class="form-check form-check-inline">
212+
<input class="form-check-input" type="radio" name="localization" id="localizationDisabled" value="label" onchange="disableLocalization(); localizationCustomLanguageControl.disabled = true">
213+
<label class="form-check-label" for="localizationDisabled">Disabled</label>
214+
</div>
215+
<div class="form-check form-check-inline">
216+
<input class="form-check-input" type="radio" name="localization" id="localizationAutomatic" value="name" checked onchange="automaticLocalization(); localizationCustomLanguageControl.disabled = true">
217+
<label class="form-check-label" for="localizationAutomatic">Automatic</label>
218+
</div>
219+
<div class="form-check form-check-inline" style="margin-right: 0.3rem">
220+
<input class="form-check-input" type="radio" name="localization" id="localizationCustom" value="name" onchange="customLocalization(localizationCustomLanguageControl.value); localizationCustomLanguageControl.disabled = false">
221+
<label class="form-check-label" for="localizationCustom">Custom: </label>
222+
</div>
223+
<div class="form-check form-check-inline" style="padding-left: 0; width: 5rem">
224+
<input type="text" class="form-control" name="localization-language" onchange="customLocalization(this.value)" id="localizationCustomLanguage" />
225+
</div>
226+
</div>
227+
<small class="form-text">
228+
Control whether localization is enabled and what language is used. When specifying a custom language, use the language code as specified in <a href="https://en.wikipedia.org/wiki/ISO_639-1" target="_blank">ISO_639-1</a>, for example <code>en</code> for English.
229+
</small>
230+
</div>
208231
</form>
209232
</div>
210233
</div>

proxy/js/ui.js

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ const themeDarkControl = document.getElementById('themeDark');
2424
const themeLightControl = document.getElementById('themeLight');
2525
const editorIDControl = document.getElementById('editorID');
2626
const editorJOSMControl = document.getElementById('editorJOSM');
27+
const localizationDisabledControl = document.getElementById('localizationDisabled');
28+
const localizationAutomaticControl = document.getElementById('localizationAutomatic');
29+
const localizationCustomControl = document.getElementById('localizationCustom');
30+
const localizationCustomLanguageControl = document.getElementById('localizationCustomLanguage');
2731
const backgroundMapContainer = document.getElementById('background-map');
2832
const legend = document.getElementById('legend');
2933
const legendMapContainer = document.getElementById('legend-map');
@@ -50,7 +54,16 @@ function getFlagEmoji(countryCode) {
5054
return String.fromCodePoint(...codePoints);
5155
}
5256

53-
const locale = new Intl.Locale(navigator.language);
57+
let locale = new Intl.Locale(navigator.language);
58+
window.addEventListener('languagechange', () => {
59+
locale = new Intl.Locale(navigator.language);
60+
console.info(`Browser language changed to ${locale.language}`);
61+
62+
const localization = configuration.localization ?? defaultConfiguration.localization;
63+
if (localization === 'automatic') {
64+
onStyleChange();
65+
}
66+
})
5467

5568
const icons = {
5669
railway: {
@@ -92,7 +105,10 @@ function registerLastSearchResults(results) {
92105

93106
function facilitySearchUrl(type, term, language) {
94107
const url = new URL(`${location.origin}/api/facility`)
95-
url.searchParams.set('lang', language)
108+
109+
if (language) {
110+
url.searchParams.set('lang', language)
111+
}
96112

97113
switch (type) {
98114
case 'name':
@@ -286,6 +302,19 @@ function showConfiguration() {
286302
stationLabelNameControl.checked = true
287303
}
288304

305+
const localization = configuration.localization ?? defaultConfiguration.localization;
306+
if (localization === 'automatic') {
307+
localizationAutomaticControl.checked = true;
308+
localizationCustomLanguageControl.disabled = true;
309+
} else if (localization === 'disabled') {
310+
localizationDisabledControl.checked = true;
311+
localizationCustomLanguageControl.disabled = true;
312+
} else if (localization === 'custom') {
313+
localizationCustomControl.checked = true;
314+
localizationCustomLanguageControl.disabled = false;
315+
}
316+
localizationCustomLanguageControl.value = configuration.localizationCustomLanguage ?? locale.language;
317+
289318
configurationBackdrop.style.display = 'block';
290319
}
291320

@@ -361,7 +390,7 @@ searchFacilitiesForm.addEventListener('submit', event => {
361390
event.preventDefault();
362391
const formData = new FormData(event.target);
363392
const data = Object.fromEntries(formData);
364-
searchForFacilities(data.type, data.term, locale.language)
393+
searchForFacilities(data.type, data.term, configuredLanguage())
365394
})
366395
searchMilestonesForm.addEventListener('submit', event => {
367396
event.preventDefault();
@@ -648,6 +677,33 @@ function onStationLabelChange(stationlabel) {
648677
}
649678
}
650679

680+
function disableLocalization() {
681+
updateConfiguration('localization', 'disabled');
682+
onStyleChange();
683+
}
684+
685+
function automaticLocalization() {
686+
updateConfiguration('localization', 'automatic');
687+
onStyleChange();
688+
}
689+
690+
function customLocalization(language) {
691+
updateConfiguration('localization', 'custom');
692+
updateConfiguration('localizationCustomLanguage', language);
693+
onStyleChange();
694+
}
695+
696+
function configuredLanguage() {
697+
const localization = configuration.localization ?? defaultConfiguration.localization;
698+
if (localization === 'automatic') {
699+
return locale.language;
700+
} else if (localization === 'disabled') {
701+
return null;
702+
} else if (localization === 'custom') {
703+
return configuration.localizationCustomLanguage;
704+
}
705+
}
706+
651707
function resolveTheme(configuredTheme) {
652708
return configuredTheme === 'system'
653709
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
@@ -937,7 +993,8 @@ const defaultConfiguration = {
937993
theme: 'system',
938994
editor: 'id',
939995
view: {},
940-
stationLowZoomLabel: 'label'
996+
stationLowZoomLabel: 'label',
997+
localization: 'automatic',
941998
};
942999
let configuration = readConfiguration(localStorage);
9431000
configuration = migrateConfiguration(localStorage, configuration);
@@ -1045,13 +1102,17 @@ function rewriteStylePathsToOrigin(style) {
10451102
}
10461103

10471104
// Rewrite source URLs to append the language query parameter
1048-
function addLanguageToSupportedSources(style) {
1105+
function addLanguageToSupportedSources(style, language) {
10491106
style.sources = Object.fromEntries(
10501107
Object.entries(style.sources)
10511108
.map(([key, source]) => {
10521109
if (source && source.url && ((source.metadata ?? {}).supports ?? []).includes('language')) {
10531110
const parsedUrl = new URL(source.url)
1054-
parsedUrl.searchParams.set('lang', locale.language)
1111+
1112+
if (language) {
1113+
parsedUrl.searchParams.set('lang', language)
1114+
}
1115+
10551116
return [
10561117
key,
10571118
{
@@ -1086,28 +1147,31 @@ function toggleHillShadeLayer(style) {
10861147
}
10871148

10881149
let lastSetMapStyle = null;
1089-
const onStyleChange = () => {
1150+
let lastSetMapLanguage = null;
1151+
function onStyleChange() {
10901152
const supportsDate = knownStyles[selectedStyle].styles.date;
10911153
const dateActive = supportsDate && dateControl.active;
10921154
const mapStyle = dateActive
10931155
? knownStyles[selectedStyle].styles.date
10941156
: knownStyles[selectedStyle].styles.default
1157+
const language = configuredLanguage();
10951158

1096-
if (mapStyle !== lastSetMapStyle) {
1097-
lastSetMapStyle = mapStyle;
1098-
1159+
if (mapStyle !== lastSetMapStyle || language != lastSetMapLanguage) {
10991160
// Change styles
11001161
map.setStyle(mapStyles[mapStyle], {
11011162
validate: false,
11021163
transformStyle: (previous, next) => {
11031164
rewriteStylePathsToOrigin(next)
1104-
addLanguageToSupportedSources(next)
1165+
addLanguageToSupportedSources(next, language)
11051166
rewriteGlobalStateDefaults(next)
11061167
toggleHillShadeLayer(next)
11071168
return next;
11081169
},
11091170
});
1171+
}
11101172

1173+
if (mapStyle !== lastSetMapStyle) {
1174+
// Change legend styles
11111175
legendMap.setStyle(legendStyles[mapStyle], {
11121176
validate: false,
11131177
// Do not calculate a diff because of the large structural layer differences causing a blocking performance hit
@@ -1127,6 +1191,9 @@ const onStyleChange = () => {
11271191
dateControl.hide();
11281192
}
11291193

1194+
lastSetMapStyle = mapStyle;
1195+
lastSetMapLanguage = language;
1196+
11301197
onPageParametersChange();
11311198
}
11321199

0 commit comments

Comments
 (0)