diff --git a/examples/example-app-router-single-locale/src/app/layout.tsx b/examples/example-app-router-single-locale/src/app/layout.tsx
index dd542a179..99c0dd7d7 100644
--- a/examples/example-app-router-single-locale/src/app/layout.tsx
+++ b/examples/example-app-router-single-locale/src/app/layout.tsx
@@ -1,5 +1,5 @@
import {NextIntlClientProvider} from 'next-intl';
-import {getLocale, getMessages} from 'next-intl/server';
+import {getLocale} from 'next-intl/server';
import {ReactNode} from 'react';
type Props = {
@@ -9,19 +9,13 @@ type Props = {
export default async function LocaleLayout({children}: Props) {
const locale = await getLocale();
- // Providing all messages to the client
- // side is the easiest way to get started
- const messages = await getMessages();
-
return (
next-intl
-
- {children}
-
+
{children}
);
diff --git a/examples/example-app-router-without-i18n-routing/src/app/layout.tsx b/examples/example-app-router-without-i18n-routing/src/app/layout.tsx
index c9efefe2f..587b115f9 100644
--- a/examples/example-app-router-without-i18n-routing/src/app/layout.tsx
+++ b/examples/example-app-router-without-i18n-routing/src/app/layout.tsx
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import {Inter} from 'next/font/google';
import {NextIntlClientProvider} from 'next-intl';
-import {getLocale, getMessages} from 'next-intl/server';
+import {getLocale} from 'next-intl/server';
import {ReactNode} from 'react';
import './globals.css';
@@ -14,10 +14,6 @@ type Props = {
export default async function LocaleLayout({children}: Props) {
const locale = await getLocale();
- // Providing all messages to the client
- // side is the easiest way to get started
- const messages = await getMessages();
-
return (
@@ -29,9 +25,7 @@ export default async function LocaleLayout({children}: Props) {
inter.className
)}
>
-
- {children}
-
+
{children}
);
diff --git a/examples/example-app-router/src/components/BaseLayout.tsx b/examples/example-app-router/src/components/BaseLayout.tsx
index 8907d4e9b..89358fbb8 100644
--- a/examples/example-app-router/src/components/BaseLayout.tsx
+++ b/examples/example-app-router/src/components/BaseLayout.tsx
@@ -1,7 +1,6 @@
import {clsx} from 'clsx';
import {Inter} from 'next/font/google';
import {NextIntlClientProvider} from 'next-intl';
-import {getMessages} from 'next-intl/server';
import {ReactNode} from 'react';
import Navigation from '@/components/Navigation';
@@ -13,14 +12,10 @@ type Props = {
};
export default async function BaseLayout({children, locale}: Props) {
- // Providing all messages to the client
- // side is the easiest way to get started
- const messages = await getMessages();
-
return (
-
+
{children}
diff --git a/packages/next-intl/.size-limit.ts b/packages/next-intl/.size-limit.ts
index e00f8f64a..2eaf0b8a8 100644
--- a/packages/next-intl/.size-limit.ts
+++ b/packages/next-intl/.size-limit.ts
@@ -10,7 +10,7 @@ const config: SizeLimitConfig = [
name: "import {NextIntlClientProvider} from 'next-intl' (react-client)",
import: '{NextIntlClientProvider}',
path: 'dist/esm/production/index.react-client.js',
- limit: '1 KB'
+ limit: '1.005 KB'
},
{
name: "import * from 'next-intl' (react-server)",
diff --git a/packages/next-intl/src/react-server/NextIntlClientProviderServer.test.tsx b/packages/next-intl/src/react-server/NextIntlClientProviderServer.test.tsx
index 9680df759..2e5cb37e1 100644
--- a/packages/next-intl/src/react-server/NextIntlClientProviderServer.test.tsx
+++ b/packages/next-intl/src/react-server/NextIntlClientProviderServer.test.tsx
@@ -1,12 +1,13 @@
import {expect, it, vi} from 'vitest';
import getConfigNow from '../server/react-server/getConfigNow.js';
import getFormats from '../server/react-server/getFormats.js';
-import {getLocale, getTimeZone} from '../server.react-server.js';
+import {getLocale, getMessages, getTimeZone} from '../server.react-server.js';
import NextIntlClientProvider from '../shared/NextIntlClientProvider.js';
import NextIntlClientProviderServer from './NextIntlClientProviderServer.js';
vi.mock('../../src/server/react-server', async () => ({
getLocale: vi.fn(async () => 'en-US'),
+ getMessages: vi.fn(async () => ({})),
getTimeZone: vi.fn(async () => 'America/New_York')
}));
@@ -34,7 +35,8 @@ it("doesn't read from headers if all relevant configuration is passed", async ()
locale: 'en-GB',
now: new Date('2020-02-01T00:00:00.000Z'),
timeZone: 'Europe/London',
- formats: {}
+ formats: {},
+ messages: {}
});
expect(result.type).toBe(NextIntlClientProvider);
@@ -43,13 +45,15 @@ it("doesn't read from headers if all relevant configuration is passed", async ()
locale: 'en-GB',
now: new Date('2020-02-01T00:00:00.000Z'),
timeZone: 'Europe/London',
- formats: {}
+ formats: {},
+ messages: {}
});
expect(getLocale).not.toHaveBeenCalled();
expect(getConfigNow).not.toHaveBeenCalled();
expect(getTimeZone).not.toHaveBeenCalled();
expect(getFormats).not.toHaveBeenCalled();
+ expect(getMessages).not.toHaveBeenCalled();
});
it('reads missing configuration from getter functions', async () => {
@@ -63,6 +67,7 @@ it('reads missing configuration from getter functions', async () => {
locale: 'en-US',
now: new Date('2020-01-01T00:00:00.000Z'),
timeZone: 'America/New_York',
+ messages: {},
formats: {
dateTime: {
short: {
@@ -76,4 +81,5 @@ it('reads missing configuration from getter functions', async () => {
expect(getConfigNow).toHaveBeenCalled();
expect(getTimeZone).toHaveBeenCalled();
expect(getFormats).toHaveBeenCalled();
+ expect(getMessages).toHaveBeenCalled();
});
diff --git a/packages/next-intl/src/react-server/NextIntlClientProviderServer.tsx b/packages/next-intl/src/react-server/NextIntlClientProviderServer.tsx
index fc03de3d9..f468f1149 100644
--- a/packages/next-intl/src/react-server/NextIntlClientProviderServer.tsx
+++ b/packages/next-intl/src/react-server/NextIntlClientProviderServer.tsx
@@ -1,7 +1,7 @@
import type {ComponentProps} from 'react';
import getConfigNow from '../server/react-server/getConfigNow.js';
import getFormats from '../server/react-server/getFormats.js';
-import {getLocale, getTimeZone} from '../server.react-server.js';
+import {getLocale, getMessages, getTimeZone} from '../server.react-server.js';
import BaseNextIntlClientProvider from '../shared/NextIntlClientProvider.js';
type Props = ComponentProps;
@@ -9,6 +9,7 @@ type Props = ComponentProps;
export default async function NextIntlClientProviderServer({
formats,
locale,
+ messages,
now,
timeZone,
...rest
@@ -19,6 +20,7 @@ export default async function NextIntlClientProviderServer({
// See https://github.com/amannn/next-intl/issues/631
formats={formats === undefined ? await getFormats() : formats}
locale={locale ?? (await getLocale())}
+ messages={messages === undefined ? await getMessages() : messages}
// Note that we don't assign a default for `now` here,
// we only read one from the request config - if any.
// Otherwise this would cause a `dynamicIO` error.
diff --git a/packages/next-intl/src/server/react-server/getConfig.tsx b/packages/next-intl/src/server/react-server/getConfig.tsx
index 8372249a9..8f58b7217 100644
--- a/packages/next-intl/src/server/react-server/getConfig.tsx
+++ b/packages/next-intl/src/server/react-server/getConfig.tsx
@@ -68,14 +68,16 @@ const receiveRuntimeConfig = cache(receiveRuntimeConfigImpl);
const getFormatters = cache(_createIntlFormatters);
const getCache = cache(_createCache);
-async function getConfigImpl(localeOverride?: Locale): Promise<
- IntlConfig & {
- getMessageFallback: NonNullable;
- onError: NonNullable;
- timeZone: NonNullable;
- _formatters: ReturnType;
- }
-> {
+async function getConfigImpl(localeOverride?: Locale): Promise<{
+ locale: IntlConfig['locale'];
+ formats?: NonNullable;
+ timeZone: NonNullable;
+ onError: NonNullable;
+ getMessageFallback: NonNullable;
+ messages?: NonNullable;
+ now?: NonNullable;
+ _formatters: ReturnType;
+}> {
const runtimeConfig = await receiveRuntimeConfig(
createRequestConfig,
localeOverride
diff --git a/packages/next-intl/src/server/react-server/getServerFormatter.tsx b/packages/next-intl/src/server/react-server/getServerFormatter.tsx
index 33654204e..d1b6d282f 100644
--- a/packages/next-intl/src/server/react-server/getServerFormatter.tsx
+++ b/packages/next-intl/src/server/react-server/getServerFormatter.tsx
@@ -3,10 +3,6 @@ import {createFormatter} from 'use-intl/core';
import getDefaultNow from './getDefaultNow.js';
function getFormatterCachedImpl(config: Parameters[0]) {
- // same here?
- // also add a test
- // also for getTranslations/useTranslations
- // add a test with a getter maybe, don't mock
return createFormatter({
...config,
// Only init when necessary to avoid triggering a `dynamicIO` error
diff --git a/packages/use-intl/.size-limit.ts b/packages/use-intl/.size-limit.ts
index c6dae5c70..e1bf25098 100644
--- a/packages/use-intl/.size-limit.ts
+++ b/packages/use-intl/.size-limit.ts
@@ -5,14 +5,14 @@ const config: SizeLimitConfig = [
name: "import * from 'use-intl' (production)",
import: '*',
path: 'dist/esm/production/index.js',
- limit: '12.945 kB'
+ limit: '12.955 kB'
},
{
name: "import {IntlProvider, useLocale, useNow, useTimeZone, useMessages, useFormatter} from 'use-intl' (production)",
path: 'dist/esm/production/index.js',
import:
'{IntlProvider, useLocale, useNow, useTimeZone, useMessages, useFormatter}',
- limit: '1.98 kB'
+ limit: '1.995 kB'
}
];
diff --git a/packages/use-intl/src/core/IntlConfig.tsx b/packages/use-intl/src/core/IntlConfig.tsx
index 20ab1876a..68da27a1e 100644
--- a/packages/use-intl/src/core/IntlConfig.tsx
+++ b/packages/use-intl/src/core/IntlConfig.tsx
@@ -13,7 +13,7 @@ type IntlConfig = {
locale: Locale;
/** Global formats can be provided to achieve consistent
* formatting across components. */
- formats?: Formats;
+ formats?: Formats | null;
/** A time zone as defined in [the tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) which will be applied when formatting dates and times. If this is absent, the user time zone will be used. You can override this by supplying an explicit time zone to `formatDateTime`. */
timeZone?: TimeZone;
/** This callback will be invoked when an error is encountered during
@@ -40,7 +40,7 @@ type IntlConfig = {
*/
now?: Date;
/** All messages that will be available. */
- messages?: DeepPartial;
+ messages?: DeepPartial | null;
};
/**
@@ -48,7 +48,12 @@ type IntlConfig = {
* A stricter set of the configuration that should be used internally
* once defaults are assigned to `IntlConfiguration`.
*/
-export type InitializedIntlConfig = IntlConfig & {
+export type InitializedIntlConfig = Omit<
+ IntlConfig,
+ 'formats' | 'messages' | 'onError' | 'getMessageFallback'
+> & {
+ formats?: NonNullable;
+ messages?: NonNullable;
onError: NonNullable;
getMessageFallback: NonNullable;
};
diff --git a/packages/use-intl/src/core/initializeConfig.tsx b/packages/use-intl/src/core/initializeConfig.tsx
index 1bce4c21c..8bace8296 100644
--- a/packages/use-intl/src/core/initializeConfig.tsx
+++ b/packages/use-intl/src/core/initializeConfig.tsx
@@ -9,7 +9,7 @@ export default function initializeConfig<
// This is a generic to allow for stricter typing. E.g.
// the RSC integration always provides a `now` value.
Props extends IntlConfig
->({getMessageFallback, messages, onError, ...rest}: Props) {
+>({formats, getMessageFallback, messages, onError, ...rest}: Props) {
const finalOnError = onError || defaultOnError;
const finalGetMessageFallback =
getMessageFallback || defaultGetMessageFallback;
@@ -22,7 +22,12 @@ export default function initializeConfig<
return {
...rest,
- messages,
+ formats: (formats || undefined) as
+ | NonNullable
+ | undefined,
+ messages: (messages || undefined) as
+ | NonNullable
+ | undefined,
onError: finalOnError,
getMessageFallback: finalGetMessageFallback
};
diff --git a/packages/use-intl/src/react/IntlProvider.test.tsx b/packages/use-intl/src/react/IntlProvider.test.tsx
index af2564910..fbe8604a1 100644
--- a/packages/use-intl/src/react/IntlProvider.test.tsx
+++ b/packages/use-intl/src/react/IntlProvider.test.tsx
@@ -149,3 +149,25 @@ it('does not merge messages in nested providers', () => {
expect(onError.mock.calls.length).toBe(1);
});
+
+it('can opt-out of messages inheritance', () => {
+ const onError = vi.fn();
+
+ function Component() {
+ const t = useTranslations();
+ return {t('hello')};
+ }
+
+ render(
+
+
+
+
+
+
+ );
+
+ screen.getByText('Hey!');
+ screen.getByText('hello');
+ expect(onError.mock.calls.length).toBe(1);
+});
diff --git a/packages/use-intl/src/react/IntlProvider.tsx b/packages/use-intl/src/react/IntlProvider.tsx
index 81746ac34..6a67d24c1 100644
--- a/packages/use-intl/src/react/IntlProvider.tsx
+++ b/packages/use-intl/src/react/IntlProvider.tsx
@@ -49,10 +49,10 @@ export default function IntlProvider({
() => ({
...initializeConfig({
locale, // (required by provider)
- formats: formats || prevContext?.formats,
+ formats: formats === undefined ? prevContext?.formats : formats,
getMessageFallback:
getMessageFallback || prevContext?.getMessageFallback,
- messages: messages || prevContext?.messages,
+ messages: messages === undefined ? prevContext?.messages : messages,
now: now || prevContext?.now,
onError: onError || prevContext?.onError,
timeZone: timeZone || prevContext?.timeZone