UNPKG

3.41 kBJavaScriptView Raw
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
16module.exports = preferredLanguages;
17module.exports.preferredLanguages = preferredLanguages;
18
19/**
20 * Module variables.
21 * @private
22 */
23
24var simpleLanguageRegExp = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/;
25
26/**
27 * Parse the Accept-Language header.
28 * @private
29 */
30
31function 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
53function 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
86function 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
105function 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
132function 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
159function 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
168function getFullLanguage(spec) {
169 return spec.full;
170}
171
172/**
173 * Check if a spec has any quality.
174 * @private
175 */
176
177function isQuality(spec) {
178 return spec.q > 0;
179}