Skip to content
Merged
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
11 changes: 11 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ cat > /usr/share/nginx/html/config.json << EOF
}
EOF

# Inject OSRM_ENVIRONMENT into index.html meta tag to signal client to load config.json
if [ -f /usr/share/nginx/html/index.html ]; then
TMPFILE=$(mktemp)
awk -v env="$OSRM_ENVIRONMENT" '{
if ($0 ~ /<meta name="osrm-environment"/) {
sub(/content="[^"]*"/, "content=\"" env "\"")
}
print
}' /usr/share/nginx/html/index.html > "$TMPFILE" && mv "$TMPFILE" /usr/share/nginx/html/index.html || true
Comment on lines +64 to +69
fi

# Execute the default command (nginx) or any command passed to the container
if [ "$#" -eq 0 ]; then
exec nginx -g "daemon off;"
Expand Down
9 changes: 8 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<link rel='stylesheet' href="css/leaflet.css" />
<link rel="stylesheet" href="css/fonts.css" />
<link href='css/site.css' rel='stylesheet' />
<meta name="osrm-environment" content="">

</head>
<body>
Expand Down Expand Up @@ -59,7 +60,13 @@
});
}

loadConfig().then(loadBundle);
var envMeta = document.querySelector('meta[name="osrm-environment"]');
if (envMeta && envMeta.content === 'docker') {
loadConfig().then(loadBundle);
} else {
// Not running in Docker — skip fetching config.json to avoid noisy 404s
loadBundle();
}
})();
</script>
</body>
Expand Down
64 changes: 62 additions & 2 deletions src/leaflet_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,71 @@ function getZoom() {
return parsedZoom;
}

// Get language from config
// Get language, prefer browser settings when available; fallback to 'en'.
// Precedence (effective): URL param (handled in index.js) > browser language > 'en'
function getLanguage() {
return config.OSRM_LANGUAGE || 'en';
try {
// Read runtime config each time (honor OSRM_LANGUAGE when set at runtime)
var currentConfig = (typeof window !== 'undefined' ? window.osrmConfig : null) || {};
var localization = require('./localization');
var languages = localization.getLanguages();

function resolveCandidate(candidate) {
if (!candidate) return undefined;
candidate = String(candidate).trim();
// exact match (case-sensitive)
if (localization.get(candidate)) return candidate;

// case-insensitive exact match against available keys (e.g., pt-br -> pt-BR)
var lower = candidate.toLowerCase();
var keys = Object.keys(languages);
for (var k = 0; k < keys.length; k++) {
if (keys[k].toLowerCase() === lower) return keys[k];
}

// primary subtag fallback (e.g., en-US -> en)
var primary = candidate.split(/[-_]/)[0];
if (!primary) return undefined;
if (localization.get(primary)) return primary;
var lowerPrimary = primary.toLowerCase();
for (var j = 0; j < keys.length; j++) {
if (keys[j].toLowerCase() === lowerPrimary) return keys[j];
}
return undefined;
}

if (currentConfig.OSRM_LANGUAGE) {
var resolved = resolveCandidate(currentConfig.OSRM_LANGUAGE);
return resolved || currentConfig.OSRM_LANGUAGE;
}

if (typeof window !== 'undefined' && window.navigator) {
var nav = window.navigator;
var candidates = [];

if (Array.isArray(nav.languages)) {
Comment on lines +269 to +273
candidates = candidates.concat(nav.languages);
}
if (nav.language) candidates.push(nav.language);
if (nav.userLanguage) candidates.push(nav.userLanguage); // IE fallback

for (var i = 0; i < candidates.length; i++) {
var lang = candidates[i];
if (!lang) continue;
var resolvedLang = resolveCandidate(lang);
if (resolvedLang) return resolvedLang;
}
}
} catch (e) {
// Ignore detection errors and fall back to default
console.warn('Error detecting browser language:', e);
}

// Fallback to English when no browser language matches
return 'en';
}


