|
| 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 | + |
| 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. |
0 commit comments