Spaces:
Runtime error
Runtime error
File size: 4,965 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import { getRequestConfig } from "next-intl/server";
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<string, unknown>,
source: Record<string, unknown>
): Record<string, unknown> {
for (const [key, sourceValue] of Object.entries(source)) {
// Guard against prototype pollution from a crafted locale message tree.
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
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<string, unknown>, sourceValue as Record<string, unknown>);
} else if (targetValue === undefined) {
target[key] = sourceValue;
}
}
return target;
}
function setNestedValue(target: Record<string, unknown>, dottedKey: string, value: unknown): void {
const segments = dottedKey.split(".");
let cursor: Record<string, unknown> = target;
for (let index = 0; index < segments.length; index += 1) {
const segment = segments[index];
if (!segment || segment === "__proto__" || segment === "constructor" || segment === "prototype") {
return;
}
if (index === segments.length - 1) {
cursor[segment] = value;
return;
}
const next = cursor[segment];
if (next && typeof next === "object" && !Array.isArray(next)) {
cursor = next as Record<string, unknown>;
continue;
}
const created: Record<string, unknown> = {};
cursor[segment] = created;
cursor = created;
}
}
export function normalizeComplianceEventTypes(
messages: Record<string, unknown>
): Record<string, unknown> {
const compliance =
messages.compliance && typeof messages.compliance === "object" && !Array.isArray(messages.compliance)
? (messages.compliance as Record<string, unknown>)
: null;
const eventTypes =
compliance?.eventTypes &&
typeof compliance.eventTypes === "object" &&
!Array.isArray(compliance.eventTypes)
? (compliance.eventTypes as Record<string, unknown>)
: null;
if (!compliance || !eventTypes) return messages;
const normalizedEventTypes: Record<string, unknown> = {};
for (const [key, value] of Object.entries(eventTypes)) {
if (key.includes(".")) {
setNestedValue(normalizedEventTypes, key, value);
} else {
normalizedEventTypes[key] = value;
}
}
return {
...messages,
compliance: {
...compliance,
eventTypes: normalizedEventTypes,
},
};
}
export default getRequestConfig(async () => {
const cookieStore = await cookies();
let locale: string = cookieStore.get(LOCALE_COOKIE)?.value || "";
if (!locale) {
const headerStore = await headers();
locale = headerStore.get("x-locale") || "";
}
if (!LOCALES.includes(locale as Locale)) {
locale = DEFAULT_LOCALE;
}
const localeMessages = normalizeComplianceEventTypes(
(await import(`./messages/${locale}.json`)).default as Record<string, unknown>
);
// 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<string, unknown>;
if (locale !== FALLBACK_LOCALE) {
const fallbackMessages = normalizeComplianceEventTypes(
(await import(`./messages/${FALLBACK_LOCALE}.json`)).default as Record<string, unknown>
);
messages = deepMergeFallback({ ...localeMessages }, fallbackMessages);
}
// 4. Merge EN as namespace-level fallback for locales that are missing new namespaces.
// Only applied when the active locale is not EN (avoids a redundant import).
// Merging is shallow at the top-level namespace key — if a namespace is already
// present in the locale file it is kept as-is; missing namespaces fall back to EN.
// This ensures new namespaces (e.g. cliCode, cliAgents, acpAgents, cliCommon added
// in plan 14 F9) are displayed in English for the 39 non-EN/non-pt-BR locales until
// translations are shipped.
let mergedMessages: Record<string, unknown> = messages as Record<string, unknown>;
if (locale !== DEFAULT_LOCALE) {
const enMessages = normalizeComplianceEventTypes(
(await import(`./messages/${DEFAULT_LOCALE}.json`)).default as Record<string, unknown>
);
mergedMessages = { ...enMessages, ...mergedMessages };
}
return {
locale,
messages: mergedMessages,
};
});
|