@veap/intl supplies locale negotiation, ICU message formatting, message-loader composition, and React APIs for server and client code. It is an integration library for React/Next.js applications; it does not replace Next.js routing.
Install#
npm install @veap/intl @veap/kernelThe package manifest declares optional peer dependencies on Next.js 16 and React 19. Install compatible versions in the application if you use the React or Next.js entry points.
Configure supported locales#
The server configuration reader understands the intl section of Veap configuration and environment overrides. The supported shape is:
// veap.config.ts
import type { VeapConfig } from "@veap/kernel";
const config: VeapConfig = {
intl: {
default: "en",
locales: ["en", "pl"],
cookie: "veap_locale",
timeZone: "Europe/Warsaw",
},
};
export default config;The server reader also recognizes VEAPCONFIG_INTL_DEFAULT, VEAPCONFIG_INTL_LOCALES, VEAPCONFIG_INTL_COOKIE, and VEAPCONFIG_INTL_TIMEZONE, plus corresponding NEXT_PUBLIC_INTL_* variables. Public-prefixed variables are visible to client bundles; do not put secrets in them.
Register message dictionaries#
Dictionaries are nested objects. Keep the key shape aligned between locales.
// src/i18n/messages.ts
import type { AbstractIntlMessages } from "@veap/intl";
export const messages: Record<string, AbstractIntlMessages> = {
en: {
common: { save: "Save", cancel: "Cancel" },
greeting: "Hello, {name}!",
cart: "{count, plural, =0 {No items} one {# item} other {# items}}",
},
pl: {
common: { save: "Zapisz", cancel: "Anuluj" },
greeting: "Cześć, {name}!",
cart: "{count, plural, =0 {Brak produktów} one {# produkt} few {# produkty} other {# produktów}}",
},
};Register a dictionary or loader from server-only startup code:
import { registerMessages } from "@veap/intl/server";
import { messages } from "@/i18n/messages";
await registerMessages("en", messages.en);
await registerMessages("pl", messages.pl);registerMessages(locale, messages) adds a dictionary to the shared IntlService. For dynamic or plugin-owned dictionaries, use registerMessageLoader() and return messages for the requested locale.
Server Components#
The server helper is named getTranslation (singular). It returns a translator function; it is not named getTranslations or getLocale.
import { getTranslation } from "@veap/intl/server";
export default async function HomePage() {
const t = await getTranslation();
return <main><h1>{t("greeting", { name: "Veap" })}</h1></main>;
}Pass explicit overrides when rendering a locale-specific route:
const t = await getTranslation("pl", "Europe/Warsaw");
const title = t("common.save");When no locale is passed, the helper attempts to use the current request locale and falls back to the configured default when request APIs are unavailable, such as during static generation.
Client Components#
Use the client entry point and keep the component below the package's I18nProvider:
"use client";
import { useTranslation, useLocale, useSupportedLocales } from "@veap/intl/client";
export function LocaleControls() {
const t = useTranslation();
const locale = useLocale();
const locales = useSupportedLocales();
return (
<section>
<p>{t("common.save")}</p>
<p>Current locale: {locale}</p>
<p>Available locales: {locales.join(", ")}</p>
</section>
);
}The client API includes I18nProvider, useLocale, useSupportedLocales, useTimeZone, useTranslation, and the alias useTranslations. The server I18nProvider automatically resolves request locale and dictionaries:
// app/layout.tsx
import { I18nProvider } from "@veap/intl/server";
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return <html><body><I18nProvider>{children}</I18nProvider></body></html>;
}The server provider is async and wraps children in the client context provider. Verify that your app's route/request context is available in the runtime where locale detection executes.
ICU message syntax#
Messages are formatted through intl-messageformat, so variables and plural/select expressions use ICU syntax rather than string concatenation. Keep full sentences in the dictionary so translators can reorder words.
Loaders and merge order#
MessageLoaderRecord accepts id, load (or the alias loader), and priority. Lower priorities load first; higher priorities override lower priorities. The documented type defaults to priority 100, core 0, and app loaders 200+. Use unique IDs for plugin loaders and test collisions deliberately.
import { registerMessageLoader } from "@veap/intl/server";
await registerMessageLoader({
id: "plugin:orders",
priority: 150,
async load(locale) {
if (locale !== "en" && locale !== "pl") return null;
return locale === "en"
? { orders: { title: "Orders" } }
: { orders: { title: "Zamówienia" } };
},
});Locale detection#
The inspected implementation checks the configured locale cookie first. If the cookie is absent or unsupported, it negotiates the Accept-Language header against supported locales and falls back to the configured default. Do not assume that URL-prefix routing or a locale segment is automatically implemented by this package.
API reference#
See the @veap/intl reference and the published entry-point map.