diff --git a/src/i18n/request.ts b/src/i18n/request.ts index e5eb20991c..0533cbed84 100644 --- a/src/i18n/request.ts +++ b/src/i18n/request.ts @@ -3,23 +3,57 @@ import { cookies, headers } from "next/headers"; import { LOCALES, DEFAULT_LOCALE, LOCALE_COOKIE } from "./config"; import type { Locale } from "./config"; +const FALLBACK_LOCALE = "en"; + +/** + * Deep merge that mutates `target` with values from `source`. + * If both have an object at the same key, recurse. + * Otherwise prefer the existing value in `target` (locale-specific wins). + */ +export function deepMergeFallback( + target: Record, + source: Record +): Record { + for (const [key, sourceValue] of Object.entries(source)) { + const targetValue = target[key]; + if ( + sourceValue !== null && + typeof sourceValue === "object" && + !Array.isArray(sourceValue) && + targetValue !== null && + typeof targetValue === "object" && + !Array.isArray(targetValue) + ) { + deepMergeFallback(targetValue as Record, sourceValue as Record); + } else if (targetValue === undefined) { + target[key] = sourceValue; + } + } + return target; +} + export default getRequestConfig(async () => { - // 1. Try cookie const cookieStore = await cookies(); let locale: string = cookieStore.get(LOCALE_COOKIE)?.value || ""; - // 2. Try custom header (set by middleware) if (!locale) { const headerStore = await headers(); locale = headerStore.get("x-locale") || ""; } - // 3. Validate & fallback if (!LOCALES.includes(locale as Locale)) { locale = DEFAULT_LOCALE; } - const messages = (await import(`./messages/${locale}.json`)).default; + const localeMessages = (await import(`./messages/${locale}.json`)).default; + + // G1: fall back to EN for any missing key. EN is loaded only once per request + // and only when the active locale is not EN itself (no-op). + let messages = localeMessages as Record; + if (locale !== FALLBACK_LOCALE) { + const fallbackMessages = (await import(`./messages/${FALLBACK_LOCALE}.json`)).default as Record; + messages = deepMergeFallback({ ...localeMessages }, fallbackMessages); + } return { locale,