Skip to content

Commit ca4bad8

Browse files
committed
chore: add blog post on deprecating Intl wrappers and update documentation
1 parent 07e9cbf commit ca4bad8

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
title: Saying goodbye to Lingui's Intl wrappers
3+
authors: [yslpn]
4+
tags: [deprecation, intl, api]
5+
image: ./social-card.jpg
6+
description: Why we are deprecating i18n.date/time/number and asking everyone to use native Intl APIs.
7+
---
8+
9+
We are deprecating Lingui wrappers around `Intl` (`i18n.date`, `i18n.time`, `i18n.number` and the shared helpers) and recommending direct use of native `Intl.*`. This follows [issue #2265](https://github.com/lingui/js-lingui/issues/2265) and [PR #2386](https://github.com/lingui/js-lingui/pull/2386).
10+
11+
![Social preview](./social-card.jpg)
12+
13+
<!--truncate-->
14+
15+
## Why we are leaving the wrappers
16+
17+
- Coverage gap. `@lingui/core` wraps only `Intl.DateTimeFormat` and `Intl.NumberFormat`, but users expect parity for `Intl.ListFormat`, `Intl.RelativeTimeFormat`, `Intl.DisplayNames`, `Intl.Collator`, `Intl.Segmenter`, and more. Chasing the full [MDN Intl surface](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) adds work without unique value.
18+
- Maintenance and bundle weight. Every wrapper needs types, tests, caching, and examples. Native `Intl` is stable and cross platform already; extra code in Lingui is not buying us much.
19+
- Expectation creep. If we ship React hooks on top of these wrappers, people will want the same helpers outside React, which brings us back to maintaining a copy of every `Intl` method.
20+
- Special cases already covered. We do not need an `Intl.PluralRules` wrapper because plural selection lives inside the messageformat compiler (`@lingui/message-utils`). Exposing another public API only confuses the story.
21+
22+
## What full wrapper parity would require
23+
24+
To meet the expectations for a complete helper surface we would need wrappers and caching at least for `DateTimeFormat`, `NumberFormat`, `ListFormat`, `RelativeTimeFormat`, `DisplayNames`, `Collator`, `Segmenter`, `Locale`, `DurationFormat` (once it ships), plus `formatRange` and `formatToParts`. We are intentionally not going to maintain this matrix.
25+
26+
## Plan
27+
28+
- Now: mark the helpers as deprecated in code and docs without breaking existing apps.
29+
- Upcoming majors: keep them long enough to avoid blocking the ESM transition and give teams time to migrate; removal will happen in one of the next majors (not necessarily the very next one).
30+
- Removal: no separate warning blog is planned - please track the release notes for major versions.
31+
32+
## Migrating to native Intl
33+
34+
Pick the same locale precedence (`i18n.locales ?? i18n.locale`) and build your own formatters. Define helpers that read the current locale every time, so locale switches stay in sync:
35+
36+
```ts
37+
import { i18n } from "@lingui/core"
38+
39+
function getDateFormatter(options?: Intl.DateTimeFormatOptions) {
40+
const locales = i18n.locales ?? i18n.locale
41+
return new Intl.DateTimeFormat(locales, options)
42+
}
43+
44+
function getNumberFormatter(options?: Intl.NumberFormatOptions) {
45+
const locales = i18n.locales ?? i18n.locale
46+
return new Intl.NumberFormat(locales, options)
47+
}
48+
49+
export function formatOrderSummary(date: Date, total: number) {
50+
const dateFormatter = getDateFormatter({ dateStyle: "medium" })
51+
const numberFormatter = getNumberFormatter({
52+
style: "currency",
53+
currency: "EUR",
54+
})
55+
56+
return `${dateFormatter.format(date)} - ${numberFormatter.format(total)}`
57+
}
58+
```
59+
60+
In React you can create small reusable hooks that memoize formatters, accept formatting options, and still react to locale changes:
61+
62+
```tsx
63+
import { useLingui } from "@lingui/react"
64+
import { useMemo } from "react"
65+
66+
function useDateFormatter(options?: Intl.DateTimeFormatOptions) {
67+
const { i18n } = useLingui()
68+
69+
return useMemo(() => {
70+
const locales = i18n.locales ?? i18n.locale
71+
return new Intl.DateTimeFormat(locales, options)
72+
}, [i18n.locales, i18n.locale, options])
73+
}
74+
75+
function useNumberFormatter(options?: Intl.NumberFormatOptions) {
76+
const { i18n } = useLingui()
77+
78+
return useMemo(() => {
79+
const locales = i18n.locales ?? i18n.locale
80+
return new Intl.NumberFormat(locales, options)
81+
}, [i18n.locales, i18n.locale, options])
82+
}
83+
84+
function PriceLine({ date, total }: { date: Date; total: number }) {
85+
const dateOptions = useMemo(() => ({ dateStyle: "medium" }), [])
86+
const numberOptions = useMemo(
87+
() => ({
88+
style: "currency",
89+
currency: "USD",
90+
minimumFractionDigits: 2,
91+
}),
92+
[]
93+
)
94+
95+
const dateFormatter = useDateFormatter(dateOptions)
96+
const numberFormatter = useNumberFormatter(numberOptions)
97+
return (
98+
<span>
99+
{dateFormatter.format(date)} - {numberFormatter.format(total)}
100+
</span>
101+
)
102+
}
103+
```
104+
105+
## What about React hooks
106+
107+
We do not plan to add `useDateFormat` or `useNumberFormat`. Shipping hooks on top of the wrappers would trigger requests for the same helpers outside React and across frameworks, putting us back into the role of wrapping all of `Intl`. Teams can keep local hooks or shared formatters without Lingui adapters.
108+
109+
## FAQ
110+
111+
- **What about performance?** Creating `Intl.*Format` is inexpensive, and you can keep formatters at module scope or memoize them. No special internal cache from Lingui is required.
112+
- **What about `PluralRules`?** Lingui already uses it inside message compilation, so another public formatter is unnecessary.
113+
- **When will removal happen?** Deprecated APIs will remain for now; removal will land in one of the upcoming majors (not necessarily the very next). We do not plan a separate warning post - please watch major release notes.
135 KB
Loading

website/blog/authors.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,11 @@ vonovak:
1414
socials:
1515
x: vonovak
1616
github: vonovak
17+
yslpn:
18+
name: Yaroslav Slepukhin
19+
title: Contributor
20+
page: true
21+
image_url: https://github.com/yslpn.png
22+
socials:
23+
x: yslpn
24+
github: yslpn

website/docs/ref/core.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ Format a date using the conventional format for the active language.
234234
```ts
235235
new Intl.DateTimeFormat(i18n.locales ?? i18n.locale, format).format(d)
236236
```
237+
238+
For more context and migration tips, see the blog post: [Intl wrapper deprecation](/blog/2025/12/17/deprecating-intl-wrappers).
237239
:::
238240

239241
```ts
@@ -269,6 +271,8 @@ Format a number using the conventional format for the active language.
269271
```ts
270272
new Intl.NumberFormat(i18n.locales ?? i18n.locale, format).format(value)
271273
```
274+
275+
For more context and migration tips, see the blog post: [Intl wrapper deprecation](/blog/2025/12/17/deprecating-intl-wrappers).
272276
:::
273277

274278
```ts

0 commit comments

Comments
 (0)