73 lines
3.5 KiB
TypeScript
73 lines
3.5 KiB
TypeScript
/**
|
|
* Locale registry (Session 12).
|
|
*
|
|
* Single source of truth for which languages the app supports.
|
|
* Imported by the middleware, the translation loader, and the locale
|
|
* switcher so adding a new language is a one-file change here plus a
|
|
* matching JSON file in `web/src/locales/`.
|
|
*
|
|
* RTL languages get `dir: 'rtl'` so the root layout can toggle the
|
|
* `<html dir>` attribute without a per-locale lookup.
|
|
*/
|
|
|
|
export const LOCALES = [
|
|
'en', 'es', 'fr', 'pt', 'ar', 'sw', 'hi', 'ja', 'ko', 'zh',
|
|
] as const;
|
|
|
|
export type Locale = (typeof LOCALES)[number];
|
|
|
|
export const DEFAULT_LOCALE: Locale = 'en';
|
|
|
|
export const LOCALE_META: Record<Locale, { label: string; native: string; dir: 'ltr' | 'rtl'; region: string }> = {
|
|
en: { label: 'English', native: 'English', dir: 'ltr', region: 'Global' },
|
|
es: { label: 'Spanish', native: 'Español', dir: 'ltr', region: 'Latin America / Spain' },
|
|
fr: { label: 'French', native: 'Français', dir: 'ltr', region: 'France / West Africa' },
|
|
pt: { label: 'Portuguese', native: 'Português', dir: 'ltr', region: 'Brazil / Portugal' },
|
|
ar: { label: 'Arabic', native: 'العربية', dir: 'rtl', region: 'MENA' },
|
|
sw: { label: 'Swahili', native: 'Kiswahili', dir: 'ltr', region: 'East Africa' },
|
|
hi: { label: 'Hindi', native: 'हिन्दी', dir: 'ltr', region: 'India' },
|
|
ja: { label: 'Japanese', native: '日本語', dir: 'ltr', region: 'Japan' },
|
|
ko: { label: 'Korean', native: '한국어', dir: 'ltr', region: 'South Korea' },
|
|
zh: { label: 'Chinese', native: '中文', dir: 'ltr', region: 'China' },
|
|
};
|
|
|
|
// Session 12: kept as a hint for the *interface language*. Session 13
|
|
// replaces the locale-based pricing-tier proxy with real IP geo
|
|
// (Cloudflare CF-IPCountry header → x-vyndr-country, see middleware).
|
|
// Pricing.tsx now reads the country code, not this set.
|
|
export const AFRICA_LOCALES: ReadonlySet<Locale> = new Set(['sw']);
|
|
|
|
// Session 13 — ISO-3166-1 alpha-2 codes for the African countries we
|
|
// surface the VYNDR Africa tier in. The list intentionally covers
|
|
// every sovereign African state (54). Membership IS the gate: outside
|
|
// this set, the Africa tier card is filtered out of the pricing page
|
|
// entirely. Inside this set, it renders first.
|
|
export const AFRICAN_COUNTRIES: ReadonlySet<string> = new Set([
|
|
// Sub-Saharan
|
|
'NG', 'KE', 'ZA', 'GH', 'TZ', 'ET', 'CM', 'SN', 'CI', 'UG',
|
|
'RW', 'MZ', 'AO', 'ZW', 'BW', 'NA', 'MU', 'ML', 'BF', 'NE',
|
|
'TD', 'MW', 'ZM', 'MG', 'CD', 'CG', 'GA', 'GQ', 'BJ', 'TG',
|
|
'SL', 'LR', 'GN', 'GM', 'CV', 'ST', 'KM', 'SC', 'DJ', 'ER',
|
|
'LS', 'SZ', 'SO', 'SS', 'BI',
|
|
// North Africa (MENA overlap)
|
|
'EG', 'MA', 'DZ', 'TN', 'LY', 'SD', 'EH',
|
|
]);
|
|
|
|
export function isLocale(value: string | null | undefined): value is Locale {
|
|
return !!value && (LOCALES as readonly string[]).includes(value);
|
|
}
|
|
|
|
export function isAfricanCountry(code: string | null | undefined): boolean {
|
|
if (!code) return false;
|
|
return AFRICAN_COUNTRIES.has(String(code).toUpperCase());
|
|
}
|
|
|
|
// Cookie name + locale-detection header name (set by middleware,
|
|
// read by server components via next/headers).
|
|
export const LOCALE_COOKIE = 'NEXT_LOCALE';
|
|
export const LOCALE_HEADER = 'x-vyndr-locale';
|
|
// Country code — Cloudflare stamps this on every edge request as
|
|
// CF-IPCountry; middleware copies it onto a vendor-namespaced header
|
|
// so server components don't depend on knowing about Cloudflare.
|
|
export const COUNTRY_HEADER = 'x-vyndr-country';
|