Skip to content

Commit 7978cd9

Browse files
committed
fix: inconsistent use of accept-header header
commit 6d80189 Merge: 4746946 a7f04a3 Author: Rickard Falk <[email protected]> Date: Wed May 8 08:44:25 2024 +0200 Merge pull request #57 from KTH/fix/language-error Fix language error, add tests for it and refactor the init method commit a7f04a3 Author: Pipeline <[email protected]> Date: Thu May 2 12:09:48 2024 +0000 9.3.1-0 commit cf61720 Author: Rickard Falk <[email protected]> Date: Thu May 2 14:03:39 2024 +0200 refactor language tests commit da2a120 Author: Rickard Falk <[email protected]> Date: Thu May 2 14:03:10 2024 +0200 fix: set res.locals.locale.language commit 6be3590 Author: Rickard Falk <[email protected]> Date: Tue Apr 30 10:24:51 2024 +0200 add tests for res.locals.locale.language commit 9078fe5 Author: Rickard Falk <[email protected]> Date: Tue Apr 30 08:43:53 2024 +0200 fix: accept-language header contains preferred languages, not a single language commit d46d132 Author: Ellen <[email protected]> Date: Mon Apr 29 15:42:22 2024 +0200 Refactor init method for language commit 8f96edc Author: Ellen <[email protected]> Date: Mon Apr 29 15:41:57 2024 +0200 Add some more tests commit e08ce20 Author: mthege <[email protected]> Date: Mon Apr 29 14:55:38 2024 +0200 feat: tests for language.js commit 3d5dd4d Author: Rickard Falk <[email protected]> Date: Mon Apr 29 13:34:03 2024 +0200 fix: use chosenLocale for cookie language
1 parent 4746946 commit 7978cd9

File tree

4 files changed

+139
-28
lines changed

4 files changed

+139
-28
lines changed

lib/language.js

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,41 @@
77
const locale = require('locale')
88

99
const cookieName = 'language'
10+
const cookieOptions = { secure: true, sameSite: 'strict', httpOnly: true }
1011
const validLanguages = ['sv', 'en']
1112
const defaultLanguage = validLanguages[0]
1213
const supportedLocales = new locale.Locales(validLanguages, defaultLanguage)
1314

