Skip to content

Commit

Permalink
Merge branch 'main' into canary
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn committed Aug 23, 2024
2 parents e98682b + 3a5ba36 commit 7c6f533
Show file tree
Hide file tree
Showing 76 changed files with 5,346 additions and 809 deletions.
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## 3.17.6 (2024-08-23)

### Bug Fixes

* Enable React Compiler ESLint plugin and fix relevant case ([#1281](https://github.com/amannn/next-intl/issues/1281)) ([606f4cc](https://github.com/amannn/next-intl/commit/606f4cc7bb821d685e3559dc674859fe13bb521a)) – by @amannn

## 3.17.5 (2024-08-23)

### Bug Fixes

* Lazy init message formatter for improved tree shaking in case `useTranslations` is only used in Server Components ([#1279](https://github.com/amannn/next-intl/issues/1279)) ([9f1725c](https://github.com/amannn/next-intl/commit/9f1725c20b8c542e46f197c2afa2b066e1293a7a)) – by @amannn

## 3.17.4 (2024-08-20)

### Bug Fixes

* Update `@formatjs/intl-localematcher` to latest version ([#1140](https://github.com/amannn/next-intl/issues/1140)) ([c217582](https://github.com/amannn/next-intl/commit/c217582cf47a3d0d65315e70eb9fd945efca7163)) – by @amannn

## 3.17.3 (2024-08-14)

### Bug Fixes

* Handle optional catch-all segments in navigation APIs if no value is provided and handle the case if a dynamic value appears multiple times in a pathname ([#1259](https://github.com/amannn/next-intl/issues/1259)) ([58ef482](https://github.com/amannn/next-intl/commit/58ef482eda383fc03a552a4f34b00c7b3136a4af)), closes [#1236](https://github.com/amannn/next-intl/issues/1236) – by @amannn

## 3.17.2 (2024-07-19)

### Bug Fixes

* Fix open redirect vulnerability for `localePrefix: 'as-necessary'` by sanitizing pathname in the middleware ([#1208](https://github.com/amannn/next-intl/issues/1208)) ([f42ac01](https://github.com/amannn/next-intl/commit/f42ac014c8a01124ab4eba46652a5224c5d7698e)), closes [#1207](https://github.com/amannn/next-intl/issues/1207) – by @hblee12294

## 3.17.1 (2024-07-15)

### Bug Fixes
Expand Down
6 changes: 3 additions & 3 deletions docs/components/CodeSnippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ function buildOutput() {
<span style={{color: 'var(--shiki-color-text)'}}> kB</span>
<span style={{color: 'var(--shiki-color-text)'}}>{' '}</span>
<span style={{color: 'var(--shiki-token-string-expression)'}}>
87.6 kB
89.7 kB
</span>
</span>
<span className="line">
Expand All @@ -344,7 +344,7 @@ function buildOutput() {
<span style={{color: 'var(--shiki-color-text)'}}> B</span>
<span style={{color: 'var(--shiki-color-text)'}}>{' '}</span>
<span style={{color: 'var(--shiki-token-string-expression)'}}>
86.2 kB
89.3 kB
</span>
</span>
<span className="line">
Expand All @@ -354,7 +354,7 @@ function buildOutput() {
<span style={{color: 'var(--shiki-color-text)'}}> kB</span>
<span style={{color: 'var(--shiki-color-text)'}}>{' '}</span>
<span style={{color: 'var(--shiki-token-string-expression)'}}>
89.3 kB
91.1 kB
</span>
</span>
<span className="line"> </span>
Expand Down
69 changes: 44 additions & 25 deletions docs/pages/docs/environments/actions-metadata-route-handlers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,7 @@ Next.js supports providing alternate URLs per language via the [`alternates` ent
<Tabs items={['Shared pathnames', 'Localized pathnames']}>
<Tab>

If you're use the same pathnames for all locales (i.e. you're not using the [`pathnames`](/docs/routing#pathnames) setting), you can iterate over an array of pathnames that your app supports and generate a sitemap entry for each pathname.

**Example:**
If you're using the same pathnames for all locales (i.e. you're not using the [`pathnames`](/docs/routing#pathnames) setting), you can assemble the sitemap based on the pathnames that your app supports.

```tsx
import {MetadataRoute} from 'next';
Expand All @@ -170,59 +168,80 @@ const defaultLocale = 'en' as const;
const locales = ['en', 'de'] as const;

// Adapt this as necessary
const pathnames = ['/', '/about'];
const host = 'https://acme.com';

export default function sitemap(): MetadataRoute.Sitemap {
function getUrl(pathname: string, locale: string) {
return `${host}/${locale}${pathname === '/' ? '' : pathname}`;
}
// Adapt this as necessary
return [
getEntry('/'),
getEntry('/users'),
getEntry('/users/1'),
getEntry('/users/2')
];
}

return pathnames.map((pathname) => ({
function getEntry(pathname: string) {
return {
url: getUrl(pathname, defaultLocale),
lastModified: new Date(),
alternates: {
languages: Object.fromEntries(
locales.map((locale) => [locale, getUrl(pathname, locale)])
)
}
}));
};
}

function getUrl(pathname: string, locale: string) {
return `${host}/${locale}${pathname === '/' ? '' : pathname}`;
}
```

</Tab>
<Tab>

If you're using the [`pathnames`](/docs/routing#pathnames) setting, you can use the keys of your already declared `pathnames` and generate an entry for each locale via the [`getPathname`](/docs/routing/navigation#getpathname) function.
If you're using the [`pathnames`](/docs/routing#pathnames) setting, you can generate sitemap entries for each locale by using the [`getPathname`](/docs/routing/navigation#getpathname) function.

```tsx
import {MetadataRoute} from 'next';
import {locales, pathnames, defaultLocale} from '@/config';
import {locales, defaultLocale} from '@/config';
import {getPathname} from '@/navigation';

// Adapt this as necessary
const host = 'https://acme.com';

export default function sitemap(): MetadataRoute.Sitemap {
const keys = Object.keys(pathnames) as Array<keyof typeof pathnames>;

function getUrl(
key: keyof typeof pathnames,
locale: (typeof locales)[number]
) {
const pathname = getPathname({locale, href: key});
return `${HOST}/${locale}${pathname === '/' ? '' : pathname}`;
}
// Adapt this as necessary
return [
getEntry('/'),
getEntry('/users'),
getEntry({
pathname: '/users/[id]',
params: {id: '1'}
}),
getEntry({
pathname: '/users/[id]',
params: {id: '2'}
})
];
}

return keys.map((key) => ({
url: getUrl(key, defaultLocale),
lastModified: new Date(),
type Href = Parameters<typeof getPathname>[0]['href'];

function getEntry(href: Href) {
return {
url: getUrl(href, defaultLocale),
alternates: {
languages: Object.fromEntries(
locales.map((locale) => [locale, getUrl(key, locale)])
locales.map((locale) => [locale, getUrl(href, locale)])
)
}
}));
};
}

function getUrl(href: Href, locale: (typeof locales)[number]) {
const pathname = getPathname({locale, href});
return `${host}/${locale}${pathname === '/' ? '' : pathname}`;
}
```

Expand Down
4 changes: 2 additions & 2 deletions docs/pages/docs/environments/core-library.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This allows you to use the same APIs that you know from `next-intl` in other env
2. React Native ([example](/examples#react-native))
3. Remix ([example](/examples#remix))
4. Testing environments like Jest ([example](https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/components/Navigation.spec.tsx))
5. Storybook (by using a [global decorator](https://storybook.js.org/docs/writing-stories/decorators#global-decorators))
5. [Storybook](/docs/workflows/storybook)

**Basic usage:**

Expand Down Expand Up @@ -68,7 +68,7 @@ These APIs receive all relevant configuration directly and don't rely on global
You can use these APIs as follows:

```tsx
import {createTranslator, createFormatter} from 'use-intl';
import {createTranslator, createFormatter} from 'use-intl/core';

const messages = {
basic: 'Hello {name}!',
Expand Down
14 changes: 11 additions & 3 deletions docs/pages/docs/environments/mdx.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ src
Now, in `page.tsx`, you can import the MDX content based on the user's locale:

```tsx filename="src/app/[locale]/page.tsx"
import {notFound} from 'next/navigation';

export default async function HomePage({params}) {
const Content = (await import(`./${params.locale}.mdx`)).default;
return <Content />;
try {
const Content = (await import(`./${params.locale}.mdx`)).default;
return <Content />;
} catch (error) {
notFound();
}
}
```

Expand All @@ -44,7 +50,9 @@ Components that invoke hooks from `next-intl` like `useTranslations` can natural
<Details id="rich-text">
<summary>Is MDX required to format rich text?</summary>

Not at all! Messages support [rich text syntax](/docs/usage/messages#rich-text), which can be used to provide formatting, structure and embedding of components.
Not at all! The built in message formatting of `next-intl` supports [rich text syntax](/docs/usage/messages#rich-text), which can be used to provide formatting, and to embed React components within messages.

MDX is best suited for cases where content varies significantly by locale. If all you're looking for is rich text formatting, the built-in message formatting may be an easier choice.

</Details>

Expand Down
33 changes: 2 additions & 31 deletions docs/pages/docs/environments/server-client-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -368,35 +368,6 @@ The component accepts the following props that are not serializable:
2. [`getMessageFallback`](/docs/usage/configuration#error-handling)
3. Rich text elements for [`defaultTranslationValues`](/docs/usage/configuration#default-translation-values)

To configure these, you can wrap `NextIntlClientProvider` with another component that is marked with `'use client'` and defines the relevant props:
To configure these, you can wrap `NextIntlClientProvider` with another component that is marked with `'use client'` and defines the relevant props.

```tsx filename="MyCustomNextIntlClientProvider.tsx"
'use client';

import {NextIntlClientProvider} from 'next-intl';

export default function MyCustomNextIntlClientProvider({
locale,
timeZone,
now,
...rest
}) {
return (
<NextIntlClientProvider
// Define non-serializable props here
defaultTranslationValues={{
i: (text) => <i>{text}</i>
}}
// Make sure to forward these props to avoid markup mismatches
locale={locale}
timeZone={timeZone}
now={now}
{...props}
/>
);
}
```

By doing this, your custom provider will already be part of the client-side bundle and can therefore define and pass functions as props.

**Important:** Be sure to pass explicit `locale`, `formats`, `timeZone` and `now` props to `NextIntlClientProvider` in this case, since the props aren't automatically inherited from a Server Component when you import `NextIntlClientProvider` from a Client Component.
See: [How can I provide non-serializable props like `onError` to `NextIntlClientProvider`?](/docs/usage/configuration#nextintlclientprovider-non-serializable-props)
47 changes: 41 additions & 6 deletions docs/pages/docs/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export const pathnames = {
} satisfies Pathnames<typeof locales>;
```

**Note:** Localized pathnames map to a single internal pathname that is created via the file-system based routing in Next.js. If you're using an external system like a CMS to localize pathnames, you'll typically implement this with a catch-all route like `[locale]/[[...slug]]`.
Localized pathnames map to a single internal pathname that is created via the file-system based routing in Next.js. In the example above, `/de/ueber-uns` will be handled by the page at `/[locale]/about/page.tsx`.

<Callout>
If you're using localized pathnames, you should use
Expand Down Expand Up @@ -261,11 +261,11 @@ import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
defaultLocale: 'en',
locales: ['en', 'fr'],
locales: ['en', 'de'],
pathnames: {
'/news/[slug]': {
en: '/news/[slug]',
fr: '/infos/[slug]'
de: '/neuigkeiten/[slug]'
}
}
});
Expand All @@ -275,10 +275,10 @@ Depending on whether `some-article` was included in [`generateStaticParams`](htt

```tsx
// Statically generated at build time
revalidatePath('/fr/news/some-article');
revalidatePath('/de/news/some-article');

// Dynamically generated at runtime:
revalidatePath('/fr/infos/some-article');
revalidatePath('/de/neuigkeiten/some-article');
```

When in doubt, you can revalidate both paths to be on the safe side.
Expand All @@ -301,7 +301,42 @@ In this case, the localized slug can either be provided by the backend or genera

A good practice is to include the ID in the URL, allowing you to retrieve the article based on this information from the backend. The ID can be further used to implement [self-healing URLs](https://mikebifulco.com/posts/self-healing-urls-nextjs-seo), where a redirect is added if the `articleSlug` doesn't match.

If you localize the values for dynamic segments, you might want to turn off [alternate links](#alternate-links) and provide your own implementation that considers localized values for dynamic segments.
If you localize the values for dynamic segments, you might want to turn off [alternate links](/docs/routing/middleware#alternate-links) and provide your own implementation that considers localized values for dynamic segments.

</Details>

<Details id="localized-pathnames-cms">
<summary>How do I integrate with an external system like a CMS that provides localized pathnames?</summary>

In case you're using a system like a CMS to configure localized pathnames, you'll typically implement this with a dynamic segment that catches all localized pathnames _instead_ of using the `pathnames` configuration from `next-intl`.

**Examples:**

1. All pathnames are handled by your CMS: `[locale]/[[...slug]]/page.tsx`
2. Some pathnames are handled by your CMS: `[locale]/blog/[...slug]/page.tsx`

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

type Props = {
params: {
locale: string;
slug: Array<string>;
};
};

export default async function CatchAllPage({params}: Props) {
const content = await fetchContent(params.locale, params.slug);
if (!content) notFound();

// ...
}
```

In this case, you'll likely want to disable [alternate links](/docs/routing/middleware#alternate-links) and provide your own implementation instead.

Furthermore, in case you provide a locale switcher, it might require special care to be able to switch between localized pathnames of the same page. A simplified implementation might always redirect to the home page instead.

</Details>

Expand Down
Loading

0 comments on commit 7c6f533

Please sign in to comment.