Skip to content

Commit

Permalink
feat(ilc-server): pass additional methods to i18nParamsDetectionPlugin
Browse files Browse the repository at this point in the history
  • Loading branch information
stas-nc committed Dec 11, 2023
1 parent eebdf3f commit 7144d0f
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 36 deletions.
48 changes: 30 additions & 18 deletions ilc/server/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,47 @@ const onRequestFactory = (i18nConfig, i18nParamsDetectionPlugin) => async (req,
return; // Excluding system routes
}

let decodedI18nCookie;
let currI18nConf = { ...i18nConfig.default };

const encodedI18nCookie = Cookie.parse(req.headers.cookie || '')[i18nCookie.name];
const decodedI18nCookie = encodedI18nCookie && i18nCookie.decode(encodedI18nCookie);
const initialI18nConfig = decodedI18nCookie
? processI18nFromCookie(decodedI18nCookie, i18nConfig)
: i18nConfig.default;

if (encodedI18nCookie) {
decodedI18nCookie = i18nCookie.decode(encodedI18nCookie);
currI18nConf.locale =
IlcIntl.getCanonicalLocale(decodedI18nCookie.locale, i18nConfig.supported.locale) || currI18nConf.locale;
currI18nConf.currency = i18nConfig.supported.currency.includes(decodedI18nCookie.currency)
? decodedI18nCookie.currency
: currI18nConf.currency;
}

