Skip to content

Commit

Permalink
fix: Don't retrieve defaults for locale, now and timeZone if th…
Browse files Browse the repository at this point in the history
…ese options have been provided to `NextIntlClientProvider` (#633)

Fixes #631 

See corresponding section in the release notes: [Changes to
`NextIntlClientProvider`](https://next-intl-docs.vercel.app/blog/next-intl-3-0#changes-to-nextintlclientprovider)
(once this PR is merged)
  • Loading branch information
amannn committed Nov 15, 2023
1 parent 5671ef2 commit 824363a
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 27 deletions.
39 changes: 30 additions & 9 deletions docs/pages/blog/next-intl-3-0.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,44 @@ If you call APIs from `next-intl` in Server Components, the page will by default

Note that if you're using `next-intl` exclusively in Client Components, this doesn't apply to your app and static rendering will continue to work as it did before.

### Changes to [`NextIntlClientProvider`](/docs/usage/configuration#nextintlclientprovider)

First, the import for this component has changed:

```diff
- import {NextIntlClientProvider} from 'next-intl/client';
+ import {NextIntlClientProvider} from 'next-intl';
```

Depending on if you're using `NextIntlClientProvider` with or without the App Router, there's been another change to consider that was implemented in order to avoid hydration mismatches across the server and client.

**Using `NextIntlClientProvider` without the App Router**

If you're using `NextIntlClientProvider` _without the App Router_ (e.g. with the Pages Router), you need to define the `locale` prop now explicitly (e.g. via `useRouter().locale`). Furthermore, a warning has been added if a `timeZone` hasn't been defined.

**Using `NextIntlClientProvider` with the App Router**

If you're using `NextIntlClientProvider` _with the App Router_, there has been a slight change to the semantics. If you're rendering `NextIntlClientProvider` within a Server Component, the component will pass defaults for `locale`, `now` and `timeZone` to the client side.

If you're already providing these options, then you're set.

If you're not, be aware that this will by default opt the corresponding page into dynamic rendering. If your app relies on static rendering, you can avoid this by passing all of the mentioned options, or by enabling static rendering explicitly (see the previous section).

### Other notable changes

1. `next-intl` now uses [`exports` in `package.json`](https://nodejs.org/api/packages.html#subpath-exports) to clearly define which modules are exported. This should not affect you, unless you've previously imported undocumented internals.
2. `NextIntlProvider` has been removed in favor of [`NextIntlClientProvider`](/docs/usage/configuration#client-server-components)
3. `NextIntlClientProvider` now needs to be imported from `next-intl` instead of `next-intl/client`.
4. If you're using `NextIntlClientProvider` outside of the App Router (e.g. with the Pages Router), you need to define the `locale` prop explicitly. Furthermore, a warning has been added if a `timeZone` hasn't been defined.
5. [The middleware](/docs/routing/middleware) now needs to be imported from `next-intl/middleware` instead of `next-intl/server` (deprecated since v2.14).
6. `next@^13.4` is now required for the RSC APIs. Next.js 12 is still supported for the Pages Router integration.
7. `useMessages` now has a non-nullable return type for easier consumption and will throw if no messages are configured.
8. `createTranslator(…).rich` now returns a `ReactNode`. Previously, this was somewhat confusing, since `t.rich` accepted and returned either React elements or strings depending on if you retrieve the fuction via `useTranslations` or `createTranslator`. Now, an explicit [`t.markup`](/docs/usage/messages#html-markup) function has been added to generate markup strings like `'<b>Hello</b>'` outside of React components.
9. `useIntl` has been replaced with [`useFormatter`](/docs/usage/dates-times) (deprecated since v2.11).
10. `createIntl` has been replaced with [`createFormatter`](/docs/environments/core-library) (deprecated since v2.11).
3. [The middleware](/docs/routing/middleware) now needs to be imported from `next-intl/middleware` instead of `next-intl/server` (deprecated since v2.14).
4. `next@^13.4` is now required for the RSC APIs. Next.js 12 is still supported for the Pages Router integration.
5. `useMessages` now has a non-nullable return type for easier consumption and will throw if no messages are configured.
6. `createTranslator(…).rich` now returns a `ReactNode`. Previously, this was somewhat confusing, since `t.rich` accepted and returned either React elements or strings depending on if you retrieve the fuction via `useTranslations` or `createTranslator`. Now, an explicit [`t.markup`](/docs/usage/messages#html-markup) function has been added to generate markup strings like `'<b>Hello</b>'` outside of React components.
7. `useIntl` has been replaced with [`useFormatter`](/docs/usage/dates-times) (deprecated since v2.11).
8. `createIntl` has been replaced with [`createFormatter`](/docs/environments/core-library) (deprecated since v2.11).

## Upgrade now

```
npm install next-intl@3.0.0
npm install next-intl@latest
```

## Thank you
Expand Down
4 changes: 2 additions & 2 deletions examples/example-app-router/src/components/NavigationLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import clsx from 'clsx';
import {useSelectedLayoutSegment} from 'next/navigation';
import {ComponentProps} from 'react';
import type {AppPathnames} from 'config';
import {Link} from 'navigation';
import type {AppPathnames} from '../config';
import {Link} from '../navigation';

export default function NavigationLink<Pathname extends AppPathnames>({
href,
Expand Down
2 changes: 1 addition & 1 deletion examples/example-app-router/src/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createLocalizedPathnamesNavigation} from 'next-intl/navigation';
import {locales, pathnames} from 'config';
import {locales, pathnames} from './config';

export const {Link, redirect, usePathname, useRouter} =
createLocalizedPathnamesNavigation({
Expand Down
9 changes: 5 additions & 4 deletions packages/next-intl/src/navigation/react-server/BaseLink.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {ComponentProps} from 'react';
import useLocale from '../../react-server/useLocale';
import {getLocale} from '../../server';
import BaseLinkWithLocale from '../../shared/BaseLinkWithLocale';
import {AllLocales} from '../../shared/types';

Expand All @@ -10,10 +10,11 @@ type Props<Locales extends AllLocales> = Omit<
locale?: Locales[number];
};

export default function BaseLink<Locales extends AllLocales>({
export default async function BaseLink<Locales extends AllLocales>({
locale,
...rest
}: Props<Locales>) {
const defaultLocale = useLocale();
return <BaseLinkWithLocale locale={locale || defaultLocale} {...rest} />;
return (
<BaseLinkWithLocale locale={locale || (await getLocale())} {...rest} />
);
}
18 changes: 7 additions & 11 deletions packages/next-intl/src/react-server/NextIntlClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import React, {ComponentProps} from 'react';
import {getLocale, getNow, getTimeZone} from '../server';
import BaseNextIntlClientProvider from '../shared/NextIntlClientProvider';
import useLocale from './useLocale';
import useNow from './useNow';
import useTimeZone from './useTimeZone';

type Props = ComponentProps<typeof BaseNextIntlClientProvider>;

export default function NextIntlClientProvider({
export default async function NextIntlClientProvider({
locale,
now,
timeZone,
...rest
}: Props) {
const defaultLocale = useLocale();
const defaultNow = useNow();
const defaultTimeZone = useTimeZone();

return (
<BaseNextIntlClientProvider
locale={locale ?? defaultLocale}
now={now ?? defaultNow}
timeZone={timeZone ?? defaultTimeZone}
// We need to be careful about potentially reading from headers here.
// See https://github.com/amannn/next-intl/issues/631
locale={locale ?? (await getLocale())}
now={now ?? (await getNow())}
timeZone={timeZone ?? (await getTimeZone())}
{...rest}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {expect, it, vi} from 'vitest';
import NextIntlClientProvider from '../../src/react-server/NextIntlClientProvider';
import {getLocale, getNow, getTimeZone} from '../../src/server';
import BaseNextIntlClientProvider from '../../src/shared/NextIntlClientProvider';

vi.mock('../../src/server', async () => ({
getLocale: vi.fn(async () => 'en-US'),
getNow: vi.fn(async () => new Date('2020-01-01T00:00:00.000Z')),
getTimeZone: vi.fn(async () => 'America/New_York')
}));

vi.mock('../../src/shared/NextIntlClientProvider', async () => ({
default: vi.fn(() => 'NextIntlClientProvider')
}));

it("doesn't read from headers if all relevant configuration is passed", async () => {
const result = await NextIntlClientProvider({
children: null,
locale: 'en-GB',
now: new Date('2020-02-01T00:00:00.000Z'),
timeZone: 'Europe/London'
});

expect(result.type).toBe(BaseNextIntlClientProvider);
expect(result.props).toEqual({
children: null,
locale: 'en-GB',
now: new Date('2020-02-01T00:00:00.000Z'),
timeZone: 'Europe/London'
});

expect(getLocale).not.toHaveBeenCalled();
expect(getNow).not.toHaveBeenCalled();
expect(getTimeZone).not.toHaveBeenCalled();
});

it('reads missing configuration from getter functions', async () => {
const result = await NextIntlClientProvider({
children: null
});

expect(result.type).toBe(BaseNextIntlClientProvider);
expect(result.props).toEqual({
children: null,
locale: 'en-US',
now: new Date('2020-01-01T00:00:00.000Z'),
timeZone: 'America/New_York'
});

expect(getLocale).toHaveBeenCalled();
expect(getNow).toHaveBeenCalled();
expect(getTimeZone).toHaveBeenCalled();
});

2 comments on commit 824363a

@vercel
Copy link

@vercel vercel bot commented on 824363a Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 824363a Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-docs – ./docs

next-intl-docs-git-main-next-intl.vercel.app
next-intl-docs.vercel.app
next-intl-docs-next-intl.vercel.app

Please sign in to comment.