15+
const validLanguage = lang => {
16+
if (validLanguages.includes(lang)) {
17+
return lang
18+
}
19+
}
20+
21+
const useAcceptLanguageOrDefault = preferredLanguages => {
22+
const preferredLocales = new locale.Locales(preferredLanguages, defaultLanguage)
23+
const bestLocale = preferredLocales.best(supportedLocales)
24+
return bestLocale.language
25+
}
26+
1427
/**
1528
* Initialize locale and language for the current user session (respects cookie: language).
1629
*/
1730
function _init(req, res, newLang) {
18-
let lang = newLang || req.cookies[cookieName]
31+
const lang =
32+
validLanguage(newLang) ||
33+
validLanguage(req.cookies[cookieName]) ||
34+
useAcceptLanguageOrDefault(req.headers['accept-language'])
1935

20-
// Only allow lang to be one of the valid languages
21-
if (lang && !validLanguages.includes(lang)) lang = defaultLanguage
22-
23-
let chosenLocale
24-
25-
if (lang) {
26-
const locales = new locale.Locales(lang, defaultLanguage)
27-
chosenLocale = locales.best(supportedLocales)
28-
} else {
29-
const locales = new locale.Locales(req.headers['accept-language'], defaultLanguage)
30-
chosenLocale = locales.best(supportedLocales)
36+
if (req.cookies[cookieName] !== lang) {
37+
res.cookie(cookieName, lang, cookieOptions)
3138
}
3239

33-
// If we got an explicit language we set the language cookie
34-
if (newLang) {
35-
res.cookie(cookieName, chosenLocale.language, { secure: true, sameSite: 'strict', httpOnly: true })
36-
} else if (!req.cookies[cookieName]) {
37-
// Make sure language cookie is set so subsequent requests are guaranteed to use the same language
38-
res.cookie(cookieName, lang || 'sv', { secure: true, sameSite: 'strict', httpOnly: true })
39-
}
40-
41-
res.locals.locale = chosenLocale
40+
const locales = new locale.Locales(lang)
41+
const bestLocale = locales.best(supportedLocales)
42+
res.locals.locale = bestLocale
4243
// Backwards compatibility only in case someone accessed the prop directly:
43-
res.locals.language = chosenLocale.language
44-
45-
return chosenLocale
44+
res.locals.language = bestLocale.language
4645
}
4746

4847
/**
@@ -77,7 +76,6 @@ function _cookieLanguage(req) {
7776

7877
module.exports = {
7978
getLanguage: _getLanguage,
80-
init: _init,
8179
validLanguages,
8280
defaultLanguage,
8381
languageHandler: _languageHandler,

lib/language.test.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// const locale = require('locale')
2+
const { languageHandler } = require('./language')
3+
4+
describe('languageHandler', () => {
5+
const cookieOptions = { httpOnly: true, sameSite: 'strict', secure: true }
6+
const cookieName = 'language'
7+
8+
it('should save language if requested and valid', () => {
9+
const requestedLanguage = 'sv'
10+
const req = { query: { l: requestedLanguage }, cookies: {}, headers: {} }
11+
const res = { locals: {}, cookie: jest.fn() }
12+
const next = jest.fn()
13+
14+
languageHandler(req, res, next)
15+
expect(res.cookie).toHaveBeenCalledWith(cookieName, requestedLanguage, cookieOptions)
16+
expect(res.locals.locale.language).toEqual(requestedLanguage)
17+
})
18+
19+
it('should not save language in cookie if language has not changed', () => {
20+
const presetLanguage = 'sv'
21+
const req = { cookies: { [cookieName]: presetLanguage }, query: {}, headers: {} }
22+
const res = { locals: {}, cookie: jest.fn() }
23+
const next = jest.fn()
24+
25+
languageHandler(req, res, next)
26+
expect(res.cookie).not.toHaveBeenCalled()
27+
})
28+
29+
it('should not save invalid language', () => {
30+
const invalidLanguage = 'fr'
31+
const defaultLanguage = 'sv'
32+
const req = { query: { l: invalidLanguage }, cookies: {}, headers: {} }
33+
const res = { locals: {}, cookie: jest.fn() }
34+
const next = jest.fn()
35+
36+
languageHandler(req, res, next)
37+
expect(res.cookie).toHaveBeenCalledWith(cookieName, defaultLanguage, cookieOptions)
38+
expect(res.locals.locale.language).toEqual(defaultLanguage)
39+
})
40+
41+
it('should use default language as fallback', () => {
42+
const defaultLanguage = 'sv'
43+
const req = { query: {}, cookies: {}, headers: {} }
44+
const res = { locals: {}, cookie: jest.fn() }
45+
const next = jest.fn()
46+
47+
languageHandler(req, res, next)
48+
expect(res.cookie).toHaveBeenCalledWith(cookieName, defaultLanguage, cookieOptions)
49+
expect(res.locals.locale.language).toEqual(defaultLanguage)
50+
})
51+
52+
it('should update language if requested ', () => {
53+
const requestedLanguage = 'en'
54+
const presetLanguage = 'sv'
55+
const req = {
56+
query: { l: requestedLanguage },
57+
cookies: { [cookieName]: presetLanguage },
58+
headers: {},
59+
}
60+
const res = { locals: {}, cookie: jest.fn() }
61+
const next = jest.fn()
62+
63+
languageHandler(req, res, next)
64+
expect(res.cookie).toHaveBeenCalledWith(cookieName, requestedLanguage, cookieOptions)
65+
expect(res.locals.locale.language).toEqual(requestedLanguage)
66+
})
67+
68+
it('should use accept-language if requested language and cookie are unset', () => {
69+
const acceptLanguage = 'en,sv;q=0.9'
70+
const expectedLanguage = 'en'
71+
const req = { query: {}, cookies: {}, headers: { 'accept-language': acceptLanguage } }
72+
const res = { locals: {}, cookie: jest.fn() }
73+
const next = jest.fn()
74+
75+
languageHandler(req, res, next)
76+
expect(res.cookie).toHaveBeenCalledWith(cookieName, expectedLanguage, cookieOptions)
77+
expect(res.locals.locale.language).toEqual(expectedLanguage)
78+
})
79+
80+
it('should use accept-language if requested language is invalid and cookie is unset', () => {
81+
const requestedLanguage = 'fr'
82+
const acceptLanguage = 'en,sv;q=0.9'
83+
const expectedLanguage = 'en'
84+
const req = {
85+
query: { l: requestedLanguage },
86+
cookies: {},
87+
headers: { 'accept-language': acceptLanguage },
88+
}
89+
const res = { locals: {}, cookie: jest.fn() }
90+
const next = jest.fn()
91+
92+
languageHandler(req, res, next)
93+
expect(res.cookie).toHaveBeenCalledWith(cookieName, expectedLanguage, cookieOptions)
94+
expect(res.locals.locale.language).toEqual(expectedLanguage)
95+
})
96+
97+
it('should use accept-language if cookie language is invalid', () => {
98+
const cookieLanguage = 'fr'
99+
const acceptLanguage = 'en,sv;q=0.9'
100+
const expectedLanguage = 'en'
101+
const req = {
102+
query: {},
103+
cookies: { [cookieName]: cookieLanguage },
104+
headers: { 'accept-language': acceptLanguage },
105+
}
106+
const res = { locals: {}, cookie: jest.fn() }
107+
const next = jest.fn()
108+
109+
languageHandler(req, res, next)
110+
expect(res.cookie).toHaveBeenCalledWith(cookieName, expectedLanguage, cookieOptions)
111+
expect(res.locals.locale.language).toEqual(expectedLanguage)
112+
})
113+
})

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kth/kth-node-web-common",
3-
"version": "9.3.0",
3+
"version": "9.3.1-0",
44
"description": "Common components for node-web projects",
55
"scripts": {
66
"test": "jest",

0 commit comments

Comments
 (0)