import { cookies, headers } from "next/headers"; import { getRequestConfig } from "next-intl/server"; // i18n WITHOUT routing: the locale is chosen by a `NEXT_LOCALE` cookie, then // the Accept-Language header, and finally English as fallback. export const SUPPORTED_LOCALES = [ "en", "it", "nl", "de", "fr", "es", "pt", "pl", "sv", "tr", "ro", "hu", "cs", "sk", "da", "no", "el", "bg", "hr", "sr", "uk", "ru", ] as const; export type AppLocale = (typeof SUPPORTED_LOCALES)[number]; export const DEFAULT_LOCALE: AppLocale = "en"; export function isSupportedLocale( value: string | undefined, ): value is AppLocale { return !!value && (SUPPORTED_LOCALES as readonly string[]).includes(value); } function detectLocaleFromAcceptLanguage( acceptLanguage: string | null, ): AppLocale | null { if (!acceptLanguage) return null; const locales = acceptLanguage .split(",") .map((entry) => { const [tag, qPart] = entry.split(";"); const quality = qPart ? parseFloat(qPart.replace("q=", "")) : 1; const lang = tag?.trim().split("-")[0] ?? ""; return { lang, quality }; }) .filter((l) => l.lang) .sort((a, b) => b.quality - a.quality); for (const { lang } of locales) { if (isSupportedLocale(lang)) return lang; } return null; } export default getRequestConfig(async () => { const store = await cookies(); const cookieLocale = store.get("NEXT_LOCALE")?.value; let locale: AppLocale; if (isSupportedLocale(cookieLocale)) { locale = cookieLocale; } else { const headersList = await headers(); const detected = detectLocaleFromAcceptLanguage( headersList.get("Accept-Language"), ); locale = detected ?? DEFAULT_LOCALE; } // Safe: `locale` is validated against SUPPORTED_LOCALES above (only en/it/nl/de/fr/es). const messages = (await import(`../messages/${locale}.json`)).default; // English is the source of truth; fall back to it for any key missing from a // translation so the UI never shows a raw key path. const fallback = locale === DEFAULT_LOCALE ? messages : (await import(`../messages/${DEFAULT_LOCALE}.json`)).default; return { locale, messages, // Never throw on a missing message — use the English value, or a humanised // key as a last resort. getMessageFallback({ key }: { key: string }) { const fromEnglish = key .split(".") .reduce( (o, k) => o && typeof o === "object" ? (o as Record)[k] : undefined, fallback, ); if (typeof fromEnglish === "string") return fromEnglish; const seg = key.split(".").pop() ?? key; return seg .replace(/[._-]/g, " ") .replace(/\b\w/g, (c) => c.toUpperCase()); }, onError() { // Swallow MISSING_MESSAGE etc. — getMessageFallback already degrades. }, }; });