Skip to content

Commit

Permalink
docs: Add blog post
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn committed Oct 18, 2024
1 parent 28e0389 commit c533ea3
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Props = Omit<ComponentProps<typeof Link>, 'children'> & {
type?: 'article' | 'video';
};

export default function CommunityLink({
export default function BlogPostLink({
author,
date,
title,
Expand Down
32 changes: 24 additions & 8 deletions docs/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@ import minLight from 'shiki/themes/min-light.mjs';

const lightTheme = {
...minLight,
tokenColors: minLight.tokenColors.map((color) => {
// Increase the contrast of comments
if (color.scope?.includes('comment')) {
return {...color, settings: {foreground: '#808ea3'}};
} else {
return color;
}
})
tokenColors: minLight.tokenColors
.map((color) => {
// Increase the contrast of comments
if (color.scope?.includes('comment')) {
return {...color, settings: {foreground: '#808ea3'}};
} else {
return color;
}
})
// Add colors for diffs
.concat([
{
scope: 'markup.deleted.diff',
settings: {
foreground: '#D32F2F'
}
},
{
scope: 'markup.inserted.diff',
settings: {
foreground: '#22863A'
}
}
])
};

const withNextra = nextra({
Expand Down
14 changes: 10 additions & 4 deletions docs/pages/blog/index.mdx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import CommunityLink from 'components/CommunityLink';
import BlogPostLink from 'components/BlogPostLink';

# next-intl blog

<div className="flex flex-col gap-4 py-8">
<CommunityLink
<BlogPostLink
href="/blog/next-intl-3-22"
title="next-intl 3.22: Incrementally moving forward"
date="Oct 21, 2024"
author="By Jan Amann"
/>
<BlogPostLink
href="/blog/date-formatting-nextjs"
title="Reliable date formatting in Next.js"
date="Sep 25, 2024"
author="By Jan Amann"
/>
<CommunityLink
<BlogPostLink
href="/blog/next-intl-3-0"
title="next-intl 3.0"
date="Nov 14, 2023"
author="By Jan Amann"
/>
<CommunityLink
<BlogPostLink
href="/blog/translations-outside-of-react-components"
title="How (not) to use translations outside of React components"
date="Apr 21, 2023"
Expand Down
215 changes: 215 additions & 0 deletions docs/pages/blog/next-intl-3-22.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
---
title: 'next-intl 3.22: Incrementally moving forward'
---

# next-intl 3.22: Incrementally moving forward

<small>Oct 21, 2024 · by Jan Amann</small>

Over the past few months, a number of minor releases have been rolled out, each providing incremental improvements to `next-intl`. And while each of these releases has been an improvement on its own, they've also been part of a larger progression towards a more unified and streamlined API surface in general.

Today, another minor release was published that marks the final step of this progression: `[email protected]`.

While this release is fully backwards-compatible, it includes some modern alternatives to existing APIs. Therefore, it's a good time to take a moment to review the changes and consider migrating where relevant.

**Recent improvements:**

1. [**`defineRouting`**](#define-routing): Type-safe, centralized routing configuration (introduced in `v3.18`)
2. [**`i18n/request.ts`**](#i18n-request): Streamlined file organization (introduced in `v3.19`)
3. [**`await requestLocale`**](#await-request-locale): Preparation for Next.js 15 (introduced in `v3.22`)
4. [**`createNavigation`**](#create-navigation): Streamlined navigation APIs (introduced in `v3.22`)
5. [**`defaultTranslationValues`**](#default-translation-values): Deprecated in favor of a user-land pattern (introduced in `v3.22`)

Let's take a look at these changes in more detail.

## `defineRouting` [#define-routing]

Previously, configuration that was shared among the middleware and the navigation APIs required special care to be kept in sync. With the introduction of [`defineRouting`](/docs/routing#define-routing), this configuration can now be centralized in a type-safe manner:

```tsx filename="i18n/routing.ts"
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
locales: ['en-US', 'en-GB'],
defaultLocale: 'en-US',
localePrefix: {
mode: 'always',
prefixes: {
'en-US': '/us',
'en-GB': '/uk'
}
},
pathnames: {
'/': '/',
'/organization': {
'en-US': '/organization',
'en-GB': '/organisation'
}
}
});
```

This `routing` config is typically defined in a central place like `i18n/routing.ts` and can be used where previously individual configuration was required:

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';

export default createMiddleware(routing);

// ...
```

The docs have been consistently updated to reflect these changes and also suggest the creation of navigation APIs directly in `i18n/routing.ts`. If you prefer to keep your navigation APIs separately, that's fine too of course.

## `i18n/request.ts` [#i18n-request]

If you're using i18n routing, you'll typically end up with two configuration files for `next-intl`:

1. Routing configuration (defined via [`defineRouting`](/docs/routing#define-routing))
2. Request configuration (defined via [`getRequestConfig`](/docs/usage/configuration#i18n-request))

While (2) was historically suggested at `i18n.ts`, it's now suggested to be placed next to the routing configuration in order to streamline the organization of files:

```
└── src
└── i18n
├── routing.ts (1)
└── request.ts (2)
```

Due to this, `i18n/request.ts` is now considered the new default and is suggested throughout the docs.

If you prefer to use a custom location instead, you can do so by providing it in `next.config.mjs`:

```tsx filename="next.config.mjs"
import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin('./somewhere/else/request.ts');

// ...
```

## `await requestLocale` in `getRequestConfig` [#await-request-locale]

Next.js 15 is on the horizon and introduces a change for request APIs to [turn async](https://nextjs.org/blog/next-15-rc2#async-request-apis-breaking-change). In preparation for this change, the `locale` that is passed to [`getRequestConfig`](/docs/usage/configuration#i18n-request) has been replaced by `requestLocale` with some minor differences:

1. `requestLocale` needs to be awaited
2. The result can be `undefined` in case the middleware didn't run as part of the request, therefore requiring a fallback to potentially be assigned
3. The `locale` should now also be returned from `getRequestConfig`

```diff
+ import {routing} from './i18n/routing';

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

- // Validate that the incoming `locale` parameter is valid
- if (!routing.locales.includes(locale as any)) notFound();
+ // Ensure that the incoming locale is valid
+ if (!locale || !routing.locales.includes(locale as any)) {
+ locale = routing.defaultLocale;
+ }

return {
+ locale,
// ...
};
});
```

While slightly more verbose, this change allows you to return a fallback locale for edge cases where the middleware can't run (e.g. a global country selection page at `/` or a global 404 page). If you'd like to return a 404 for invalid locales inside the `[locale]` segment, you can still do so in your [root layout](/docs/getting-started/app-router/with-i18n-routing#layout).

Note that this change only applies in case you're using [i18n routing](/docs/getting-started/app-router).

## `createNavigation` [#create-navigation]

The newly added [`createNavigation`](/docs/routing/navigation) function supersedes these previously provided APIs:

1. `createSharedPathnamesNavigation`
2. `createLocalizedPathnamesNavigation`

This new function is a reimplementation of the existing navigation functionality and unifies the API for both use cases, while also fixing a few quirks of the previous APIs. Additionally, this implementation has been updated in anticipation of Next.js 15 to run without warnings.

**Usage**

```tsx filename="i18n/routing.ts"
import {createNavigation} from 'next-intl/navigation';
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting(/* ... */);

export const {Link, redirect, usePathname, useRouter} =
createNavigation(routing);
```

**Migrating to `createNavigation`**

`createNavigation` is generally considered a drop-in replacement, but a few changes might be necessary:

1. `createNavigation` is expected to receive your complete routing configuration. Ideally, you define this via the [`defineRouting`](/docs/routing#define-routing) function and pass the result to `createNavigation`. The one edge case is if you're [locales aren't known at build time](/docs/routing/navigation#locales-unknown).
2. If you've used `createLocalizedPathnamesNavigation` and have [composed the `Link`](/docs/routing/navigation#link-composition) with its `href` prop, you should no longer provide the generic `Pathname` type argument.

```diff
- ComponentProps<typeof Link<Pathname>>
+ ComponentProps<typeof Link>
```

3. If you've used [`redirect`](/docs/routing/navigation#redirect), you now have to provide an explicit locale (even if it's just the [current locale](/docs/usage/configuration#locale)). The previously passed href (whether it was a string or an object) now needs to be wrapped in an object and assigned to the `href` prop. This change was necessary in preparation for Next.js 15 (see [#1375](https://github.com/amannn/next-intl/issues/1375)).

```tsx
// Retrieving the current locale
// ... in regular components:
const locale = useLocale();
// ... in async components:
const locale = await getLocale();
```

```diff
- redirect('/about')
+ redirect({href: '/about', locale})

- redirect({pathname: '/users/[id]', params: {id: 2}})
+ redirect({href: {pathname: '/users/[id]', params: {id: 2}}, locale})
```

4. If you've used [`getPathname`](/docs/routing/navigation#getpathname) and have previously manually prepended a locale prefix, you should no longer do so—`getPathname` now takes care of this depending on your routing strategy.

```diff
- '/'+ locale + getPathname(/* ... */)
+ getPathname(/* ... */);
```

5. If you're using a combination of `localePrefix: 'as-needed'` and `domains` and you're using `getPathname`, you now need to provide a `domain` argument (see [Special case: Using `domains` with `localePrefix: 'as-needed'`](/docs/routing#domains-localeprefix-asneeded))

## `defaultTranslationValues` (deprecated) [#default-translation-values]

[`defaultTranslationValues`](/docs/usage/configuration#default-translation-values) allow you to share global values to be used in messages across your app. The most common case are shared rich text elements (e.g. `b: (chunks) => <b>{chunks}</b>`).

However, over time this feature has shown drawbacks:

1. We can't serialize them automatically across the RSC boundary (see [#611](https://github.com/amannn/next-intl/issues/611))
2. They get in the way of type-safe arguments (see [#410](https://github.com/amannn/next-intl/issues/410))

Due to this, the feature will be deprecated and the docs now suggest a [better alternative](/docs/usage/messages#rich-text-reuse-tags).

## What's next?

These release notes were originally planned to be posted as part of the next major version. However, I'm really happy that it was possible to roll out all of these changes incrementally and without breaking changes.

All of these changes were inspired by many conversations with users and contributors of `next-intl`. It's quite a privileged position to be in, being able to receive feedback from so many of you, and ensuring the spectrum of i18n use cases can be served by a lean, cohesive package. A big thank you also goes out to everyone who helped to test pre-releases of this version.

With these changes out of the way, users can upgrade to modern APIs at their own pace in the v3 range. That being said, the 4.0 release is already in the works, but mostly aims to clean up deprecated functionality to ensure `next-intl` remains minimalistic.

While this post focuses on recent changes that might require action, it's also worth noting that many other improvements have been made in the past months that don't fit into this post. For instance, we saw first-time contributions with the introduction of [optional messages](/docs/usage/messages#t-has) by [Théo Holander](https://github.com/tholander) and [type-safe global formats](http://localhost:3000/docs/workflows/typescript#formats) by [Gabriel Bianchi](https://github.com/dBianchii).

—Jan

**Stay in the loop:**

- [GitHub releases](https://github.com/amannn/next-intl/releases)
- [X (Jan Amann)](https://twitter.com/jamannnnnn)
2 changes: 1 addition & 1 deletion docs/pages/docs/routing/navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,4 @@ const pathname = getPathname({

As part of `[email protected]`, these functions have been replaced by a single `createNavigation` function, which unifies the API for both use cases and also fixes a few quirks in the previous APIs. Going forward, `createNavigation` is recommended and the previous functions are marked as deprecated.

While `createNavigation` is mostly API-compatible, there are some minor differences that should be noted. Please refer to [PR #1316](https://github.com/amannn/next-intl/pull/1316) for full details.
While `createNavigation` is mostly API-compatible, there are some minor differences that should be noted. Please refer to the [3.22 announcement post](/blog/next-intl-3-22#create-navigation) for full details.

0 comments on commit c533ea3

Please sign in to comment.