accept-language
Advanced tools
Comparing version 2.0.1 to 2.0.3
294
index.js
/** | ||
* Private variables | ||
* Dependencies | ||
*/ | ||
var bcp47 = require('bcp47'); | ||
var acceptLanguageSyntax = /((([a-zA-Z]+(-[a-zA-Z]+)?)|\*)(;q=[0-1](\.[0-9]+)?)?)*/g | ||
, isLanguage = /^[a-z]{2}$/ | ||
, isRegion = /^[a-z]{2}$/i; | ||
/** | ||
* Prototype | ||
* Object's size | ||
*/ | ||
Object.size = function(object) { | ||
var size = 0, key; | ||
for (key in object) { | ||
if (object.hasOwnProperty(key)) size++; | ||
} | ||
return size; | ||
}; | ||
var acceptLanguage = exports = module.exports = {}; | ||
/** | ||
* Languague codes | ||
* AcceptLanguage | ||
*/ | ||
var AcceptLanguage = function() {}; | ||
acceptLanguage._languages = []; | ||
/** | ||
* Default code | ||
* Language tags | ||
* | ||
* @type {Objects} | ||
* @public | ||
*/ | ||
AcceptLanguage.prototype.languageTags_ = {}; | ||
acceptLanguage.defaultLanguage = null; | ||
/** | ||
* Prune languages that aren't defined | ||
* Default language tag | ||
* | ||
* @param {Array.<language>} languages | ||
* @return {Array.<language>} | ||
* @api public | ||
* @type {String} | ||
*/ | ||
AcceptLanguage.prototype.defaultLanguageTag = null; | ||
function prune(languages) { | ||
var _this = this; | ||
if(acceptLanguage._languages.length > 0) { | ||
languages = languages.filter(function(language) { | ||
var filter = false; | ||
if(acceptLanguage._languages.indexOf(language.language) === -1) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
/** | ||
* Prune language tags that aren't defined | ||
* | ||
* @param {Array.<languageTag>} languageTags | ||
* @return {Array.<{ value: String, quality: Number }>} | ||
* @private | ||
*/ | ||
AcceptLanguage.prototype.prune_ = function(languageTags) { | ||
var this_ = this; | ||
if(Object.size(this.languageTags_) > 0) { | ||
languageTags = languageTags | ||
.filter(function(languageTag) { | ||
var language = languageTag.language; | ||
// Filter non-defined language tags. | ||
if(typeof this_.languageTags_[language] === 'undefined') { | ||
return false; | ||
} | ||
// Filter tags with only language sub tags that doesn't | ||
// have the supported language. E.g. our defined set is | ||
// ['en'] but th acceptLanguageString is ['es']. So we | ||
// filter away ['es']. | ||
if(!languageTag.region) { | ||
if(!this_.languageTags_[language].hasOnlyLanguage) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}) | ||
.map(function(languageTag) { | ||
var language = languageTag.language; | ||
if(languageTag.region) { | ||
var regionIndex = this_.languageTags_[language].regions.indexOf(languageTag.region); | ||
var hasRegion = true; | ||
if(regionIndex === -1) { | ||
hasRegion = false; | ||
regionIndex = 0; | ||
} | ||
// It should return the first matching region language tag | ||
// only if it doesn't contain ony root language tag. | ||
// So if the define language tags are ['es-419', 'es-US'] | ||
// and the Accept-Language string is ['es-ES']. We should | ||
// return 'es-419', because it has the biggest priority. | ||
// | ||
// Whenever it matches only language subtag and not region | ||
// tags and there exist one root language tag. We should | ||
// return the root language tag. E.g. If we have the set | ||
// ['es', 'es-419'] and the Accept-Language string is | ||
// 'es-ES'. Then we should return just ['es']. | ||
// | ||
// Wheneve it matches both language and region subtag it | ||
// should return that matched language tag, regardless if | ||
// there exist any root only languae subtag. E.g. If we | ||
// have the set ['es', 'es-419', 'es-PO'] and the Accept- | ||
// Language header is 'es-419'. Then we should return | ||
// ['es-419']. | ||
if(typeof this_.languageTags_[language].values[regionIndex] !== 'undefined') { | ||
if(hasRegion || !this_.languageTags_[language].onlyLanguageValue) { | ||
return { | ||
value: this_.languageTags_[language].values[regionIndex], | ||
language: language, | ||
region: this_.languageTags_[language].regions[regionIndex] || null, | ||
quality: languageTag.quality | ||
}; | ||
} | ||
} | ||
return { | ||
value: this_.languageTags_[language].onlyLanguageValue, | ||
language: language, | ||
region: null, | ||
quality: languageTag.quality | ||
}; | ||
} | ||
return languageTag; | ||
}); | ||
} | ||
// If no codes matches the defined set. Return | ||
// the default language if it is set | ||
if(languages.length === 0 && acceptLanguage.defaultLanguage) { | ||
return [acceptLanguage.defaultLanguage]; | ||
// If no language tags matches the defined set | ||
if(languageTags.length === 0 && this_.defaultLanguageTag) { | ||
return [this_.defaultLanguageTag]; | ||
} | ||
return languages; | ||
return languageTags; | ||
}; | ||
/** | ||
* Define codes | ||
* Define languages | ||
* | ||
* @param {Array.<code>} codes | ||
* @param {Array.<String>} languageTags | ||
* @return {void} | ||
* @throws {TypeError} | ||
* @api public | ||
* @public | ||
*/ | ||
AcceptLanguage.prototype.languageTags = function(languageTags) { | ||
var this_ = this; | ||
exports.languages = function(languages) { | ||
var _this = this; | ||
// Reset language tags | ||
this.languageTags_ = {}; | ||
languages.forEach(function(language) { | ||
if(typeof language !== 'string') { | ||
throw new TypeError('First parameter must be an array of strings'); | ||
languageTags.forEach(function(languageTagString) { | ||
var languageTag = bcp47.parse(languageTagString); | ||
if(!languageTag) { | ||
throw new TypeError('Your language tag (' + languageTagString + ') are not bcp47 compliant. For more info https://tools.ietf.org/html/bcp47.'); | ||
} | ||
if(!isLanguage.test(language)) { | ||
throw new TypeError('First parameter must be an array consisting of languague codes. Wrong syntax if code: ' + code); | ||
var language = languageTag.langtag.language.language; | ||
var region = languageTag.langtag.region; | ||
if(!this_.languageTags_[language]) { | ||
this_.languageTags_[language] = { | ||
values: region ? [languageTagString] : [], | ||
regions: region ? [region] : [], | ||
onlyLanguageValue: null | ||
}; | ||
} | ||
// Store language | ||
_this._languages.push(language); | ||
else { | ||
if(region) { | ||
this_.languageTags_[language].values.push(languageTagString); | ||
this_.languageTags_[language].regions.push(region); | ||
} | ||
} | ||
if(!region) { | ||
this_.languageTags_[language].onlyLanguageValue = languageTagString; | ||
} | ||
}); | ||
var defaultLanguageTag = bcp47.parse(languageTags[0]); | ||
this.defaultLanguageTag = { | ||
value: languageTags[0], | ||
language: defaultLanguageTag.langtag.language.language, | ||
region: defaultLanguageTag.langtag.region, | ||
quality: 1.0 | ||
}; | ||
}; | ||
/** | ||
* Default language if no-match occurs | ||
* | ||
* @param {String} language | ||
* @returns {void} | ||
* @throws {TypeError} | ||
* @api public | ||
*/ | ||
exports.default = function(language) { | ||
if(typeof language === 'string') { | ||
var properties = language.split('-'); | ||
if(properties.length === 0) { | ||
throw new TypeError('The string you provided is not a locale code string'); | ||
} | ||
language = { | ||
language: properties[0], | ||
region: properties[1], | ||
quality: 1 | ||
}; | ||
} | ||
if(typeof language !== 'object') { | ||
throw new TypeError('First parameter must be an object'); | ||
} | ||
if(typeof language.language !== 'string') { | ||
throw new TypeError('Property code must be a string and can\'t be undefined'); | ||
} | ||
if(!isLanguage.test(language.language)) { | ||
throw new TypeError('Property code must consist of two lowercase letters [a-z]'); | ||
} | ||
if(typeof language.region === 'string' && !isRegion.test(language.region)) { | ||
throw new TypeError('Property region must consist of two case-insensitive letters [a-zA-Z]'); | ||
} | ||
// Set language quality to 1.0 | ||
language.quality = 1.0; | ||
this.defaultLanguage = language; | ||
}; | ||
/** | ||
* Parse accept language string | ||
* | ||
* @param {String} acceptLanguage | ||
* @return {Array.<language>} | ||
* @api public | ||
* @param {String} string Accept-Language string | ||
* @return {Array.<{ value: String, quality: Number }>} | ||
* @public | ||
*/ | ||
exports.parse = function(acceptLanguage) { | ||
if(typeof acceptLanguage === 'object' && acceptLanguage.headers) { | ||
acceptLanguage = acceptLanguage.headers['accept-language']; | ||
AcceptLanguage.prototype.parse = function(string) { | ||
if(typeof string !== 'string' || string.length === 0) { | ||
return this.defaultLanguageTag ? [this.defaultLanguageTag] : []; | ||
} | ||
if(typeof acceptLanguage !== 'string') { | ||
return this.defaultLanguage ? [this.defaultLanguage] : []; | ||
} | ||
var strings = (acceptLanguage || '').match(acceptLanguageSyntax); | ||
var languages = strings.map(function(match) { | ||
if(!match){ | ||
return; | ||
var languageTags = string.split(','); | ||
languageTags = languageTags.map(function(languageTagString) { | ||
languageTagString = languageTagString.replace(/\s+/, ''); | ||
var components = languageTagString.split(';'); | ||
var languageTag = bcp47.parse(components[0]); | ||
if(!languageTag) { | ||
return null; | ||
} | ||
var bits = match.split(';'); | ||
var ietf = bits[0].split('-'); | ||
return { | ||
language: ietf[0], | ||
region: ietf[1], | ||
quality: bits[1] ? parseFloat(bits[1].split('=')[1]) : 1.0 | ||
value: components[0], | ||
language: languageTag.langtag.language.language, | ||
region: languageTag.langtag.region, | ||
quality: components[1] ? parseFloat(components[1].split('=')[1]) : 1.0 | ||
}; | ||
}) | ||
.filter(function(language) { | ||
return language; | ||
// Filter non-defined language tags | ||
.filter(function(languageTag) { | ||
return languageTag; | ||
}) | ||
// Sort language tags | ||
.sort(function(a, b) { | ||
@@ -160,4 +224,28 @@ return b.quality - a.quality; | ||
return prune(languages); | ||
return this.prune_(languageTags); | ||
}; | ||
/** | ||
* Get most suitable language tag | ||
* | ||
* @param {String} string Accept-Language string | ||
* @return {String} | ||
* @public | ||
*/ | ||
AcceptLanguage.prototype.get = function(string) { | ||
return this.parse(string)[0].value; | ||
}; | ||
/** | ||
* For use as a single-ton | ||
*/ | ||
module.exports = new AcceptLanguage(); | ||
/** | ||
* For use as a non-singleton | ||
*/ | ||
module.exports.AcceptLanguage = AcceptLanguage; | ||
{ | ||
"name": "accept-language", | ||
"version": "2.0.1", | ||
"version": "2.0.3", | ||
"description": "HTTP Accept-Language parser for node", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "./bin/test" | ||
"test": "./binaries/test" | ||
}, | ||
@@ -9,0 +9,0 @@ "repository": { |
@@ -6,3 +6,3 @@ accept-language [![Build Status](https://travis-ci.org/tinganho/node-accept-language.png)](https://travis-ci.org/tinganho/node-accept-language) | ||
`accept-language` parses HTTP Accept-Language header and returns a consumable array of language codes. | ||
`accept-language` parses HTTP Accept-Language header and returns the most likely language tag or a consumable array of language tags. | ||
@@ -17,84 +17,45 @@ ### Installation: | ||
``` | ||
```javascript | ||
var acceptLanguage = require('accept-language'); | ||
var locales = acceptLanguage.parse('en-GB,en;q=0.8,sv'); | ||
accepLanguage.languageTags(['en-US', 'zh-CN']); | ||
console.log(accepLanguage.get('en-GB,en;q=0.8,sv')); | ||
// outputs: 'en-US' | ||
console.log(locales); | ||
``` | ||
var language = acceptLanguage.parse('en-GB,en;q=0.8,sv'); | ||
console.log(language); | ||
Output: | ||
``` | ||
/* | ||
[ | ||
{ | ||
value: 'en-US', | ||
language: "en", | ||
region: "GB", | ||
region: "US", | ||
quality: 1.0 | ||
}, | ||
{ | ||
language: "sv", | ||
region: undefined, | ||
quality: 1.0 | ||
}, | ||
{ | ||
language: "en", | ||
region: undefined, | ||
quality: 0.8 | ||
} | ||
]; | ||
*/ | ||
``` | ||
### Recommended usage with L10ns: | ||
L10ns is internationalization workflow and formatting tool. This library was specifically built for [L10ns](http://l10ns.org). | ||
Filter non-defined language codes: | ||
### API | ||
#### accepLanguage.languageTags(Array languageTags); | ||
Define your language tags in highest priority comes first. The language tags must comply with [BCP47 standard](https://tools.ietf.org/html/bcp47). I.e. all language tags `en`, `en-US` and `zh-Hant-TW` are working. | ||
```javascript | ||
acceptLanguage.languageTags(['en-US', 'zh-CN']); | ||
``` | ||
var acceptLanguage = require('accept-language'); | ||
acceptLanguage.languages(['en', 'zh']); | ||
var locales = acceptLanguage.parse('en-GB,en;q=0.8,sv'); | ||
console.log(locales); | ||
#### accepLanguage.get(String acceptLanguageString); | ||
Get the most likely language tag given an `Accept-Language` string. In order for it to work you must set all your language tags first. | ||
```javascript | ||
acceptLanguage.get('en-GB,en;q=0.8,sv')); | ||
``` | ||
Output: | ||
#### accepLanguage.parse(String acceptLanguageString); | ||
Parse an `Accept-Language` string and get a consumable array. In order for it to work you must set all your language tags first. | ||
```javascript | ||
acceptLanguage.get('en-GB,en;q=0.8,sv')); | ||
``` | ||
[ | ||
{ | ||
language: "en", | ||
region: "GB", | ||
quality: 1.0 | ||
}, | ||
{ | ||
language: "en", | ||
region: undefined, | ||
quality: 0.8 | ||
} | ||
]; | ||
``` | ||
Use default value: | ||
``` | ||
var acceptLanguage = require('accept-language'); | ||
acceptLanguage.default({ | ||
language: 'en', | ||
region: 'US' | ||
// No need to specify quality | ||
}); | ||
acceptLanguage.languages(['en', 'zh']); | ||
var locales = acceptLanguage.parse('fr-CA'); | ||
console.log(locales); | ||
``` | ||
Output: | ||
``` | ||
[ | ||
{ | ||
language: "en", | ||
region: "US", | ||
quality: 1.0 | ||
} | ||
]; | ||
``` | ||
The output is always sorted with the highest quality first. | ||
### License | ||
MIT |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24191
9
591
60
1