Skip to content

Commit

Permalink
feat!: Infer default locale for NextIntlClientProvider from `useP…
Browse files Browse the repository at this point in the history
…arams` when rendered on the client side (#1483)

Bumps the Next.js peer dependency to 13.3.
  • Loading branch information
amannn authored Oct 29, 2024
1 parent a6238f4 commit 829998e
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 36 deletions.
6 changes: 3 additions & 3 deletions packages/next-intl/.size-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ const config: SizeLimitConfig = [
{
name: "import * from 'next-intl' (react-client, production)",
path: 'dist/esm/production/index.react-client.js',
limit: '13.105 KB'
limit: '13.11 KB'
},
{
name: "import {NextIntlClientProvider} from 'next-intl' (react-client, production)",
import: '{NextIntlClientProvider}',
path: 'dist/esm/production/index.react-client.js',
limit: '1 KB'
limit: '1.055 KB'
},
{
name: "import * from 'next-intl' (react-server, production)",
Expand All @@ -21,7 +21,7 @@ const config: SizeLimitConfig = [
name: "import {createNavigation} from 'next-intl/navigation' (react-client, production)",
path: 'dist/esm/production/navigation.react-client.js',
import: '{createNavigation}',
limit: '2.505 KB'
limit: '2.525 KB'
},
{
name: "import {createNavigation} from 'next-intl/navigation' (react-server, production)",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"use-intl": "workspace:^"
},
"peerDependencies": {
"next": "^13.0.0 || ^14.0.0 || ^15.0.0",
"next": "^13.3.0 || ^14.0.0 || ^15.0.0",
"react": "^17.0.0 || ^18.0.0",
"typescript": "^5.0.0"
},
Expand Down
6 changes: 2 additions & 4 deletions packages/next-intl/src/react-client/useLocale.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {useParams} from 'next/navigation.js';
import {useLocale as useBaseLocale} from 'use-intl/react';
import {LOCALE_SEGMENT_NAME} from '../shared/constants.tsx';
import useParams from '../shared/useParams.tsx';

export default function useLocale(): string {
// The types aren't entirely correct here. Outside of Next.js
// `useParams` can be called, but the return type is `null`.
const params = useParams() as ReturnType<typeof useParams> | null;
const params = useParams();

let locale;

Expand Down
46 changes: 25 additions & 21 deletions packages/next-intl/src/shared/NextIntlClientProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
import {render, screen} from '@testing-library/react';
import {it} from 'vitest';
import {it, vi} from 'vitest';
import {
NextIntlClientProvider,
useTranslations
} from '../index.react-client.tsx';

it('can use messages from the provider', () => {
function Component() {
const t = useTranslations();
return <>{t('message')}</>;
vi.mock('next/navigation.js', () => ({
useParams() {
return {locale: 'en'};
}
}));

render(
<NextIntlClientProvider locale="en" messages={{message: 'Hello'}}>
<Component />
</NextIntlClientProvider>
);

screen.getByText('Hello');
});
function Component() {
const t = useTranslations();
return <>{t('message', {price: 29000.5})}</>;
}

it('can override the locale from Next.js', () => {
function Component() {
const t = useTranslations();
return <>{t('message', {price: 29000.5})}</>;
}

render(
function TestProvider({locale}: {locale?: string}) {
return (
<NextIntlClientProvider
locale="de"
locale={locale}
messages={{message: '{price, number, ::currency/EUR}'}}
>
<Component />
</NextIntlClientProvider>
);
}

it('can use messages from the provider', () => {
render(<TestProvider locale="en" />);
screen.getByText('€29,000.50');
});

it('reads a default locale from params', () => {
render(<TestProvider />);
screen.getByText('€29,000.50');
});

it('can override the locale from Next.js', () => {
render(<TestProvider locale="de" />);
screen.getByText('29.000,50 €');
});
19 changes: 12 additions & 7 deletions packages/next-intl/src/shared/NextIntlClientProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

import {ComponentProps} from 'react';
import {IntlProvider} from 'use-intl/react';
import {LOCALE_SEGMENT_NAME} from './constants.tsx';
import useParams from './useParams.tsx';

type Props = Omit<ComponentProps<typeof IntlProvider>, 'locale'> & {
/** This is automatically received when being rendered from a Server Component. In all other cases, e.g. when rendered from a Client Component, a unit test or with the Pages Router, you can pass this prop explicitly. */
locale?: string;
};

export default function NextIntlClientProvider({locale, ...rest}: Props) {
// TODO: We could call `useParams` here to receive a default value
// for `locale`, but this would require dropping Next.js <13.
const paramsLocale = useParams()?.[LOCALE_SEGMENT_NAME];

if (!locale) {
throw new Error(
process.env.NODE_ENV !== 'production'
? 'Failed to determine locale in `NextIntlClientProvider`, please provide the `locale` prop explicitly.\n\nSee https://next-intl-docs.vercel.app/docs/configuration#locale'
: undefined
);
if (typeof paramsLocale === 'string') {
locale = paramsLocale;
} else {
throw new Error(
process.env.NODE_ENV !== 'production'
? 'Failed to determine locale in `NextIntlClientProvider`, please provide the `locale` prop explicitly.\n\nSee https://next-intl-docs.vercel.app/docs/configuration#locale'
: undefined
);
}
}

return <IntlProvider locale={locale} {...rest} />;
Expand Down
7 changes: 7 additions & 0 deletions packages/next-intl/src/shared/useParams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {useParams as useNextParams} from 'next/navigation.js';

export default function useParams() {
// The types aren't entirely correct here. Outside of Next.js
// `useParams` can be called, but the return type is `null`.
return useNextParams() as ReturnType<typeof useNextParams> | null;
}

0 comments on commit 829998e

Please sign in to comment.