diff --git a/fluent-langneg/src/accepted_languages.js b/fluent-langneg/src/accepted_languages.js index 854482bc7..655124b7b 100644 --- a/fluent-langneg/src/accepted_languages.js +++ b/fluent-langneg/src/accepted_languages.js @@ -1,7 +1,25 @@ -export default function acceptedLanguages(string = "") { - if (typeof string !== "string") { +function parseAcceptLanguageEntry(entry) { + const langWithQ = entry.split(";").map(u => u.trim()); + let q = 1.0; + if (langWithQ.length > 1) { + const qVal = langWithQ[1].split("=").map(u => u.trim()); + if (qVal.length === 2 && qVal[0].toLowerCase() === "q") { + const qn = Number(qVal[1]); + q = isNaN(qn) ? 0.0 : qn; + } + } + return { lang: langWithQ[0], q }; +} + +export default function acceptedLanguages(acceptLanguageHeader = "") { + if (typeof acceptLanguageHeader !== "string") { throw new TypeError("Argument must be a string"); } - const tokens = string.split(",").map(t => t.trim()); - return tokens.filter(t => t !== "").map(t => t.split(";")[0]); + const tokens = acceptLanguageHeader.split(",").map(t => t.trim()) + .filter(t => t !== ""); + const langsWithQ = Array.from(tokens.map(parseAcceptLanguageEntry).entries()); + // order by q descending, keeping the header order for equal weights + langsWithQ.sort(([aidx, aval], [bidx, bval]) => + aval.q === bval.q ? aidx - bidx : bval.q - aval.q); + return langsWithQ.map(([, val]) => val.lang); } diff --git a/fluent-langneg/test/headers_test.js b/fluent-langneg/test/headers_test.js index 4443f71a0..c83b52733 100644 --- a/fluent-langneg/test/headers_test.js +++ b/fluent-langneg/test/headers_test.js @@ -2,6 +2,12 @@ import assert from 'assert'; import acceptedLanguages from '../src/accepted_languages'; suite('parse headers', () => { + test('without an argument', () => { + assert.deepStrictEqual( + acceptedLanguages(), [] + ); + }); + test('without quality values', () => { assert.deepStrictEqual( acceptedLanguages('en-US, fr, pl'), [ @@ -29,6 +35,53 @@ suite('parse headers', () => { ); }); + test('with out of order quality values', () => { + assert.deepStrictEqual( + acceptedLanguages('en;q=0.8, fr;q=0.9, de;q=0.7, *;q=0.5, fr-CH'), [ + 'fr-CH', + 'fr', + 'en', + 'de', + '*' + ] + ); + }); + + test('with equal q values', () => { + assert.deepStrictEqual( + acceptedLanguages('en;q=0.1, fr;q=0.1, de;q=0.1, *;q=0.1'), [ + 'en', + 'fr', + 'de', + '*' + ] + ); + }); + + test('with duff q values', () => { + assert.deepStrictEqual( + acceptedLanguages('en;q=no, fr;z=0.9, de;q=0.7;q=9, *;q=0.5, fr-CH;q=a=0.1'), [ + 'fr', + 'fr-CH', + 'de', + '*', + 'en' + ] + ); + }); + + test('with empty entries', () => { + assert.deepStrictEqual( + acceptedLanguages('en;q=0.8,,, fr;q=0.9,, de;q=0.7, *;q=0.5, fr-CH'), [ + 'fr-CH', + 'fr', + 'en', + 'de', + '*' + ] + ); + }); + test('edge cases', () => { const args = [ null,