// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // We ["MUST NOT generate more than three digits after the decimal point"][0]. We use a // space-efficient algorithm that runs out of digits after 28 languages. This should be // fine for most users and [the server doesn't parse more than 15 languages, at least // for badges][1]. // // [0]: https://httpwg.org/specs/rfc7231.html#quality.values // [1]: https://github.com/signalapp/Signal-Server/blob/d2bc3c736080c3d852c9e88af0bffcb6632d9975/service/src/main/java/org/whispersystems/textsecuregcm/badges/ConfiguredProfileBadgeConverter.java#L29 const MAX_LANGUAGES_TO_FORMAT = 28; export function formatAcceptLanguageHeader( languages: ReadonlyArray ): string { if (languages.length === 0) { return '*'; } const result: Array = []; const length = Math.min(languages.length, MAX_LANGUAGES_TO_FORMAT); for (let i = 0; i < length; i += 1) { const language = languages[i]; // ["If no 'q' parameter is present, the default weight is 1."][1] // // [1]: https://httpwg.org/specs/rfc7231.html#quality.values if (i === 0) { result.push(language); continue; } // These values compute a descending sequence with minimal bytes. See the tests for // examples. const magnitude = 1 / 10 ** (Math.ceil(i / 9) - 1); const subtractor = (((i - 1) % 9) + 1) * (magnitude / 10); const q = magnitude - subtractor; const formattedQ = q.toFixed(3).replace(/0+$/, ''); result.push(`${language};q=${formattedQ}`); } return result.join(', '); } export function getUserLanguages( defaults: undefined | ReadonlyArray, fallback: string ): ReadonlyArray { const result = defaults || []; return result.length ? result : [fallback]; }