language.js (3408B)
1 /** 2 * negotiator 3 * Copyright(c) 2012 Isaac Z. Schlueter 4 * Copyright(c) 2014 Federico Romero 5 * Copyright(c) 2014-2015 Douglas Christopher Wilson 6 * MIT Licensed 7 */ 8 9 'use strict'; 10 11 /** 12 * Module exports. 13 * @public 14 */ 15 16 module.exports = preferredLanguages; 17 module.exports.preferredLanguages = preferredLanguages; 18 19 /** 20 * Module variables. 21 * @private 22 */ 23 24 var simpleLanguageRegExp = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/; 25 26 /** 27 * Parse the Accept-Language header. 28 * @private 29 */ 30 31 function parseAcceptLanguage(accept) { 32 var accepts = accept.split(','); 33 34 for (var i = 0, j = 0; i < accepts.length; i++) { 35 var language = parseLanguage(accepts[i].trim(), i); 36 37 if (language) { 38 accepts[j++] = language; 39 } 40 } 41 42 // trim accepts 43 accepts.length = j; 44 45 return accepts; 46 } 47 48 /** 49 * Parse a language from the Accept-Language header. 50 * @private 51 */ 52 53 function parseLanguage(str, i) { 54 var match = simpleLanguageRegExp.exec(str); 55 if (!match) return null; 56 57 var prefix = match[1], 58 suffix = match[2], 59 full = prefix; 60 61 if (suffix) full += "-" + suffix; 62 63 var q = 1; 64 if (match[3]) { 65 var params = match[3].split(';') 66 for (var j = 0; j < params.length; j++) { 67 var p = params[j].split('='); 68 if (p[0] === 'q') q = parseFloat(p[1]); 69 } 70 } 71 72 return { 73 prefix: prefix, 74 suffix: suffix, 75 q: q, 76 i: i, 77 full: full 78 }; 79 } 80 81 /** 82 * Get the priority of a language. 83 * @private 84 */ 85 86 function getLanguagePriority(language, accepted, index) { 87 var priority = {o: -1, q: 0, s: 0}; 88 89 for (var i = 0; i < accepted.length; i++) { 90 var spec = specify(language, accepted[i], index); 91 92 if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) { 93 priority = spec; 94 } 95 } 96 97 return priority; 98 } 99 100 /** 101 * Get the specificity of the language. 102 * @private 103 */ 104 105 function specify(language, spec, index) { 106 var p = parseLanguage(language) 107 if (!p) return null; 108 var s = 0; 109 if(spec.full.toLowerCase() === p.full.toLowerCase()){ 110 s |= 4; 111 } else if (spec.prefix.toLowerCase() === p.full.toLowerCase()) { 112 s |= 2; 113 } else if (spec.full.toLowerCase() === p.prefix.toLowerCase()) { 114 s |= 1; 115 } else if (spec.full !== '*' ) { 116 return null 117 } 118 119 return { 120 i: index, 121 o: spec.i, 122 q: spec.q, 123 s: s 124 } 125 }; 126 127 /** 128 * Get the preferred languages from an Accept-Language header. 129 * @public 130 */ 131 132 function preferredLanguages(accept, provided) { 133 // RFC 2616 sec 14.4: no header = * 134 var accepts = parseAcceptLanguage(accept === undefined ? '*' : accept || ''); 135 136 if (!provided) { 137 // sorted list of all languages 138 return accepts 139 .filter(isQuality) 140 .sort(compareSpecs) 141 .map(getFullLanguage); 142 } 143 144 var priorities = provided.map(function getPriority(type, index) { 145 return getLanguagePriority(type, accepts, index); 146 }); 147 148 // sorted list of accepted languages 149 return priorities.filter(isQuality).sort(compareSpecs).map(function getLanguage(priority) { 150 return provided[priorities.indexOf(priority)]; 151 }); 152 } 153 154 /** 155 * Compare two specs. 156 * @private 157 */ 158 159 function compareSpecs(a, b) { 160 return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; 161 } 162 163 /** 164 * Get full language string. 165 * @private 166 */ 167 168 function getFullLanguage(spec) { 169 return spec.full; 170 } 171 172 /** 173 * Check if a spec has any quality. 174 * @private 175 */ 176 177 function isQuality(spec) { 178 return spec.q > 0; 179 }