import type { AbstractIntlMessages } from "next-intl"; /** * Client-side message scoping. * * `getMessages()` hands back the whole catalogue, and the root layout feeds all * of it to `NextIntlClientProvider`, which serialises every message into the RSC * payload of every page. The catalogue is ~235 KB of which `pages.admin` and * `admin` together are ~177 KB — and no public page ever reads either. * * Nested `NextIntlClientProvider`s REPLACE the parent's messages rather than * merging them (see use-intl's IntlProvider: `messages ?? parent.messages`), so * a segment cannot ask for "just my part". Instead the public layout installs a * provider seeded with the public subset, which shrinks the payload for every * public page while leaving /admin, /mod, /client and /admin-next on the full set. */ /** Top-level namespaces plus `pages.*` sub-namespaces reserved for staff tools. */ const ADMIN_ONLY_NAMESPACES = new Set(["admin"]); const ADMIN_ONLY_PAGE_NAMESPACES = new Set(["admin"]); /** * Drop the staff-tool namespaces from a full catalogue. * * A denylist rather than an allowlist on purpose: a new public page should work * immediately rather than silently 500 on a missing namespace, and the only * namespaces that must never reach a public client are the two admin ones. A * guard test asserts that no component under `app/(site)` references them. */ export function publicClientMessages( messages: AbstractIntlMessages, ): AbstractIntlMessages { const result: AbstractIntlMessages = {}; for (const [key, value] of Object.entries(messages)) { if (ADMIN_ONLY_NAMESPACES.has(key)) continue; if (key !== "pages") { result[key] = value; continue; } const pages: AbstractIntlMessages = {}; for (const [pageKey, pageValue] of Object.entries( value as AbstractIntlMessages, )) { if (ADMIN_ONLY_PAGE_NAMESPACES.has(pageKey)) continue; pages[pageKey] = pageValue; } result.pages = pages; } return result; }