/** * The languages Wellformed speaks, and the one setting that chooses between * them. * * A locale here is a property of the *workspace*, like its currency: it * decides the words in the chrome, the comma inside a number, the order * inside a date — the same for everyone who opens it. It briefly lived on the * account instead, on the theory that words belong to the reader, but a * workspace is full of proper names — tables, columns, views — written in one * language, and chrome in another language around them reads like two * products taped together. One set of books, one language, one way of writing * what is in them. * * Outside a workspace — the login page, the marketing page, mail to someone * locked out — there is nothing to ask, and the browser's language decides. */ export const SUPPORTED_LOCALES = ['en', 'nl'] as const; export type Locale = (typeof SUPPORTED_LOCALES)[number]; /** What a reader gets when neither they nor their browser expressed a wish. */ export const DEFAULT_LOCALE: Locale = 'en'; /** * Each language named in itself, because the picker is read by exactly the * person who may not understand the language the rest of the page is in. */ export const LOCALE_LABELS: Record = { en: 'English', nl: 'Nederlands', }; /** * The BCP 47 tag `Intl` formats under for each UI language. * * Broader than the language alone: 'en' could format as en-GB or en-US and * they disagree about dates. Picking one region per language keeps every * installation rendering identically instead of drifting with the server's or * the browser's own idea of English. */ export const FORMAT_LOCALES: Record = { en: 'en-US', nl: 'nl-NL', }; export function isLocale(value: unknown): value is Locale { return typeof value === 'string' && (SUPPORTED_LOCALES as readonly string[]).includes(value); } /** * Pick the best supported locale from an ordered preference list, e.g. * `navigator.languages` or a parsed Accept-Language header. * * Only the primary subtag is compared: 'nl-BE' is somebody who reads Dutch, * and telling them "sorry, only nl-NL here" by answering in English would be * the wrong kind of precision. */ export function resolveLocale(preferences: readonly string[]): Locale { for (const pref of preferences) { const primary = pref.trim().toLowerCase().split('-')[0]; if (isLocale(primary)) return primary; } return DEFAULT_LOCALE; }