Skip to content

Commit

Permalink
introduce convertDetectedLanguage option
Browse files Browse the repository at this point in the history
  • Loading branch information
adrai committed May 7, 2024
1 parent ab4c37f commit a2a2459
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [v3.6.0](https://github.com/i18next/i18next-http-middleware/compare/v3.5.0...v3.6.0)
- introduce convertDetectedLanguage option

## [v3.5.0](https://github.com/i18next/i18next-http-middleware/compare/v3.4.1...v3.5.0)
- fix: separate cjs and mjs typings

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,11 @@ As with all modules you can either pass the constructor function (class) to the
cookieDomain: 'myDomain',
cookiePath: '/my/path',
cookieSecure: true, // if need secure cookie
cookieSameSite: 'strict' // 'strict', 'lax' or 'none'
cookieSameSite: 'strict', // 'strict', 'lax' or 'none'

// optional conversion function used to modify the detected language code
convertDetectedLanguage: 'Iso15897',
convertDetectedLanguage: (lng) => lng.replace('-', '_')
}
```
Expand Down
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ interface LanguageDetectorOptions {
caches?: LanguageDetectorCaches;
cookieExpirationDate?: Date;
cookieDomain?: string;

/**
* optional conversion function to use to modify the detected language code
*/
convertDetectedLanguage?: 'Iso15897' | ((lng: string) => string);
}
interface LanguageDetectorAllOptions {
fallbackLng: boolean | string | string[];
Expand Down
9 changes: 8 additions & 1 deletion lib/LanguageDetector.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ function getDefaults () {
// cookieDomain: 'myDomain'
// cookiePath: '/my/path'
cookieSameSite: 'strict',
ignoreCase: true
ignoreCase: true,

convertDetectedLanguage: (l) => l
})
}

Expand All @@ -37,6 +39,10 @@ class LanguageDetector {
this.options = utils.defaults(options, this.options || {}, getDefaults())
this.allOptions = allOptions

if (typeof this.options.convertDetectedLanguage === 'string' && this.options.convertDetectedLanguage.indexOf('15897') > -1) {
this.options.convertDetectedLanguage = (l) => l.replace('-', '_')
}

this.addDetector(cookieLookup)
this.addDetector(querystringLookup)
this.addDetector(pathLookup)
Expand All @@ -61,6 +67,7 @@ class LanguageDetector {
if (!Array.isArray(detections)) detections = [detections]

detections = detections.filter((d) => d !== undefined && d !== null)
detections = detections.map((d) => this.options.convertDetectedLanguage(d))

if (this.services.languageUtils.getBestMatchFromCodes) { // new i18next v19.5.0
found = this.services.languageUtils.getBestMatchFromCodes(detections)
Expand Down
197 changes: 197 additions & 0 deletions test/languageDetector.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,200 @@ describe('language detector', () => {
})
})
})

describe('language detector (ISO 15897 locales)', () => {
const ld = new LanguageDetector(i18next.services, { order: ['session', 'querystring', 'path', 'cookie', 'header'], cookieSameSite: 'none', convertDetectedLanguage: 'Iso15897' })

describe('cookie', () => {
it('detect', () => {
const req = {
headers: {
cookie: 'i18next=de-CH'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('de_CH')
// expect(res).to.eql({})
})

it('shouldn\'t fail on URI malformed from cookie content', () => {
const req = {
headers: {
cookie: 'i18next=%'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('%')
})

it('cacheUserLanguage', () => {
const req = {}
const res = {
headers: {
'Set-Cookie': 'my=cookie'
}
}
res.header = (name, value) => { res.headers[name] = value }
ld.cacheUserLanguage(req, res, 'it_IT', ['cookie'])
expect(req).to.eql({})
expect(res).to.have.property('headers')
expect(res.headers).to.have.property('Set-Cookie')
expect(res.headers['Set-Cookie']).to.match(/i18next=it_IT/)
expect(res.headers['Set-Cookie']).to.match(/Path=\//)
expect(res.headers['Set-Cookie']).to.match(/my=cookie/)
expect(res.headers['Set-Cookie']).to.match(/SameSite=None/)
})
})

describe('header', () => {
it('detect', () => {
const req = {
headers: {
'accept-language': 'de-DE'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('de_DE')
// expect(res).to.eql({})
})

it('detect special', () => {
const req = {
headers: {
'accept-language': 'zh-Hans'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('zh_Hans')
// expect(res).to.eql({})
})

it('detect 3 char lngs', () => {
const req = {
headers: {
'accept-language': 'haw-US'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('haw_US')
// expect(res).to.eql({})
})

it('detect with custom regex', () => {
const req = {
headers: {
'accept-language': 'zh-Hans'
}
}
const res = {}
const ldCustom = new LanguageDetector(i18next.services, { order: ['header'], lookupHeaderRegex: /(([a-z]{4})-?([A-Z]{2})?)\s*;?\s*(q=([0-9.]+))?/gi })
const lng = ldCustom.detect(req, res)
expect(lng).to.eql('Hans')
// expect(res).to.eql({})
})

it('detect region with numbers', () => {
const req = {
headers: {
'accept-language': 'es-419'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('es_419')
// expect(res).to.eql({})
})

it('parses weight correctly', () => {
const req = {
headers: {
'accept-language': 'pt-PT;q=0.9,es-419;q=0.8,en;q=0.7'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('pt_PT')
// expect(res).to.eql({})
})

it('parses weight out of order correctly', () => {
const req = {
headers: {
'accept-language': 'es-419;q=0.7,en;q=0.8,pt-PT;q=0.9'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('pt_PT')
// expect(res).to.eql({})
})

it('sets weight to 1 as default', () => {
const req = {
headers: {
'accept-language': 'pt-BR,pt;q=0.9,es-419;q=0.8,en;q=0.7'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('pt_BR')
// expect(res).to.eql({})
})
})

describe('path', () => {
it('detect', () => {
const req = {
url: '/fr-fr/some/route'
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('fr_fr')
// expect(res).to.eql({})
})
})

describe('querystring', () => {
it('detect', () => {
const req = {
url: '/fr/some/route?lng=de-CH'
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('de_CH')
// expect(res).to.eql({})
})
})

describe('session', () => {
it('detect', () => {
const req = {
session: {
lng: 'de-AT'
}
}
const res = {}
const lng = ld.detect(req, res)
expect(lng).to.eql('de_AT')
// expect(res).to.eql({})
})

it('cacheUserLanguage', () => {
const req = {
session: {
lng: 'de-DE'
}
}
const res = {}
ld.cacheUserLanguage(req, res, 'it', ['session'])
expect(req).to.have.property('session')
expect(req.session).to.have.property('lng', 'it')
// expect(res).to.eql({})
})
})
})

0 comments on commit a2a2459

Please sign in to comment.