currI18nConf = await i18nParamsDetectionPlugin.detectI18nConfig(
const pluginProcessedI18nConfig = await i18nParamsDetectionPlugin.detectI18nConfig(
req.raw,
{
parseUrl: (url) => IlcIntl.parseUrl(i18nConfig, url),
localizeUrl: (url, { locale }) => IlcIntl.localizeUrl(i18nConfig, url, { locale }),
getCanonicalLocale: (locale) => IlcIntl.getCanonicalLocale(locale, i18nConfig.supported.locale),
getSupportedCurrencies: async () => i18nConfig.supported.currency,
getSupportedLocales: async () => i18nConfig.supported.locale,
getDefaultCurrency: async () => i18nConfig.default.currency,
getDefaultLocale: async () => i18nConfig.default.locale,
},
currI18nConf,
initialI18nConfig,
decodedI18nCookie,
);

if (!req.raw.url.startsWith('/_ilc/')) {
const fixedUrl = IlcIntl.localizeUrl(i18nConfig, req.raw.url, { locale: currI18nConf.locale });
const fixedUrl = IlcIntl.localizeUrl(i18nConfig, req.raw.url, { locale: pluginProcessedI18nConfig.locale });
if (fixedUrl !== req.raw.url) {
reply.redirect(fixedUrl);
return;
}
}

// Passing current locale to TailorX in order to render template properly
req.raw.ilcState.locale = currI18nConf.locale;
req.raw.ilcState.locale = pluginProcessedI18nConfig.locale;

// Passing i18n data down to fragments
req.headers['x-request-intl'] = intlSchema
.toBuffer({
...i18nConfig,
current: currI18nConf,
current: pluginProcessedI18nConfig,
})
.toString('base64');

const encodedNextI18nCookie = i18nCookie.encode(currI18nConf);
const encodedNextI18nCookie = i18nCookie.encode(pluginProcessedI18nConfig);

if (encodedI18nCookie !== encodedNextI18nCookie) {
reply.res.setHeader(
Expand All @@ -72,6 +68,22 @@ const onRequestFactory = (i18nConfig, i18nParamsDetectionPlugin) => async (req,
}
};

/**
* Validates data in cookie and sets default if not valid
* @param {object} cookie
* @param {object} i18nConfig
* @returns object
*/
function processI18nFromCookie(cookie, i18nConfig) {
const locale = IlcIntl.getCanonicalLocale(cookie.locale, i18nConfig.supported.locale) ?? i18nConfig.default.locale;

const currency = i18nConfig.supported.currency.includes(cookie.currency)
? cookie.currency
: i18nConfig.default.currency;

return { locale, currency };
}

/**
* @param i18nConfig
* @param {string} url
Expand Down
101 changes: 83 additions & 18 deletions ilc/server/i18n.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const nock = require('nock');
const _ = require('lodash');

const { intlSchema } = require('ilc-sdk/dist/server/IlcProtocol'); // "Private" import
const { IlcIntl } = require('ilc-sdk/app');

const i18n = require('./i18n');
const createApp = require('./app');
Expand Down Expand Up @@ -107,12 +108,78 @@ describe('i18n', () => {

describe('Unit tests', () => {
let onRequest, reply;

beforeEach(() => {
onRequest = i18n.onRequestFactory(i18nConfig, i18nParamsDetectionPlugin);
reply = getReplyMock();
});

describe('detect locale by i18n params detection plugin', () => {
describe('Correct call signature', () => {
const detectedI18nConfig = {
locale: 'ua-UA',
currency: 'UAH',
};
const cookiesI18nConfig = {
currency: 'EUR',
locale: 'en-GB',
};
const req = getReqMock('/test', `ilc-i18n=${cookiesI18nConfig.locale}:${cookiesI18nConfig.currency};`);

let parseUrlStub, localizeUrlStub, getCanonicalLocaleStub;
before(() => {
parseUrlStub = sinon.stub(IlcIntl, 'parseUrl');
localizeUrlStub = sinon.stub(IlcIntl, 'localizeUrl');
getCanonicalLocaleStub = sinon.stub(IlcIntl, 'getCanonicalLocale');
});
beforeEach(() => {
i18nParamsDetectionPlugin.detectI18nConfig.returns(detectedI18nConfig);
});
after(() => {
parseUrlStub.restore();
localizeUrlStub.restore();
getCanonicalLocaleStub.restore();
});

it('should provide all arguments', async () => {
await onRequest(req, reply);
const [providedReqRaw, providedIntl, providedI18nConfig, providedCookie] =
i18nParamsDetectionPlugin.detectI18nConfig.lastCall.args;
chai.expect(providedIntl).to.be.an('object');
chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);
chai.expect(providedCookie).to.be.eql(cookiesI18nConfig);
});
it('should provide intl', async () => {
await onRequest(req, reply);

const [, providedIntl] = i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;
chai.expect(providedIntl).to.have.keys([
'parseUrl',
'localizeUrl',
'getCanonicalLocale',
'getDefaultCurrency',
'getDefaultLocale',
'getSupportedCurrencies',
'getSupportedLocales',
]);
chai.expect(providedIntl.parseUrl('/test')).to.not.throw;
sinon.assert.calledOnceWithExactly(parseUrlStub, i18nConfig, '/test');
chai.expect(providedIntl.localizeUrl('/test', { locale: 'de-DE' })).to.not.throw;
sinon.assert.calledWithExactly(localizeUrlStub, i18nConfig, '/test', { locale: 'de-DE' });
chai.expect(providedIntl.getCanonicalLocale('de-DE')).to.not.throw;
sinon.assert.calledWithExactly(getCanonicalLocaleStub, 'de-DE', i18nConfig.supported.locale);
await chai.expect(providedIntl.getDefaultCurrency()).to.eventually.eql(i18nConfig.default.currency);
await chai.expect(providedIntl.getDefaultLocale()).to.eventually.eql(i18nConfig.default.locale);
await chai
.expect(providedIntl.getSupportedCurrencies())
.to.eventually.eql(i18nConfig.supported.currency);
await chai
.expect(providedIntl.getSupportedLocales())
.to.eventually.eql(i18nConfig.supported.locale);
});
});

it('ua-UA, redirects to URL with correct lang code', async () => {
const detectedI18nConfig = {
locale: 'ua-UA',
Expand All @@ -125,11 +192,10 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);
chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);

sinon.assert.calledWithExactly(reply.redirect, '/ua/test');
Expand All @@ -147,11 +213,10 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);
chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);

sinon.assert.calledOnceWithExactly(reply.redirect, '/ua/test');
Expand All @@ -173,11 +238,11 @@ describe('i18n', () => {
chai.expect(req.raw.ilcState.locale).to.be.eql(detectedI18nConfig.locale);
chai.expect(decodeIntlHeader(req.headers['x-request-intl'])).to.eql(expectedHeader(detectedI18nConfig));

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);

chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);

sinon.assert.calledWith(
Expand All @@ -200,11 +265,11 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);

chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);

sinon.assert.calledWith(reply.redirect, '/test');
Expand All @@ -222,11 +287,11 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);

chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);

sinon.assert.calledWith(reply.redirect, '/test');
Expand All @@ -244,11 +309,11 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);

chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);

sinon.assert.calledWith(reply.redirect, '/test');
Expand All @@ -274,11 +339,11 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);

chai.expect(providedI18nConfig).to.be.eql(i18nConfig.default);

sinon.assert.calledWith(reply.redirect, '/ua/test');
Expand All @@ -301,11 +366,11 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);

chai.expect(providedI18nConfig).to.be.eql(cookiesI18nConfig);

sinon.assert.notCalled(reply.redirect);
Expand All @@ -326,11 +391,11 @@ describe('i18n', () => {

await onRequest(req, reply);

const [providedReqRaw, providedIntl, providedI18nConfig] =
const [providedReqRaw, , providedI18nConfig] =
i18nParamsDetectionPlugin.detectI18nConfig.getCalls()[0].args;

chai.expect(providedReqRaw).to.be.eql(req.raw);
chai.expect(providedIntl).to.have.keys(['parseUrl', 'localizeUrl', 'getCanonicalLocale']);

chai.expect(providedI18nConfig).to.be.eql(detectedI18nConfig);

sinon.assert.notCalled(reply.redirect);
Expand Down
1 change: 1 addition & 0 deletions ilc/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"target": "es6",
"allowJs": true,
"strict": true,
"sourceMap": true,
"esModuleInterop": true
},
"exclude": ["node_modules", "public"],
Expand Down

0 comments on commit 7144d0f

Please sign in to comment.