// Get default layer from config
function getDefaultLayer() {
return config.OSRM_DEFAULT_LAYER || 'streets';
Expand Down
21 changes: 19 additions & 2 deletions test/entrypoint.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const { execFileSync } = require('child_process');

const entrypointPath = path.join(__dirname, '..', 'docker', 'entrypoint.sh');

function generateConfig(envOverrides) {
function generateConfig(envOverrides, options) {
options = options || {};
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'osrm-entrypoint-'));
const outputDir = path.join(tempDir, 'usr', 'share', 'nginx', 'html');
const tempEntrypointPath = path.join(tempDir, 'entrypoint.sh');
Expand All @@ -19,6 +20,10 @@ function generateConfig(envOverrides) {
);
fs.chmodSync(tempEntrypointPath, 0o755);

if (options.indexHtml) {
fs.writeFileSync(path.join(outputDir, 'index.html'), options.indexHtml, 'utf8');
}

try {
execFileSync(tempEntrypointPath, ['true'], {
env: {
Expand All @@ -28,7 +33,19 @@ function generateConfig(envOverrides) {
stdio: 'pipe'
});

return JSON.parse(fs.readFileSync(path.join(outputDir, 'config.json'), 'utf8'));
const config = JSON.parse(fs.readFileSync(path.join(outputDir, 'config.json'), 'utf8'));

if (options.indexHtml) {
let rewritten = null;
try {
rewritten = fs.readFileSync(path.join(outputDir, 'index.html'), 'utf8');
} catch (e) {
// ignore
}
return { config: config, indexHtml: rewritten };
}

return config;
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
Expand Down
61 changes: 57 additions & 4 deletions test/leaflet_options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,20 +198,73 @@ describe('leaflet_options — runtime configuration overrides', () => {
});
});

describe('OSRM_LANGUAGE override', () => {
test('uses custom language when provided', () => {
describe('language precedence (URL > browser > en)', () => {
test('honors OSRM_LANGUAGE runtime override', () => {
global.window = { osrmConfig: { OSRM_LANGUAGE: 'de' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('de');
delete global.window;
});

test('defaults to en when language not provided', () => {
global.window = { osrmConfig: {} };
test('uses browser language exact match', () => {
global.window = { navigator: { languages: ['de'], language: 'de' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('de');
delete global.window;
});

test('falls back to navigator.language when navigator.languages absent', () => {
global.window = { navigator: { language: 'de' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('de');
delete global.window;
});

test('uses primary subtag when regional locale provided (en-US -> en)', () => {
global.window = { navigator: { languages: ['en-US'], language: 'en-US' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('en');
delete global.window;
});
Comment on lines +209 to +228

test('prefers first candidate in navigator.languages array', () => {
global.window = { navigator: { languages: ['fr-CA', 'de'], language: 'fr-CA' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('fr');
delete global.window;
});

test('matches exact regional variant when available (pt-BR)', () => {
global.window = { navigator: { languages: ['pt-BR'], language: 'pt-BR' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('pt-BR');
delete global.window;
});

test('case-insensitive regional tag (pt-br)', () => {
global.window = { navigator: { languages: ['pt-br'], language: 'pt-br' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('pt-BR');
delete global.window;
});

test('falls back to English when no supported browser languages', () => {
global.window = { navigator: { languages: ['xx','yy'], language: 'xx' } };
const leafletOptions = require('../src/leaflet_options');
expect(leafletOptions.defaultState.language).toBe('en');
delete global.window;
});

test('URL param (hl) takes precedence over browser default when merged', () => {
// Simulate browser default 'en' but URL param asks for 'de'
global.window = { navigator: { languages: ['en'], language: 'en' } };
const leafletOptions = require('../src/leaflet_options');
const links = require('../src/links');
const parsed = links.parse('hl=de');
const merged = Object.assign({}, leafletOptions.defaultState, parsed);
expect(merged.language).toBe('de');
delete global.window;
});
});
Comment on lines +201 to 268

describe('OSRM_LABEL and OSRM_DEFAULT_LAYER overrides', () => {
Expand Down
Loading