Files
vyndr/web/src/lib/locales.ts
T

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';