Skip to content

Commit

Permalink
feat!: Revamp augmented types and add support for typed Locale (#1495)
Browse files Browse the repository at this point in the history
**Changes**
- Revamps the API to augment types by getting rid of the global
`IntlMessages` and `IntlFormats` in favor of a more general `AppConfig`
that is scoped to `next-intl`.
- Adds support for strictly-typing the locale across `useLocale` as well
as the navigation APIs.
- Adds `import {Locale} from 'next-intl';` as a convenience API to be
reused wherever a `locale` is passed around.
- Add `hasLocale(locales, candidate)` API for simplified checking of
whether a locale is available with TypeScript.
- Adds a new `import {Messages} from 'next-intl;` type that corresponds
to the `Messages` you've provided in `AppConfig` (probably rarely
needed).


**Example:**

```tsx
// global.d.ts

import {routing} from '@/i18n/routing';
import {formats} from '@/i18n/request';
import en from './messages/en.json';

declare module 'next-intl' {
  interface AppConfig {
    Locale: (typeof routing.locales)[number];
    Formats: typeof formats;
    Messages: typeof en;
  }
}
```

**→ [Proposed
docs](https://next-intl-docs-git-feat-augmented-config-next-intl.vercel.app/docs/workflows/typescript)**

Fixes #1377
  • Loading branch information
amannn authored Nov 1, 2024
1 parent d83abc2 commit a7aaf56
Show file tree
Hide file tree
Showing 96 changed files with 979 additions and 578 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ Note that by default, `next-intl` returns [the `link` response header](/docs/rou

Next.js supports providing alternate URLs per language via the [`alternates` entry](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generate-a-localized-sitemap) as of version 14.2. You can use your default locale for the main URL and provide alternate URLs based on all locales that your app supports. Keep in mind that also the default locale should be included in the `alternates` object.

```tsx filename="app/sitemap.ts" {4-5,8-9}
```tsx filename="app/sitemap.ts" {5-6,9-10}
import {MetadataRoute} from 'next';
import {Locale} from 'next-intl';
import {routing, getPathname} from '@/i18n/routing';

// Adapt this as necessary
Expand All @@ -179,7 +180,7 @@ function getEntry(href: Href) {
};
}

function getUrl(href: Href, locale: (typeof routing.locales)[number]) {
function getUrl(href: Href, locale: Locale) {
const pathname = getPathname({locale, href});
return host + pathname;
}
Expand All @@ -206,12 +207,16 @@ You can use `next-intl` in [Route Handlers](https://nextjs.org/docs/app/building

```tsx filename="app/api/hello/route.tsx"
import {NextResponse} from 'next/server';
import {hasLocale} from 'next-intl';
import {getTranslations} from 'next-intl/server';

export async function GET(request) {
// Example: Receive the `locale` via a search param
const {searchParams} = new URL(request.url);
const locale = searchParams.get('locale');
if (!hasLocale(locales, locale)) {
return NextResponse.json({error: 'Invalid locale'}, {status: 400});
}

const t = await getTranslations({locale, namespace: 'Hello'});
return NextResponse.json({title: t('title')});
Expand Down
7 changes: 4 additions & 3 deletions docs/src/pages/docs/environments/error-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,15 @@ export default function RootLayout({children}) {
}
```

For the 404 page to render, we need to call the `notFound` function in the root layout when we detect an incoming `locale` param that isn't a valid locale.
For the 404 page to render, we need to call the `notFound` function in the root layout when we detect an incoming `locale` param that isn't valid.

```tsx filename="app/[locale]/layout.tsx"
import {hasLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';

export default function LocaleLayout({children, params: {locale}}) {
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) {
if (!hasLocale(routing.locales, locale)) {
notFound();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,15 @@ When using features from `next-intl` in Server Components, the relevant configur

```tsx filename="src/i18n/request.ts"
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';

export default getRequestConfig(async ({requestLocale}) => {
// This typically corresponds to the `[locale]` segment
let locale = await requestLocale;

// Ensure that a valid locale is used
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
// Typically corresponds to the `[locale]` segment
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;

return {
locale,
Expand Down Expand Up @@ -186,7 +185,7 @@ const withNextIntl = createNextIntlPlugin(
The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n/request.ts` to Client Components via `NextIntlClientProvider`.

```tsx filename="app/[locale]/layout.tsx"
import {NextIntlClientProvider} from 'next-intl';
import {NextIntlClientProvider, Locale, hasLocale} from 'next-intl';
import {getMessages} from 'next-intl/server';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';
Expand All @@ -196,10 +195,9 @@ export default async function LocaleLayout({
params: {locale}
}: {
children: React.ReactNode;
params: {locale: string};
params: {locale: Locale};
}) {
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) {
if (!hasLocale(routing.locales, locale)) {
notFound();
}

Expand Down Expand Up @@ -297,12 +295,12 @@ export function generateStaticParams() {

```tsx filename="app/[locale]/layout.tsx"
import {setRequestLocale} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';

export default async function LocaleLayout({children, params: {locale}}) {
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) {
if (!hasLocale(routing.locales, locale)) {
notFound();
}

Expand Down
3 changes: 2 additions & 1 deletion docs/src/pages/docs/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,12 @@ In case you're using a system like a CMS to configure localized pathnames, you'l

```tsx filename="page.tsx"
import {notFound} from 'next';
import {Locale} from 'next-intl';
import {fetchContent} from './cms';

type Props = {
params: {
locale: string;
locale: Locale;
slug: Array<string>;
};
};
Expand Down
Loading

0 comments on commit a7aaf56

Please sign in to comment.