/** * The locale table. Adding a language is this file, `xx.json`, and nothing else. * * Everything downstream reads from `LOCALES`: the Astro i18n config, the language * switcher, the hreflang set, the sitemap, the suggestion bar and the catalog loader. * There is deliberately no second list to keep in step. */ export const DEFAULT_LOCALE = 'en' as const; export interface LocaleInfo { /** URL prefix and catalog filename. Also the value of ``. */ code: string; /** * The language's own name for itself, for the switcher. Never a flag: a flag is a * country, and Spanish is not Spain. */ label: string; /** * BCP 47 tag handed to Intl. Region included because number and date formatting * differ by region even when the language does not. */ intl: string; /** `og:locale`. Underscored, region required by the OpenGraph spec. */ og: string; /** Writing direction for the document. */ dir?: 'rtl'; } /** * Order matters: this is the order the switcher renders in, and the default locale * comes first. */ export const LOCALES: LocaleInfo[] = [ { code: 'en', label: 'English', intl: 'en-GB', og: 'en_US' }, { code: 'es', label: 'Español', intl: 'es-ES', og: 'es_ES' }, { code: 'nl', label: 'Nederlands', intl: 'nl-NL', og: 'nl_NL' }, { code: 'zh', label: '简体中文', intl: 'zh-CN', og: 'zh_CN' }, { code: 'hi', label: 'हिन्दी', intl: 'hi-IN', og: 'hi_IN' }, { code: 'ar', label: 'العربية الفصحى', intl: 'ar-SA', og: 'ar_SA', dir: 'rtl' }, { code: 'fr', label: 'Français', intl: 'fr-FR', og: 'fr_FR' }, { code: 'pt', label: 'Português', intl: 'pt-BR', og: 'pt_BR' }, { code: 'it', label: 'Italiano', intl: 'it-IT', og: 'it_IT' }, { code: 'tr', label: 'Türkçe', intl: 'tr-TR', og: 'tr_TR' }, { code: 'pl', label: 'Polski', intl: 'pl-PL', og: 'pl_PL' }, { code: 'uk', label: 'Українська', intl: 'uk-UA', og: 'uk_UA' }, { code: 'ro', label: 'Română', intl: 'ro-RO', og: 'ro_RO' }, { code: 'el', label: 'Ελληνικά', intl: 'el-GR', og: 'el_GR' }, { code: 'cs', label: 'Čeština', intl: 'cs-CZ', og: 'cs_CZ' }, { code: 'sv', label: 'Svenska', intl: 'sv-SE', og: 'sv_SE' }, { code: 'da', label: 'Dansk', intl: 'da-DK', og: 'da_DK' }, { code: 'id', label: 'Bahasa Indonesia', intl: 'id-ID', og: 'id_ID' }, { code: 'ur', label: 'اردو', intl: 'ur-PK', og: 'ur_PK', dir: 'rtl' }, { code: 'ru', label: 'Русский', intl: 'ru-RU', og: 'ru_RU' }, { code: 'de', label: 'Deutsch', intl: 'de-DE', og: 'de_DE' }, { code: 'ja', label: '日本語', intl: 'ja-JP', og: 'ja_JP' }, { code: 'ar-eg', label: 'العربية المصرية', intl: 'ar-EG', og: 'ar_EG', dir: 'rtl' }, { code: 'vi', label: 'Tiếng Việt', intl: 'vi-VN', og: 'vi_VN' }, ]; export type Locale = string; /** Just the codes, for the Astro config and for validating a URL segment. */ export const LOCALE_CODES: string[] = LOCALES.map((l) => l.code); /** Every locale except the default, which is the set that carries a URL prefix. */ export const PREFIXED_LOCALES: string[] = LOCALE_CODES.filter((code) => code !== DEFAULT_LOCALE); export function isLocale(value: unknown): value is Locale { return typeof value === 'string' && LOCALE_CODES.includes(value); } export function localeInfo(locale: Locale): LocaleInfo { return LOCALES.find((l) => l.code === locale) ?? LOCALES[0]!; } /** The Intl tag for a locale, for `Intl.NumberFormat` and friends. */ export function intlTag(locale: Locale): string { return localeInfo(locale).intl; } /** * Namespaces whose keys are sent to the browser. * * Islands render strings at runtime, so their copy has to reach the client. The static * half of the site (home page sections, about, the legal pages) is rendered at build * time and its copy never needs to ship. Splitting on the namespace keeps the inline * payload to roughly a third of the catalog. * * `scripts/check-i18n.mjs` fails the build if an island reaches for a key outside this * list, so the split cannot silently rot into a missing string on a real page. */ export const CLIENT_NAMESPACES = [ 'common', 'time', 'status', 'pulse', 'mints', 'mint', 'nut', // The federation index's own count line, and the module names the live island // redraws when a fresh announcement disagrees with the build. 'fedimints', 'fedimint', 'card', 'reviews', 'feed', 'login', 'signer', 'notfound', ] as const;