Skip to content

Commit dfa5ea0

Browse files
committed
docs: blog post draft
1 parent 486a7bd commit dfa5ea0

File tree

3 files changed

+254
-14
lines changed

3 files changed

+254
-14
lines changed

docs/src/pages/blog/index.mdx

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import StayUpdated from '@/components/StayUpdated.mdx';
44
# next-intl blog
55

66
<div className="flex flex-col gap-4 py-8">
7+
<BlogPostLink
8+
href="/blog/next-intl-4-0"
9+
title="next-intl 4.0"
10+
date="Dec XX, 2024"
11+
author="By Jan Amann"
12+
/>
713
<BlogPostLink
814
href="/blog/next-intl-3-22"
915
title="next-intl 3.22: Incrementally moving forward"

docs/src/pages/blog/next-intl-4-0.mdx

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
---
2+
title: next-intl 4.0
3+
---
4+
5+
import PartnerContentLink from '@/components/PartnerContentLink';
6+
import StayUpdated from '@/components/StayUpdated.mdx';
7+
8+
# next-intl 4.0
9+
10+
<small>Dec XX, 2024 · by Jan Amann</small>
11+
12+
(this post is still a draft)
13+
14+
After a year of feature development, this release mostly aims to clean up the API surface to ensure `next-intl` remains lean—there should be no big surprises. Many significant improvements have already been shipped in [minor versions](/blog/next-intl-3-22) previously.
15+
16+
However, this release also comes with a series of improvements that you might find useful.
17+
18+
Here's what's new in `next-intl@4.0`:
19+
20+
1. [**Modernized build output**](#modernized-build-output)
21+
2. [**Revamped augmented types**](#revamped-augmented-types)
22+
3. [**Strictly-typed locale**](#strictly-typed-locale)
23+
4. [**Strictly-typed ICU arguments**](#strictly-typed-icu-arguments)
24+
5. [**GDPR compliance**](#gdpr-compliance)
25+
6. [**Preparation for upcoming Next.js features**](#nextjs-future)
26+
27+
## Modernized build output
28+
29+
The build output of `next-intl` has been modernized and now leverages the following optimizations:
30+
31+
1. **ESM-only:** To enable enhanced tree-shaking and align with the modern JavaScript ecosystem, `next-intl` is now ESM-only. The only exception is `next-intl/plugin` which is published both as CommonJS as well as ESM, due to `next.config.js` still being popular.
32+
2. **Modern JSX transform:** The peer dependency for React has been bumped to v17 in order to use the more efficient, modern JSX transform.
33+
3. **Modern syntax:** Syntax is now compiled down to the Browserslist `defaults` query, which is a shortcut for `>0.5%, last 2 versions, Firefox ESR, not dead`—a baseline that is considered a reasonable target for modern apps.
34+
35+
With these changes, the bundle size of `next-intl` has been reduced by ~7% ([all details](https://github.com/amannn/next-intl/pull/1470)).
36+
37+
## Revamped augmented types
38+
39+
After type-safe [`Formats`](/docs/usage/configuration#formats) was added in `next-intl@3.20`, it became clear that a new API was needed that centralizes the registration of augmented types.
40+
41+
With `next-intl@4.0`, both `Messages` as well as `Formats` can now be registered under a single type that is scoped to `next-intl` and no longer affects the global scope:
42+
43+
```tsx
44+
// global.d.ts
45+
46+
import {formats} from '@/i18n/request';
47+
import en from './messages/en.json';
48+
49+
declare module 'next-intl' {
50+
interface AppConfig {
51+
Formats: typeof formats;
52+
Messages: typeof en;
53+
}
54+
}
55+
```
56+
57+
See the updated [TypeScript augmentation](/docs/workflows/typescript) guide.
58+
59+
## Strictly-typed locale
60+
61+
Building on the new type augmentation mechanism, `next-intl@4.0` now allows you to strictly type locales across your app:
62+
63+
```tsx
64+
// global.d.ts
65+
66+
import {routing} from '@/i18n/routing';
67+
68+
declare module 'next-intl' {
69+
interface AppConfig {
70+
// ...
71+
Locale: (typeof routing.locales)[number];
72+
}
73+
}
74+
```
75+
76+
By doing so, APIs like `useLocale()` or `<Link />` that either return or receive a `locale` will now pick up your app-specific `Locale` type, improving type safety across your app.
77+
78+
To simplify narrowing of `string`-based locales, a `hasLocale` function has been added. This can for example be used in [`i18n/request.ts`](/docs/getting-started/app-router/with-i18n-routing#i18n-request) to return a valid locale:
79+
80+
```tsx
81+
import {getRequestConfig} from 'next-intl/server';
82+
import {hasLocale} from 'next-intl';
83+
import {routing} from './routing';
84+
85+
export default getRequestConfig(async ({requestLocale}) => {
86+
// Typically corresponds to the `[locale]` segment
87+
const requested = await requestLocale;
88+
const locale = hasLocale(routing.locales, requested)
89+
? requested
90+
: routing.defaultLocale;
91+
92+
return {
93+
locale,
94+
messages: (await import(`../../messages/${locale}.json`)).default
95+
};
96+
});
97+
```
98+
99+
Furthermore, the `Locale` type can be imported into your app code in case you're passing a locale to another function and want to ensure type safety:
100+
101+
```tsx
102+
import {Locale} from 'next-intl';
103+
104+
async function getPosts(locale: Locale) {
105+
// ...
106+
}
107+
```
108+
109+
Note that strictly-typing the `Locale` is optional and can be used as desired in case you wish to have additional guardrails in your app.
110+
111+
## Strictly-typed ICU arguments
112+
113+
How type-safe can your app be?
114+
115+
The quest to bring type safety to the last corner of `next-intl` has led me down a rabbit hole with the discovery of an ICU parser by [Marco Schumacher](https://github.com/schummar)—written entirely in types. Marco kindly published his implementation for usage in `next-intl` with me only adding support for rich tags on top.
116+
117+
Check it out:
118+
119+
```tsx
120+
// "Hello {name}"
121+
t('message');
122+
// ^? Expected 2 arguments
123+
124+
// "Hello {name}"
125+
t('message', {});
126+
// ^? {name: string}
127+
128+
// "It's {today, date, long}"
129+
t('message', {});
130+
// ^? {today: Date}
131+
132+
// "Market share: {value, number, percent}"
133+
t('message', {});
134+
// ^? {value: number}
135+
136+
// "You have {count, plural, =0 {no followers yet} =1 {one follower} other {# followers}}."
137+
t('message', {});
138+
// ^? {count: number}
139+
140+
// "Country: {country, select, US {United States} CA {Canada} other {Other}}"
141+
t('message', {});
142+
// ^? {country: 'US' | 'CA' | (string & {})}
143+
144+
// "Please refer to <guidelines>the guidelines</guidelines>."
145+
t('message', {});
146+
// ^? {guidelines: (chunks: ReactNode) => ReactNode}
147+
```
148+
149+
(the types in these examples are slightly simplified, e.g. a date can also be provided as a timestamp)
150+
151+
With this type inference in place, you can now use autocompletion in your IDE to get suggestions for the available arguments of a given ICU message and catch potential errors early.
152+
153+
Due to a current limitation in TypeScript, this feature is opt-in for now. Please refer to the [strict arguments](/docs/workflows/typescript#messages-arguments) docs to learn how to enable it.
154+
155+
## GDPR compliance
156+
157+
In order to comply with the current GDPR regulations, the following changes have been made:
158+
159+
1. The locale cookie expiration has been decreased to 5 hours.
160+
2. The locale cookie is now only set when a user switches to a locale that doesn't match the `accept-language` header.
161+
162+
If you want to increase the cookie expiration, e.g. because you're informing users about the usage of cookies or if GDPR doesn't apply to your app, you can use the `maxAge` attribute to do so:
163+
164+
```tsx
165+
// i18n/routing.tsx
166+
167+
import {defineRouting} from 'next-intl/routing';
168+
169+
export const routing = defineRouting({
170+
// ...
171+
172+
localeCookie: {
173+
// Expire in one year
174+
maxAge: 60 * 60 * 24 * 365
175+
}
176+
});
177+
```
178+
179+
As part of this change, disabling a cookie now requires you to set [`localeCookie: false`](/docs/routing#locale-cookie) in your routing configuration. Previously, `localeDetection: false` ambiguously also disabled the cookie from being set, but since a separate `localeCookie` option was introduced recently, this should now be used instead.
180+
181+
Learn more in the [locale cookie](/docs/routing#locale-cookie) docs.
182+
183+
## Preparation for upcoming Next.js features
184+
185+
To ensure that the sails of `next-intl` are set for a steady course in the upcoming future, I've investigated the implications of upcoming Next.js features like [Partial Prerendering](https://nextjs.org/docs/app/api-reference/next-config-js/ppr) and [`dynamicIO`](https://nextjs.org/docs/canary/app/api-reference/config/next-config-js/dynamicIO) for `next-intl`.
186+
187+
This led to two minor changes:
188+
189+
1. If you don't already have a `NextIntlClientProvider` in your app that wraps all Client Components, you now have to add one (see [PR #1541](https://github.com/amannn/next-intl/pull/1541) for details).
190+
2. If you're using `format.relativeTime` in Client Components, you may need to provide the `now` argument explicitly now (see [PR #1536](https://github.com/amannn/next-intl/pull/1536) for details).
191+
192+
While the mentioned Next.js features are still under development and may change, these two changes seem reasonable to me in any case—and ideally will be all that's necessary to adapt for `next-intl` to get the most out of these upcoming capabilities.
193+
194+
As a closing note for this section, it seems like another feature is on its way to Next.js: [`rootParams`](https://github.com/vercel/next.js/pull/72837).
195+
196+
```tsx
197+
import {unstable_rootParams as rootParams} from 'next/server';
198+
199+
async function Component() {
200+
// The ability to read params deeply in
201+
// Server Components ... finally!
202+
const {locale} = await rootParams();
203+
}
204+
```
205+
206+
If things go well, I think this will finally fill in the [missing piece](https://github.com/vercel/next.js/discussions/58862) that enables apps with i18n routing to support static rendering without workarounds like `setRequestLocale`.
207+
208+
## Other breaking changes
209+
210+
1. Return type-safe messages from `useMessages` and `getMessages` (see [PR #1489](https://github.com/amannn/next-intl/pull/1489))
211+
2. Inherit context in case nested `NextIntlClientProvider` instances are present (see [PR #1413](https://github.com/amannn/next-intl/pull/1413))
212+
3. Automatically inherit formats when `NextIntlClientProvider` is rendered from a Server Component (see [PR #1191](https://github.com/amannn/next-intl/pull/1191))
213+
4. Require locale to be returned from `getRequestConfig` (see [PR #1486](https://github.com/amannn/next-intl/pull/1486))
214+
5. Bump minimum required typescript version to 5 for projects using TypeScript (see [PR #1481](https://github.com/amannn/next-intl/pull/1481))
215+
6. Remove deprecated APIs (see [PR #1479](https://github.com/amannn/next-intl/pull/1479))
216+
7. Remove deprecated APIs pt. 2 (see [PR #1482](https://github.com/amannn/next-intl/pull/1482))
217+
218+
## Upgrade now
219+
220+
For a smooth upgrade, please initially upgrade to the latest v3.x version and check for deprecation warnings.
221+
222+
Once all warnings are resolved, you can upgrade by running:
223+
224+
```
225+
npm install next-intl@v4
226+
```
227+
228+
## Thank you
229+
230+
I want to sincerely thank everyone who has helped to make `next-intl` what it is today. A special thank you goes to <PartnerContentLink href="https://crowdin.com/">Crowdin</PartnerContentLink>, the primary sponsor of `next-intl`, enabling me to regularly dedicate time for this project.
231+
232+
—Jan
233+
234+
<StayUpdated />

docs/src/pages/docs/workflows/typescript.mdx

+14-14
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,22 @@ function UserProfile({user}) {
146146
}
147147
```
148148

149-
TypeScript currently has a [limitation](https://github.com/microsoft/TypeScript/issues/32063) where it infers the types of an imported JSON module as rather wide. Due to this, `next-intl` provides a stopgap solution that allows you to generate an accompanying `.d.json.ts` file for the messages that you're assigning to your `AppConfig`.
149+
TypeScript currently has a [limitation](https://github.com/microsoft/TypeScript/issues/32063) where it infers values of imported JSON modules as loose types like `string` instead of the actual value. To bridge this gap for the time being, `next-intl` can generate an accompanying `.d.json.ts` file for the messages that you're assigning to your `AppConfig`.
150150

151151
**Usage:**
152152

153-
1. Enable the `createMessagesDeclaration` setting in your Next.js config:
153+
1. Add support for JSON type declarations in your `tsconfig.json`:
154+
155+
```json filename="tsconfig.json"
156+
{
157+
"compilerOptions": {
158+
// ...
159+
"allowArbitraryExtensions": true
160+
}
161+
}
162+
```
163+
164+
2. Enable the `createMessagesDeclaration` setting in your Next.js config:
154165

155166
```tsx filename="next.config.mjs"
156167
import {createNextIntlPlugin} from 'next-intl/plugin';
@@ -166,25 +177,14 @@ const withNextIntl = createNextIntlPlugin({
166177
// ...
167178
```
168179

169-
2. Add support for JSON type declarations in your `tsconfig.json`:
170-
171-
```json filename="tsconfig.json"
172-
{
173-
"compilerOptions": {
174-
// ...
175-
"allowArbitraryExtensions": true
176-
}
177-
}
178-
```
179-
180180
With this setup in place, you'll see a new declaration file generated in your `messages` directory once you run `next dev` or `next build`:
181181

182182
```diff
183183
messages/en.json
184184
+ messages/en.d.json.ts
185185
```
186186

187-
This declaration file will provide the exact types for the messages that you're using in `AppConfig`, enabling type safety for message arguments.
187+
This declaration file will provide the exact types for the JSON messages that you're importing and assigning to `AppConfig`, enabling type safety for message arguments.
188188

189189
To keep your code base tidy, you can ignore this file in Git:
190190

0 commit comments

Comments
 (0)