diff --git a/app/component/LangSelect.js b/app/component/LangSelect.js
index e4af801785..d058e32dd1 100644
--- a/app/component/LangSelect.js
+++ b/app/component/LangSelect.js
@@ -1,32 +1,35 @@
import PropTypes from 'prop-types';
import React from 'react';
+import { routerShape } from 'react-router';
import connectToStores from 'fluxible-addons-react/connectToStores';
import moment from 'moment';
import ComponentUsageExample from './ComponentUsageExample';
import { setLanguage } from '../action/userPreferencesActions';
import { isBrowser } from '../util/browser';
+import { replaceQueryParams } from '../util/queryUtils';
-const selectLanguage = (executeAction, lang) => () => {
+const selectLanguage = (executeAction, lang, router) => () => {
executeAction(setLanguage, lang);
if (lang !== 'en') {
// eslint-disable-next-line global-require, import/no-dynamic-require
require(`moment/locale/${lang}`);
}
moment.locale(lang);
+ replaceQueryParams(router, { locale: lang });
};
-const language = (lang, currentLanguage, highlight, executeAction) => (
+const language = (lang, currentLanguage, highlight, executeAction, router) => (
);
-const LangSelect = ({ currentLanguage }, { executeAction, config }) => {
+const LangSelect = ({ currentLanguage }, { executeAction, config, router }) => {
if (isBrowser) {
return (
@@ -36,6 +39,7 @@ const LangSelect = ({ currentLanguage }, { executeAction, config }) => {
currentLanguage,
lang === currentLanguage,
executeAction,
+ router,
),
)}
@@ -64,6 +68,7 @@ LangSelect.propTypes = {
LangSelect.contextTypes = {
executeAction: PropTypes.func.isRequired,
config: PropTypes.object.isRequired,
+ router: routerShape.isRequired,
};
const connected = connectToStores(
diff --git a/app/component/SummaryPage.js b/app/component/SummaryPage.js
index fee726566c..0dc09ebe3d 100644
--- a/app/component/SummaryPage.js
+++ b/app/component/SummaryPage.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
/* eslint-disable react/no-array-index-key */
import React from 'react';
import Relay from 'react-relay/classic';
+import cookie from 'react-cookie';
import moment from 'moment';
import findIndex from 'lodash/findIndex';
import get from 'lodash/get';
@@ -485,7 +486,8 @@ const containerComponent = Relay.createContainer(SummaryPageWithBreakpoint, {
preferred: $preferred,
unpreferred: $unpreferred,
allowedBikeRentalNetworks: $allowedBikeRentalNetworks,
- ),
+ locale: $locale,
+ ),
{
${SummaryPlanContainer.getFragment('plan')}
${ItineraryTab.getFragment('searchTime')}
@@ -542,6 +544,7 @@ const containerComponent = Relay.createContainer(SummaryPageWithBreakpoint, {
walkSpeed: null,
wheelchair: null,
allowedBikeRentalNetworks: null,
+ locale: cookie.load('lang') || 'fi',
},
...defaultRoutingSettings,
},
diff --git a/app/component/SummaryPlanContainer.js b/app/component/SummaryPlanContainer.js
index 0af4c28167..416621b8f3 100644
--- a/app/component/SummaryPlanContainer.js
+++ b/app/component/SummaryPlanContainer.js
@@ -352,6 +352,7 @@ class SummaryPlanContainer extends React.Component {
$itineraryFiltering: Float!,
$modeWeight: InputModeWeight!,
$allowedBikeRentalNetworks: [String]!,
+ $locale: String!,
) { viewer {
plan(
fromPlace:$fromPlace,
@@ -390,6 +391,7 @@ class SummaryPlanContainer extends React.Component {
itineraryFiltering: $itineraryFiltering,
modeWeight: $modeWeight,
allowedBikeRentalNetworks: $allowedBikeRentalNetworks,
+ locale: $locale,
) {itineraries {startTime,endTime}}
}
}`;
diff --git a/app/util/planParamUtil.js b/app/util/planParamUtil.js
index e244d709a5..19eabf3694 100644
--- a/app/util/planParamUtil.js
+++ b/app/util/planParamUtil.js
@@ -269,6 +269,7 @@ export const preparePlanParams = config => (
walkReluctance,
walkSpeed,
allowedBikeRentalNetworks,
+ locale,
},
},
},
@@ -401,6 +402,7 @@ export const preparePlanParams = config => (
settings,
intermediatePlaceLocations,
),
+ locale,
},
nullOrUndefined,
),
diff --git a/server/reittiopasParameterMiddleware.js b/server/reittiopasParameterMiddleware.js
index ca026166fa..c63a2eb9a2 100644
--- a/server/reittiopasParameterMiddleware.js
+++ b/server/reittiopasParameterMiddleware.js
@@ -3,16 +3,25 @@ import isFinite from 'lodash/isFinite';
import oldParamParser from '../app/util/oldParamParser';
import { getConfiguration } from '../app/config';
+function formatQuery(query) {
+ const params = Object.keys(query)
+ .map(k => `${k}=${query[k]}`)
+ .join('&');
+
+ return `?${params}`;
+}
+
+function formatUrl(req) {
+ const query = formatQuery(req.query);
+ return `${req.path}?${query}`;
+}
+
function removeUrlParam(req, param) {
if (req.query[param]) {
delete req.query[param];
}
- const params = Object.keys(req.query)
- .map(k => `${k}=${req.query[k]}`)
- .join('&');
- const url = `${req.path}?${params}`;
- return url;
+ return formatUrl(req);
}
export function validateParams(req, config) {
@@ -55,14 +64,24 @@ export function validateParams(req, config) {
return url;
}
-export const langParamParser = path => {
- if (path.includes('/slangi/')) {
- const newPath = path.replace('/slangi/', '/');
- return newPath;
- }
- const lang = path.substring(0, 4);
- const newPath = path.replace(lang, '/');
- return newPath;
+const fixLocaleParam = (req, lang) => {
+ // override locale query param with the selected language
+ req.query.locale = lang === 'slangi' ? 'fi' : lang;
+ return formatQuery(req.query);
+};
+
+export const dropPathLanguageAndFixLocaleParam = (req, lang) => {
+ return req.path.replace(`/${lang}/`, '/') + fixLocaleParam(req, lang);
+};
+
+const dropPathLanguageAndRedirect = (req, res, lang) => {
+ const trimmedUrl = dropPathLanguageAndFixLocaleParam(req, lang);
+ res.redirect(trimmedUrl);
+};
+
+const fixLocaleParamAndRedirect = (req, res, lang) => {
+ const fixedUrl = req.path + fixLocaleParam(req, lang);
+ res.redirect(fixedUrl);
};
export default function reittiopasParameterMiddleware(req, res, next) {
@@ -87,15 +106,17 @@ export default function reittiopasParameterMiddleware(req, res, next) {
req.query.to_in
) {
oldParamParser(req.query, config).then(url => res.redirect(url));
- } else if (
- ['/fi/', '/en/', '/sv/', '/ru/', '/slangi/'].some(param =>
- req.path.includes(param),
- )
- ) {
- const redirectPath = langParamParser(req.url);
- res.redirect(redirectPath);
+ } else if (['fi', 'en', 'sv', 'ru', 'slangi'].includes(lang)) {
+ dropPathLanguageAndRedirect(req, res, lang);
} else {
- next();
+ const { locale } = req.query;
+ const cookieLang = req.cookies.lang;
+
+ if (cookieLang && locale && cookieLang !== locale) {
+ fixLocaleParamAndRedirect(req, res, cookieLang);
+ } else {
+ next();
+ }
}
} else {
next();
diff --git a/test/unit/component/LangSelect.test.js b/test/unit/component/LangSelect.test.js
index 7efb474e27..5591388513 100644
--- a/test/unit/component/LangSelect.test.js
+++ b/test/unit/component/LangSelect.test.js
@@ -46,9 +46,15 @@ describe('LangSelect', () => {
'Europe/Helsinki|EET EEST|-20 -30|01010101010101010101010|1BWp0 1qM0 WM0 1qM0 ' +
'WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|35e5',
};
+
+ const mockRouter = {
+ getCurrentLocation: () => '/',
+ replace: () => true,
+ };
+
configureMoment('sv', configWithMoment);
expect(moment.locale()).to.equal('sv');
- selectLanguage(() => true, 'fi')();
+ selectLanguage(() => true, 'fi', mockRouter)();
expect(moment.locale()).to.equal('fi');
});
});
diff --git a/test/unit/reittiopasParameterMiddleware.test.js b/test/unit/reittiopasParameterMiddleware.test.js
index a27a16c1e3..5d364f86f1 100644
--- a/test/unit/reittiopasParameterMiddleware.test.js
+++ b/test/unit/reittiopasParameterMiddleware.test.js
@@ -2,27 +2,27 @@ import { expect } from 'chai';
import { describe, it } from 'mocha';
import {
validateParams,
- langParamParser,
+ dropPathLanguageAndFixLocaleParam,
} from '../../server/reittiopasParameterMiddleware';
import config from '../../app/configurations/config.default';
-const req = {
- query: {
- minTransferTime: '60',
- modes: 'BUS,TRAM,RAIL,SUBWAY,FERRY,WALK,CITYBIKE',
- transferPenalty: '0',
- walkBoardCost: '540',
- walkReluctance: '1.5',
- walkSpeed: '1.5',
- },
-};
-
// validateParams returns an url if it is modified and it removes invalid
// parameteres from req.query => two ways to check if it did what it should
describe('reittiopasParameterMiddleware', () => {
describe('validateParams', () => {
+ const req = {
+ query: {
+ minTransferTime: '60',
+ modes: 'BUS,TRAM,RAIL,SUBWAY,FERRY,WALK,CITYBIKE',
+ transferPenalty: '0',
+ walkBoardCost: '540',
+ walkReluctance: '1.5',
+ walkSpeed: '1.5',
+ },
+ };
+
it('should not modify valid url', () => {
const url = validateParams(req, config);
expect(url).to.be.a('undefined');
@@ -44,28 +44,44 @@ describe('reittiopasParameterMiddleware', () => {
expect(req.query.modes).to.be.an('undefined');
});
});
- describe('langParamParser', () => {
- it('should return empty path', () => {
- const path = '/en/';
- const newPath = langParamParser(path);
- expect(newPath).to.equal('/');
+
+ describe('dropLanguageAndSetLocaleParam', () => {
+ const req = {
+ path: '/en/',
+ query: {
+ locale: 'fi',
+ },
+ };
+
+ it('should return empty path with "locale" query param', () => {
+ const relativeUrl = dropPathLanguageAndFixLocaleParam(req, 'en');
+ expect(relativeUrl).to.equal('/?locale=en');
});
- it('should return path without language parameter', () => {
- const path =
+ it('should return path without language', () => {
+ req.path =
'/sv/reitti/Rautatientori%2C%20Helsinki%3A%3A60.171283%2C24.942572/Pasila%2C%20Helsinki%3A%3A60.199017%2C24.933973';
- const newPath = langParamParser(path);
- expect(newPath).to.equal(
- '/reitti/Rautatientori%2C%20Helsinki%3A%3A60.171283%2C24.942572/Pasila%2C%20Helsinki%3A%3A60.199017%2C24.933973',
+ const relativeUrl = dropPathLanguageAndFixLocaleParam(req, 'sv');
+ expect(relativeUrl).to.equal(
+ '/reitti/Rautatientori%2C%20Helsinki%3A%3A60.171283%2C24.942572/Pasila%2C%20Helsinki%3A%3A60.199017%2C24.933973?locale=sv',
);
});
it('should not ignore URL parameters', () => {
- const path =
- '/en/reitti/Otaniemi,%20Espoo::60.187938,24.83182/Rautatientori,%20Asemanaukio%202,%20Helsinki::60.170384,24.939846?time=1565074800&arriveBy=false&utm_campaign=hsl.fi&utm_source=etusivu-reittihaku&utm_medium=referral';
- const newPath = langParamParser(path);
- expect(newPath).to.equal(
- '/reitti/Otaniemi,%20Espoo::60.187938,24.83182/Rautatientori,%20Asemanaukio%202,%20Helsinki::60.170384,24.939846?time=1565074800&arriveBy=false&utm_campaign=hsl.fi&utm_source=etusivu-reittihaku&utm_medium=referral',
+ req.path =
+ '/en/reitti/Otaniemi,%20Espoo::60.187938,24.83182/Rautatientori,%20Asemanaukio%202,%20Helsinki::60.170384,24.939846';
+ req.query = {
+ time: 1565074800,
+ arriveBy: false,
+ utm_campaign: 'hsl.fi',
+ utm_source: 'etusivu-reittihaku',
+ utm_medium: 'referral',
+ locale: 'fi',
+ };
+
+ const relativeUrl = dropPathLanguageAndFixLocaleParam(req, 'en');
+ expect(relativeUrl).to.equal(
+ '/reitti/Otaniemi,%20Espoo::60.187938,24.83182/Rautatientori,%20Asemanaukio%202,%20Helsinki::60.170384,24.939846?time=1565074800&arriveBy=false&utm_campaign=hsl.fi&utm_source=etusivu-reittihaku&utm_medium=referral&locale=en',
);
});
});