• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  var suffix = match[2]
59  var 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}
180