From 968189bb88b3b35fee0bb55350fae71bd595d21a Mon Sep 17 00:00:00 2001 From: StNekroman Date: Fri, 22 Nov 2024 19:29:04 +0200 Subject: [PATCH] Add setDefaultLanguageTag --- Library/Typings.d.ts | 11 +- README.md | 22 ++-- Source/AcceptLanguage.ts | 228 +++++++++++++++++++++++----------- Tests/Test.ts | 258 ++++++++++++++++++++++----------------- package-lock.json | 17 ++- package.json | 5 +- 6 files changed, 346 insertions(+), 195 deletions(-) diff --git a/Library/Typings.d.ts b/Library/Typings.d.ts index 3d457b1..05538c4 100644 --- a/Library/Typings.d.ts +++ b/Library/Typings.d.ts @@ -1,13 +1,15 @@ - -declare module 'accept-language' { - +declare module "accept-language" { interface AcceptLanguage { - /** * Define your supported languages. The first language will be your default language. */ languages(languages: string[]): void; + /** + * Sets default//fallback languages. If not called - then defaults language will be languages[0], which were set via languages(...) method. + */ + setDefaultLanguageTag(defaultLanguageTag: string | null): void; + /** * Get matched language. If no match, the default language will be returned. */ @@ -15,7 +17,6 @@ declare module 'accept-language' { } interface AcceptLanguageModule extends AcceptLanguage { - /** * Create instance of parser */ diff --git a/README.md b/README.md index 54acfbb..6fe7c06 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -accept-language [![Build Status](https://travis-ci.org/tinganho/node-accept-language.png)](https://travis-ci.org/tinganho/node-accept-language) -======================== +# accept-language [![Build Status](https://travis-ci.org/tinganho/node-accept-language.png)](https://travis-ci.org/tinganho/node-accept-language) [![NPM](https://nodei.co/npm/accept-language.png?downloads=true&stars=true)](https://nodei.co/npm/accept-language/) @@ -15,9 +14,12 @@ npm install accept-language --save ```ts // var acceptLanguage = require('accept-language'); -import acceptLanguage from 'accept-language'; -acceptLanguage.languages(['en-US', 'zh-CN']); -console.log(acceptLanguage.get('en-GB,en;q=0.8,sv')); +import acceptLanguage from "accept-language"; +acceptLanguage.languages(["en-US", "zh-CN"]); +// default language will be first from lang tags, given to languages(...) method. +// But if you dont want that behavior - uncomment following line +//acceptLanguage.setDefaultLanguageTag(null); +console.log(acceptLanguage.get("en-GB,en;q=0.8,sv")); /* 'en-US' @@ -26,20 +28,25 @@ console.log(acceptLanguage.get('en-GB,en;q=0.8,sv')); ``` ### Usage with Express: + If you are using Express server please use the middleware [express-request-language](https://www.npmjs.com/package/express-request-language). ### API + #### acceptLanguage.languages(Array languageTags); + Provide your language tags in order of priority. The language tags must comply with [BCP47][] standard. ```javascript -acceptLanguage.languages(['en-US', 'zh-CN']); +acceptLanguage.languages(["en-US", "zh-CN"]); ``` #### acceptLanguage.get(String acceptLanguageString); + Returns the most likely language given an `Accept-Language` string. At least 1 language tag must be provided. + ```javascript -acceptLanguage.get('en-GB,en;q=0.8,sv'); +acceptLanguage.get("en-GB,en;q=0.8,sv"); ``` ### Maintainer @@ -47,6 +54,7 @@ acceptLanguage.get('en-GB,en;q=0.8,sv'); Tingan Ho [@tingan87][] ### License + MIT [L10ns]: http://l10ns.org diff --git a/Source/AcceptLanguage.ts b/Source/AcceptLanguage.ts index 9fb813d..d52931f 100644 --- a/Source/AcceptLanguage.ts +++ b/Source/AcceptLanguage.ts @@ -1,5 +1,4 @@ - -import bcp47 = require('bcp47'); +import bcp47 = require("bcp47"); interface LanguageTagWithValue extends bcp47.LanguageTag { value: string; @@ -18,28 +17,39 @@ class AcceptLanguage { private defaultLanguageTag: string | null = null; + public setDefaultLanguageTag(defaultLanguageTag: string | null): void { + this.defaultLanguageTag = defaultLanguageTag; + } + public languages(definedLanguages: string[]) { if (definedLanguages.length < 1) { - throw new Error('No language tags defined. Provide at least 1 language tag to match.'); + throw new Error( + "No language tags defined. Provide at least 1 language tag to match." + ); } this.languageTagsWithValues = {}; definedLanguages.forEach((languageTagString) => { const languageTag = bcp47.parse(languageTagString); if (!languageTag) { - throw new TypeError(`'${ languageTagString }' is not bcp47 compliant. More about bcp47 https://tools.ietf.org/html/bcp47.`); + throw new TypeError( + `'${languageTagString}' is not bcp47 compliant. More about bcp47 https://tools.ietf.org/html/bcp47.` + ); } const language = languageTag.langtag.language.language; if (!language) { - throw new TypeError(`${ languageTagString } is not supported.`); + throw new TypeError(`${languageTagString} is not supported.`); } const langtag = languageTag.langtag; - let languageTagWithValues: LanguageTagWithValue = langtag as LanguageTagWithValue; + let languageTagWithValues: LanguageTagWithValue = + langtag as LanguageTagWithValue; languageTagWithValues.value = languageTagString; const lowerCasedLanguageTagWithValues: LanguageTagWithValue = { language: { language: langtag.language.language.toLowerCase(), - extlang: langtag.language.extlang.map((e) => e.toLowerCase()), + extlang: langtag.language.extlang.map((e) => + e.toLowerCase() + ), }, region: langtag.region && langtag.region.toLowerCase(), script: langtag.script && langtag.script.toLowerCase(), @@ -47,17 +57,22 @@ class AcceptLanguage { privateuse: langtag.privateuse.map((p) => p.toLowerCase()), extension: langtag.extension.map((e) => { return { - extension: e.extension && e.extension.map((e) => e.toLowerCase()), + extension: + e.extension && + e.extension.map((e) => e.toLowerCase()), singleton: e.singleton.toLowerCase(), - } + }; }), value: languageTagString, }; if (!this.languageTagsWithValues[language]) { - this.languageTagsWithValues[language] = [lowerCasedLanguageTagWithValues]; - } - else { - this.languageTagsWithValues[language].push(lowerCasedLanguageTagWithValues); + this.languageTagsWithValues[language] = [ + lowerCasedLanguageTagWithValues, + ]; + } else { + this.languageTagsWithValues[language].push( + lowerCasedLanguageTagWithValues + ); } }); @@ -72,11 +87,14 @@ class AcceptLanguage { return null as any; } - private parse(languagePriorityList: string | null | undefined): (string | null)[] { + private parse( + languagePriorityList: string | null | undefined + ): (string | null)[] { if (!languagePriorityList) { return [this.defaultLanguageTag]; } - const parsedAndSortedLanguageTags = parseAndSortLanguageTags(languagePriorityList); + const parsedAndSortedLanguageTags = + parseAndSortLanguageTags(languagePriorityList); const result: LanguageScore[] = []; for (const languageTag of parsedAndSortedLanguageTags) { const requestedLang = bcp47.parse(languageTag.tag); @@ -87,34 +105,64 @@ class AcceptLanguage { const requestedLangTag = requestedLang.langtag; - if (!this.languageTagsWithValues[requestedLangTag.language.language]) { + if ( + !this.languageTagsWithValues[requestedLangTag.language.language] + ) { continue; } - middle: - for (const definedLangTag of this.languageTagsWithValues[requestedLangTag.language.language]) { + middle: for (const definedLangTag of this.languageTagsWithValues[ + requestedLangTag.language.language + ]) { let unmatchedRequestedSubTag = 0; - for (const prop of ['privateuse', 'extension', 'variant', 'region', 'script']) { - const definedLanguagePropertValue = (definedLangTag as any)[prop]; + for (const prop of [ + "privateuse", + "extension", + "variant", + "region", + "script", + ]) { + const definedLanguagePropertValue = (definedLangTag as any)[ + prop + ]; if (!definedLanguagePropertValue) { - const requestedLanguagePropertyValue = (requestedLangTag as any)[prop]; + const requestedLanguagePropertyValue = ( + requestedLangTag as any + )[prop]; if (requestedLanguagePropertyValue) { unmatchedRequestedSubTag++; } switch (prop) { - case 'privateuse': - case 'variant': - for (let i = 0; i < requestedLanguagePropertyValue.length; i++) { + case "privateuse": + case "variant": + for ( + let i = 0; + i < requestedLanguagePropertyValue.length; + i++ + ) { if (requestedLanguagePropertyValue[i]) { unmatchedRequestedSubTag++; } } break; - case 'extension': - for (let i = 0; i < requestedLanguagePropertyValue.length; i++) { - const extension = requestedLanguagePropertyValue[i].extension; - for (let ei = 0; ei < extension.length; ei++) { - if (!requestedLanguagePropertyValue[i].extension[ei]) { + case "extension": + for ( + let i = 0; + i < requestedLanguagePropertyValue.length; + i++ + ) { + const extension = + requestedLanguagePropertyValue[i] + .extension; + for ( + let ei = 0; + ei < extension.length; + ei++ + ) { + if ( + !requestedLanguagePropertyValue[i] + .extension[ei] + ) { unmatchedRequestedSubTag++; } } @@ -127,39 +175,66 @@ class AcceptLanguage { // Filter out wider requested languages first. If someone requests 'zh' // and my defined language is 'zh-Hant'. I cannot match 'zh-Hant', because // 'zh' is wider than 'zh-Hant'. - const requestedLanguagePropertyValue = (requestedLangTag as any)[prop]; + const requestedLanguagePropertyValue = ( + requestedLangTag as any + )[prop]; if (!requestedLanguagePropertyValue) { continue middle; } - switch (prop) { - case 'privateuse': - case 'variant': - for (let i = 0; i < definedLanguagePropertValue.length; i++) { - if (!requestedLanguagePropertyValue[i] || definedLanguagePropertValue[i] !== requestedLanguagePropertyValue[i].toLowerCase()) { + case "privateuse": + case "variant": + for ( + let i = 0; + i < definedLanguagePropertValue.length; + i++ + ) { + if ( + !requestedLanguagePropertyValue[i] || + definedLanguagePropertValue[i] !== + requestedLanguagePropertyValue[ + i + ].toLowerCase() + ) { continue middle; } } break; - case 'extension': - for (let i = 0; i < definedLanguagePropertValue.length; i++) { - const extension = definedLanguagePropertValue[i].extension; + case "extension": + for ( + let i = 0; + i < definedLanguagePropertValue.length; + i++ + ) { + const extension = + definedLanguagePropertValue[i].extension; for (let ei = 0; ei < extension.length; ei++) { if (!requestedLanguagePropertyValue[i]) { continue middle; } - if (!requestedLanguagePropertyValue[i].extension[ei]) { + if ( + !requestedLanguagePropertyValue[i] + .extension[ei] + ) { continue middle; } - if (extension[ei] !== requestedLanguagePropertyValue[i].extension[ei].toLowerCase()) { + if ( + extension[ei] !== + requestedLanguagePropertyValue[ + i + ].extension[ei].toLowerCase() + ) { continue middle; } } } break; default: - if (definedLanguagePropertValue !== requestedLanguagePropertyValue.toLowerCase()) { + if ( + definedLanguagePropertValue !== + requestedLanguagePropertyValue.toLowerCase() + ) { continue middle; } } @@ -168,47 +243,62 @@ class AcceptLanguage { result.push({ unmatchedRequestedSubTag, quality: languageTag.quality, - languageTag: definedLangTag.value + languageTag: definedLangTag.value, }); } } - return result.length > 0 ? result.sort((a, b) => { - const quality = b.quality - a.quality; - if (quality != 0) { - return quality; - } - return a.unmatchedRequestedSubTag - b.unmatchedRequestedSubTag; - }).map((l) => l.languageTag) : [this.defaultLanguageTag]; + return result.length > 0 + ? result + .sort((a, b) => { + const quality = b.quality - a.quality; + if (quality != 0) { + return quality; + } + return ( + a.unmatchedRequestedSubTag - + b.unmatchedRequestedSubTag + ); + }) + .map((l) => l.languageTag) + : [this.defaultLanguageTag]; function parseAndSortLanguageTags(languagePriorityList: string) { - return languagePriorityList.split(',').map((weightedLanguageRange) => { - const components = weightedLanguageRange.replace(/\s+/, '').split(';'); - return { - tag: components[0], - quality: components[1] ? parseFloat(components[1].split('=')[1]) : 1.0 - }; - }) - - // Filter non-defined language tags - .filter((languageTag) => { - if (!languageTag) { - return false; - } - if (!languageTag.tag) { - return false; - } - return languageTag; - }); + return ( + languagePriorityList + .split(",") + .map((weightedLanguageRange) => { + const components = weightedLanguageRange + .replace(/\s+/, "") + .split(";"); + return { + tag: components[0], + quality: components[1] + ? parseFloat(components[1].split("=")[1]) + : 1.0, + }; + }) + + // Filter non-defined language tags + .filter((languageTag) => { + if (!languageTag) { + return false; + } + if (!languageTag.tag) { + return false; + } + return languageTag; + }) + ); } } } function create() { const al = new AcceptLanguage(); - al.create = function() { + al.create = function () { return new AcceptLanguage(); - } + }; return al; } diff --git a/Tests/Test.ts b/Tests/Test.ts index 0c7de2d..b840a7f 100644 --- a/Tests/Test.ts +++ b/Tests/Test.ts @@ -1,8 +1,7 @@ +require("source-map-support").install(); -require('source-map-support').install(); - -import AcceptLanguage from '../Source/AcceptLanguage'; -import chai = require('chai'); +import AcceptLanguage from "../Source/AcceptLanguage"; +import chai = require("chai"); const expect = chai.expect; function createInstance(definedLanguages?: string[]) { @@ -13,185 +12,222 @@ function createInstance(definedLanguages?: string[]) { return al; } -describe('Language definitions', () => { - it('should throw when defined languages is empty', () => { +describe("Language definitions", () => { + it("should throw when defined languages is empty", () => { const method = () => { createInstance([]); }; expect(method).to.throw(); }); - it('should return null on no defined languages', () => { + it("should return null on no defined languages", () => { const al = createInstance(); - expect(al.get('sv')).to.equal(null); + expect(al.get("sv")).to.equal(null); + }); + + it("match / no-match: should return default language when no match", () => { + const al = createInstance(["en"]); + expect(al.get("sv")).to.equal("en"); }); - it('match / no-match: should return default language when no match', () => { - const al = createInstance(['en']); - expect(al.get('sv')).to.equal('en'); + it("match / case-insensitive: language matching should be case-insensitive", () => { + const al = createInstance(["fr-FR", "de-DE"]); + expect(al.get("de-de")).to.equal("de-DE"); }); - it('match / case-insensitive: language matching should be case-insensitive', () => { - const al = createInstance(['fr-FR', 'de-DE']); - expect(al.get('de-de')).to.equal('de-DE'); + it("match / mutliple-language-requests: should match multiple requested languages", () => { + const al = createInstance(["en-US"]); + expect(al.get("en-US,sv-SE")).to.equal("en-US"); }); - it('match / mutliple-language-requests: should match multiple requested languages', () => { - const al = createInstance(['en-US']); - expect(al.get('en-US,sv-SE')).to.equal('en-US'); + it("match / mutliple-defined-languages: should match multiple defined languages", () => { + const al = createInstance(["en-US", "sv-SE"]); + expect(al.get("en-US,sv-SE")).to.equal("en-US"); }); - it('match / mutliple-defined-languages: should match multiple defined languages', () => { - const al = createInstance(['en-US', 'sv-SE']); - expect(al.get('en-US,sv-SE')).to.equal('en-US'); + it("match / quality: should match based on quality score", () => { + const al = createInstance(["en-US", "zh-CN"]); + expect(al.get("en-US;q=0.8,zh-CN;q=1.0")).to.equal("zh-CN"); }); - it('match / quality: should match based on quality score', () => { - const al = createInstance(['en-US', 'zh-CN']); - expect(al.get('en-US;q=0.8,zh-CN;q=1.0')).to.equal('zh-CN'); + it("match / specificity: should match based on specificity", () => { + const al = createInstance(["en", "en-US"]); + expect(al.get("en-US")).to.equal("en-US"); + const al2 = createInstance(["en", "en-US"]); + expect(al2.get("en, en-US")).to.equal("en"); }); - it('match / specificity: should match based on specificity', () => { - const al = createInstance(['en', 'en-US']); - expect(al.get('en-US')).to.equal('en-US'); - const al2 = createInstance(['en', 'en-US']); - expect(al2.get('en, en-US')).to.equal('en'); + it("script / perfect match", () => { + const al = createInstance(["en-US", "zh-Hant"]); + expect(al.get("zh-Hant;q=1,en-US;q=0.8")).to.equal("zh-Hant"); }); - it('script / perfect match', () => { - const al = createInstance(['en-US', 'zh-Hant']); - expect(al.get('zh-Hant;q=1,en-US;q=0.8')).to.equal('zh-Hant'); + it("script / false match", () => { + const al = createInstance(["en-US", "zh-Hant"]); + expect(al.get("zh-Hans;q=1,en-US;q=0.8")).to.equal("en-US"); }); - it('script / false match', () => { - const al = createInstance(['en-US', 'zh-Hant']); - expect(al.get('zh-Hans;q=1,en-US;q=0.8')).to.equal('en-US'); + it("region / broader match", () => { + const al = createInstance(["en-US", "zh"]); + expect(al.get("zh-Hant")).to.equal("zh"); }); - it('region / broader match', () => { - const al = createInstance(['en-US', 'zh']); - expect(al.get('zh-Hant')).to.equal('zh'); + it("script / narrower match", () => { + const al = createInstance(["en-US", "zh-Hant"]); + expect(al.get("zh;q=1,en-US;q=0.8")).to.equal("en-US"); }); - it('script / narrower match', () => { - const al = createInstance(['en-US', 'zh-Hant']); - expect(al.get('zh;q=1,en-US;q=0.8')).to.equal('en-US'); + it("region / perfect match", () => { + const al = createInstance(["en-US", "zh-CN"]); + expect(al.get("en-US;q=0.8, zh-CN;q=1.0")).to.equal("zh-CN"); }); - it('region / perfect match', () => { - const al = createInstance(['en-US', 'zh-CN']); - expect(al.get('en-US;q=0.8, zh-CN;q=1.0')).to.equal('zh-CN'); + it("region / false match", () => { + const al = createInstance(["en-US", "zh-CN"]); + expect(al.get("en-US;q=0.8, zh-US;q=1.0")).to.equal("en-US"); }); - it('region / false match', () => { - const al = createInstance(['en-US', 'zh-CN']); - expect(al.get('en-US;q=0.8, zh-US;q=1.0')).to.equal('en-US'); + it("region / broader match", () => { + const al = createInstance(["en-US", "zh"]); + expect(al.get("zh-CN")).to.equal("zh"); }); - it('region / broader match', () => { - const al = createInstance(['en-US', 'zh']); - expect(al.get('zh-CN')).to.equal('zh'); + it("region / narrower match", () => { + const al = createInstance(["en-US", "zh-CN"]); + expect(al.get("zh")).to.equal("en-US"); }); - it('region / narrower match', () => { - const al = createInstance(['en-US', 'zh-CN']); - expect(al.get('zh')).to.equal('en-US'); + it("variant / perfect match", () => { + const al = createInstance(["en-US", "de-CH-1996"]); + expect(al.get("en-US;q=0.8, de-CH-1996;q=1.0")).to.equal("de-CH-1996"); }); - it('variant / perfect match', () => { - const al = createInstance(['en-US', 'de-CH-1996']); - expect(al.get('en-US;q=0.8, de-CH-1996;q=1.0')).to.equal('de-CH-1996'); + it("variant / broader match", () => { + const al = createInstance(["en-US", "de-CH-1996"]); + expect(al.get("en-US;q=0.8, de-CH-1996-2001;q=1.0")).to.equal( + "de-CH-1996" + ); }); - it('variant / broader match', () => { - const al = createInstance(['en-US', 'de-CH-1996']); - expect(al.get('en-US;q=0.8, de-CH-1996-2001;q=1.0')).to.equal('de-CH-1996'); + it("variant / narrower match", () => { + const al = createInstance(["en-US", "de-CH-1996-2001"]); + expect(al.get("en-US;q=0.8, de-CH-1996;q=1.0")).to.equal("en-US"); }); - it('variant / narrower match', () => { - const al = createInstance(['en-US', 'de-CH-1996-2001']); - expect(al.get('en-US;q=0.8, de-CH-1996;q=1.0')).to.equal('en-US'); + it("privateuse / perfect match", () => { + const al = createInstance(["en-US", "de-CH-x-a"]); + expect(al.get("en-US;q=0.8, de-CH-x-a;q=1.0")).to.equal("de-CH-x-a"); }); - it('privateuse / perfect match', () => { - const al = createInstance(['en-US', 'de-CH-x-a']); - expect(al.get('en-US;q=0.8, de-CH-x-a;q=1.0')).to.equal('de-CH-x-a'); + it("privateuse / broader request", () => { + const al = createInstance(["en-US", "de-CH-x-a"]); + expect(al.get("en-US;q=0.8, de-CH-x-a-b;q=1.0")).to.equal("de-CH-x-a"); }); - it('privateuse / broader request', () => { - const al = createInstance(['en-US', 'de-CH-x-a']); - expect(al.get('en-US;q=0.8, de-CH-x-a-b;q=1.0')).to.equal('de-CH-x-a'); + it("privateuse / narrower request", () => { + const al = createInstance(["en-US", "de-CH-x-a-b"]); + expect(al.get("en-US;q=0.8, de-CH-x-a;q=1.0")).to.equal("en-US"); }); - it('privateuse / narrower request', () => { - const al = createInstance(['en-US', 'de-CH-x-a-b']); - expect(al.get('en-US;q=0.8, de-CH-x-a;q=1.0')).to.equal('en-US'); + it("extension / perfect match", () => { + const al = createInstance(["zh-CN", "en-a-bbb"]); + expect(al.get("en-US;q=0.8, en-a-bbb;q=1.0")).to.equal("en-a-bbb"); }); - it('extension / perfect match', () => { - const al = createInstance(['zh-CN', 'en-a-bbb']); - expect(al.get('en-US;q=0.8, en-a-bbb;q=1.0')).to.equal('en-a-bbb'); + it("extension / broader match", () => { + const al = createInstance(["zh-CN", "en-a-bbb"]); + expect(al.get("en-US;q=0.8, en-a-bbb-ccc;q=1.0")).to.equal("en-a-bbb"); }); - it('extension / broader match', () => { - const al = createInstance(['zh-CN', 'en-a-bbb']); - expect(al.get('en-US;q=0.8, en-a-bbb-ccc;q=1.0')).to.equal('en-a-bbb'); + it("extension / narrower match", () => { + const al = createInstance(["zh-CN", "en-a-bbb-ccc"]); + expect(al.get("en-US;q=0.8, en-a-bbb;q=1.0")).to.equal("zh-CN"); }); - it('extension / narrower match', () => { - const al = createInstance(['zh-CN', 'en-a-bbb-ccc']); - expect(al.get('en-US;q=0.8, en-a-bbb;q=1.0')).to.equal('zh-CN'); + it("extension / multiple extensions / perfect match", () => { + const al = createInstance(["zh-CN", "en-a-bbb-ccc-b-ddd"]); + expect(al.get("en-US;q=0.8, en-a-bbb-ccc-b-ddd;q=1.0")).to.equal( + "en-a-bbb-ccc-b-ddd" + ); }); - it('extension / multiple extensions / perfect match', () => { - const al = createInstance(['zh-CN', 'en-a-bbb-ccc-b-ddd']); - expect(al.get('en-US;q=0.8, en-a-bbb-ccc-b-ddd;q=1.0')).to.equal('en-a-bbb-ccc-b-ddd'); + it("extension / multiple extensions / broader match", () => { + const al = createInstance(["zh-CN", "en-a-bbb-ccc-b-ddd"]); + expect( + al.get("en-US;q=0.8, en-a-bbb-ccc-ddd-b-ddd-aaa;q=1.0") + ).to.equal("en-a-bbb-ccc-b-ddd"); }); - it('extension / multiple extensions / broader match', () => { - const al = createInstance(['zh-CN', 'en-a-bbb-ccc-b-ddd']); - expect(al.get('en-US;q=0.8, en-a-bbb-ccc-ddd-b-ddd-aaa;q=1.0')).to.equal('en-a-bbb-ccc-b-ddd'); + it("extension / multiple extensions / narrower match", () => { + const al = createInstance(["zh-CN", "en-a-bbb-ccc-b-ddd"]); + expect(al.get("en-US;q=0.8, en-a-bbb-b-ddd;q=1.0")).to.equal("zh-CN"); }); - it('extension / multiple extensions / narrower match', () => { - const al = createInstance(['zh-CN', 'en-a-bbb-ccc-b-ddd']); - expect(al.get('en-US;q=0.8, en-a-bbb-b-ddd;q=1.0')).to.equal('zh-CN'); + it("multiple subscripts / perfect match", () => { + const al = createInstance(["sv-SE", "zh-Hant-CN-x-red"]); + expect(al.get("en-US;q=0.8,zh-Hant-CN-x-red;q=1")).to.equal( + "zh-Hant-CN-x-red" + ); }); - it('multiple subscripts / perfect match', () => { - const al = createInstance(['sv-SE', 'zh-Hant-CN-x-red']); - expect(al.get('en-US;q=0.8,zh-Hant-CN-x-red;q=1')).to.equal('zh-Hant-CN-x-red'); + it("multiple subscripts / broader match", () => { + const al = createInstance(["sv-SE", "zh-Hant-CN-x-red"]); + expect(al.get("en-US;q=0.8,zh-Hant-CN-x-red-blue;q=1")).to.equal( + "zh-Hant-CN-x-red" + ); }); - it('multiple subscripts / broader match', () => { - const al = createInstance(['sv-SE', 'zh-Hant-CN-x-red']); - expect(al.get('en-US;q=0.8,zh-Hant-CN-x-red-blue;q=1')).to.equal('zh-Hant-CN-x-red'); + it("multiple subscripts / narrower match", () => { + const al = createInstance(["sv-SE", "zh-Hant-CN-x-red-blue"]); + expect(al.get("en-US;q=0.8,zh-Hant-CN-x-red;q=1")).to.equal("sv-SE"); }); - it('multiple subscripts / narrower match', () => { - const al = createInstance(['sv-SE', 'zh-Hant-CN-x-red-blue']); - expect(al.get('en-US;q=0.8,zh-Hant-CN-x-red;q=1')).to.equal('sv-SE'); + it("multiple subscripts / no match", () => { + const al = createInstance(["sv-SE", "zh-Hant-CN-x-red"]); + expect(al.get("en-US;q=0.8,zh-Hans-CN-x-red;q=1")).to.equal("sv-SE"); }); - it('multiple subscripts / no match', () => { - const al = createInstance(['sv-SE', 'zh-Hant-CN-x-red']); - expect(al.get('en-US;q=0.8,zh-Hans-CN-x-red;q=1')).to.equal('sv-SE'); + it("should match on *", () => { + const al = createInstance(["en-US"]); + expect(al.get("*")).to.equal("en-US"); }); - it('should match on *', () => { - const al = createInstance(['en-US']); - expect(al.get('*')).to.equal('en-US'); + it("should keep priority defined by languages", () => { + const al = createInstance([ + "en", + "ja", + "ko", + "zh-CN", + "zh-TW", + "de", + "es", + "fr", + "it", + ]); + expect( + al.get("en, ja, ne, zh, zh-TW, zh-CN, af, sq, am, ar, an") + ).to.equal("en"); }); - it('should keep priority defined by languages', () => { - const al = createInstance(['en', 'ja', 'ko', 'zh-CN', 'zh-TW', 'de', 'es', 'fr', 'it']); - expect(al.get('en, ja, ne, zh, zh-TW, zh-CN, af, sq, am, ar, an')).to.equal('en'); + it("should return default language on falsy get", () => { + const al = createInstance(["sv-SE", "zh-Hant-CN-x-red"]); + expect(al.get(undefined)).to.equal("sv-SE"); + expect(al.get(null)).to.equal("sv-SE"); + expect(al.get("")).to.equal("sv-SE"); + }); + + it("should NOT return default language on false get", () => { + const al = createInstance(["sv-SE", "zh-Hant-CN-x-red"]); + al.setDefaultLanguageTag(null); + + expect(al.get(undefined)).to.equal(null); + expect(al.get(null)).to.equal(null); + expect(al.get("")).to.equal(null); }); - it('should return default language on falsy get', () => { - const al = createInstance(['sv-SE', 'zh-Hant-CN-x-red']); - expect(al.get(undefined)).to.equal('sv-SE'); - expect(al.get(null)).to.equal('sv-SE'); - expect(al.get('')).to.equal('sv-SE'); + it("should NOT fallback to default", () => { + const al = createInstance(["en"]); + al.setDefaultLanguageTag(null); + expect(al.get("sv")).to.equal("null"); }); }); diff --git a/package-lock.json b/package-lock.json index 2611236..7139189 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "@types/node": "^8.0.31", "chai": "~1.9.1", "mocha": "^10.7.3", - "source-map-support": "^0.4.18" + "source-map-support": "^0.4.18", + "typescript": "^5.0.0" } }, "node_modules/@types/chai": { @@ -862,6 +863,20 @@ "node": "*" } }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", diff --git a/package.json b/package.json index ba828b9..92d6c69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "accept-language", - "version": "3.0.20", + "version": "3.0.21", "description": "Accept-Language parser for nodejs", "main": "Build/Source/AcceptLanguage.js", "scripts": { @@ -29,7 +29,8 @@ "@types/node": "^8.0.31", "chai": "~1.9.1", "mocha": "^10.7.3", - "source-map-support": "^0.4.18" + "source-map-support": "^0.4.18", + "typescript": "^5.0.0" }, "dependencies": { "bcp47": "^1.1.2"