@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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 `<html lang>`. */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' },
|
||||
];
|
||||
|
||||
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',
|
||||
'reviews',
|
||||
'feed',
|
||||
'login',
|
||||
'signer',
|
||||
'notfound',
|
||||
] as const;
|
||||
Reference in New Issue
Block a user