Session 12: i18n (10 languages, cookie-based), Africa tier .99, locale switcher, RTL Arabic (1305 tests)
This commit is contained in:
@@ -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';
|
||||
Reference in New Issue
Block a user