Session 12: i18n (10 languages, cookie-based), Africa tier .99, locale switcher, RTL Arabic (1305 tests)

This commit is contained in:
Kev
2026-06-10 22:24:40 -04:00
parent e5c45ecc8e
commit d957dee17b
27 changed files with 1834 additions and 29 deletions
+47
View File
@@ -0,0 +1,47 @@
/**
* 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' },
};
// Localess that map to predominantly-African markets — used by the
// pricing page to surface the Africa tier first. Browser region
// codes (NG/KE/ZA/GH/...) are checked separately at the component
// layer.
export const AFRICA_LOCALES: ReadonlySet<Locale> = new Set(['sw']);
export function isLocale(value: string | null | undefined): value is Locale {
return !!value && (LOCALES as readonly string[]).includes(value);
}
// 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';