Files
Epicnabbo-Catalogus-Updated…/src/i18n/request.ts
T
simoleo89 e004dfedaf i18n: translate all public page bodies (EN + IT)
Internationalised the ~44 public pages with next-intl (the shell was
already translated). Each page now pulls its copy from a "pages.<slug>"
namespace via getTranslations (server) / useTranslations (client); the
EN + IT catalogs were authored by parallel agents and merged centrally,
with natural Italian (ICU plurals) and it backfilled from en for any
gap. request.ts gained getMessageFallback/onError so a missing key
degrades to the English value, never a raw key.

Behaviour unchanged (only display text moved to t() calls; queries,
actions, fields, ContentCard structure preserved). Verified on the prod
server: with NEXT_LOCALE=it, home/community/staff/news/shop/rankings all
render Italian copy, no raw-key leakage; English unchanged. The nav
language switcher toggles EN/IT live. tsc 0, vitest 49/49, next build 0.

Admin pages intentionally left in English (staff tooling).
2026-06-28 20:40:06 +02:00

46 lines
1.9 KiB
TypeScript

import { cookies } from "next/headers";
import { getRequestConfig } from "next-intl/server";
// i18n WITHOUT routing: the locale is chosen by a `NEXT_LOCALE` cookie (set by
// the language switcher) rather than a URL segment, so every existing route and
// the access-guard middleware keep working unchanged. English is the fallback.
export const SUPPORTED_LOCALES = ["en", "it"] 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);
}
export default getRequestConfig(async () => {
const store = await cookies();
const cookieLocale = store.get("NEXT_LOCALE")?.value;
const locale: AppLocale = isSupportedLocale(cookieLocale) ? cookieLocale : DEFAULT_LOCALE;
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<unknown>((o, k) => (o && typeof o === "object" ? (o as Record<string, unknown>)[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.
},
};
});