Attic is a cozy space with lofty ambitions.
attic.social
1// Copyright 2018-2026 the Deno authors. MIT license.
2/*!
3 * Adapted directly from negotiator at https://github.com/jshttp/negotiator/
4 * which is licensed as follows:
5 *
6 * (The MIT License)
7 *
8 * Copyright (c) 2012-2014 Federico Romero
9 * Copyright (c) 2012-2014 Isaac Z. Schlueter
10 * Copyright (c) 2014-2015 Douglas Christopher Wilson
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining
13 * a copy of this software and associated documentation files (the
14 * 'Software'), to deal in the Software without restriction, including
15 * without limitation the rights to use, copy, modify, merge, publish,
16 * distribute, sublicense, and/or sell copies of the Software, and to
17 * permit persons to whom the Software is furnished to do so, subject to
18 * the following conditions:
19 *
20 * The above copyright notice and this permission notice shall be
21 * included in all copies or substantial portions of the Software.
22 *
23 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
28 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
29 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 */
31
32import { compareSpecs, isQuality, type Specificity } from "./common.ts";
33
34interface LanguageSpecificity extends Specificity {
35 prefix: string;
36 suffix: string | undefined;
37 full: string;
38}
39
40const SIMPLE_LANGUAGE_REGEXP = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/;
41
42function parseLanguage(
43 str: string,
44 i: number,
45): LanguageSpecificity | undefined {
46 const match = SIMPLE_LANGUAGE_REGEXP.exec(str);
47 if (!match) {
48 return undefined;
49 }
50
51 const [, prefix, suffix] = match;
52 if (!prefix) {
53 return undefined;
54 }
55
56 const full = suffix !== undefined ? `${prefix}-${suffix}` : prefix;
57
58 let q = 1;
59 if (match[3]) {
60 const params = match[3].split(";");
61 for (const param of params) {
62 const [key, value] = param.trim().split("=");
63 if (key === "q" && value) {
64 q = parseFloat(value);
65 break;
66 }
67 }
68 }
69
70 return { prefix, suffix, full, i, o: undefined, q, s: undefined };
71}
72
73function parseAcceptLanguage(accept: string): LanguageSpecificity[] {
74 const accepts = accept.split(",");
75 const result: LanguageSpecificity[] = [];
76
77 for (const [i, accept] of accepts.entries()) {
78 const language = parseLanguage(accept.trim(), i);
79 if (language) {
80 result.push(language);
81 }
82 }
83 return result;
84}
85
86function specify(
87 language: string,
88 spec: LanguageSpecificity,
89 i: number,
90): Specificity | undefined {
91 const p = parseLanguage(language, i);
92 if (!p) {
93 return undefined;
94 }
95 let s = 0;
96 if (spec.full.toLowerCase() === p.full.toLowerCase()) {
97 s |= 4;
98 } else if (spec.prefix.toLowerCase() === p.prefix.toLowerCase()) {
99 s |= 2;
100 } else if (spec.full.toLowerCase() === p.prefix.toLowerCase()) {
101 s |= 1;
102 } else if (spec.full !== "*") {
103 return;
104 }
105
106 return { i, o: spec.i, q: spec.q, s };
107}
108
109function getLanguagePriority(
110 language: string,
111 accepted: LanguageSpecificity[],
112 index: number,
113): Specificity {
114 let priority: Specificity = { i: -1, o: -1, q: 0, s: 0 };
115 for (const accepts of accepted) {
116 const spec = specify(language, accepts, index);
117 if (
118 spec &&
119 ((priority.s ?? 0) - (spec.s ?? 0) || priority.q - spec.q ||
120 (priority.o ?? 0) - (spec.o ?? 0)) < 0
121 ) {
122 priority = spec;
123 }
124 }
125 return priority;
126}
127
128export function preferredLanguages(
129 accept = "*",
130 provided?: string[],
131): string[] {
132 const accepts = parseAcceptLanguage(accept);
133
134 if (!provided) {
135 return accepts
136 .filter(isQuality)
137 .sort(compareSpecs)
138 .map((spec) => spec.full);
139 }
140
141 const priorities = provided
142 .map((type, index) => getLanguagePriority(type, accepts, index));
143
144 return priorities
145 .filter(isQuality)
146 .sort(compareSpecs)
147 .map((priority) => provided[priorities.indexOf(priority)]!);
148}