Skip to content

Commit d6b5fd2

Browse files
authored
fix: Correctly parse date skeleton EEEE to a long weekday like "Tuesday" (upgrades to intl-messageformat@10 internally) (#1039)
1 parent 6d84093 commit d6b5fd2

File tree

8 files changed

+174
-29
lines changed

8 files changed

+174
-29
lines changed

docs/pages/docs/getting-started/pages-router.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {Tab, Tabs} from 'nextra-theme-docs';
22
import Callout from 'components/Callout';
33

4-
# Next.js internationalization (i18n) with the Pages Router
4+
# Next.js Pages Router internationalization (i18n)
55

66
While it's recommended to [use `next-intl` with the App Router](/docs/getting-started/app-router), the Pages Router is still fully supported.
77

docs/pages/docs/usage/dates-times.mdx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -179,32 +179,31 @@ You can customize the formatting by using date skeletons:
179179

180180
```json filename="en.json"
181181
{
182-
"ordered": "Ordered on {orderDate, date, ::yyyyMd}"
182+
// Renders e.g. "Ordered on Jul 9, 2024"
183+
"ordered": "Ordered on {orderDate, date, ::yyyyMMMd}"
183184
}
184185
```
185186

186187
Note the leading `::` that is used to indicate that a skeleton should be used.
187188

188189
**These formats from ICU are supported:**
189190

190-
| Symbol | Meaning |
191-
| :----: | :---------------------------- |
192-
| G | Era designator |
193-
| y | Year |
194-
| M | Month in year |
195-
| L | Stand-alone month in year |
196-
| d | Day in month |
197-
| E | Day of week |
198-
| e | Local day of week |
199-
| c | Stand-alone local day of week |
200-
| a | AM/PM marker |
201-
| h | Hour [1-12] |
202-
| H | Hour [0-23] |
203-
| K | Hour [0-11] |
204-
| k | Hour [1-24] |
205-
| m | Minute |
206-
| s | Second |
207-
| z | Time zone |
191+
| Symbol | Meaning | Pattern | Example |
192+
| :----: | :------------------------------------- | ---------------------------------------- | --------------------------------------------------- |
193+
| G | Era designator (includes the date) | G<br/>GGGG<br/>GGGGG | 7/9/2024 AD<br/>7/9/2024 Anno Domini<br/>7/9/2024 A |
194+
| y | Year | y<br/>yy<br/>yyyy | 2024<br/>24<br/>2024 |
195+
| M | Month in year | M<br/>MM<br/>MMM<br/>MMMM<br/>MMMMM<br/> | 7<br/>07<br/>Jul<br/>July<br/>J |
196+
| d | Day in month | d<br/>dd | 9<br/>09 |
197+
| E | Day of week | E<br/>EEEE<br/>EEEEE | Tue<br/>Tuesday<br/>T |
198+
| h | Hour (1-12) | h<br/>hh | 9 AM<br/>09 AM |
199+
| K | Hour (0-11) | K<br/>KK | 0 AM (12 AM with `h`)<br/>00 AM |
200+
| H | Hour (0-23) | HH | 09 |
201+
| k | Hour (1-24) | kk | 24 (00 with `H`) |
202+
| m | Minute (2 digits if used with seconds) | m<br/>mmss | 6<br/>06:03 |
203+
| s | Second (2 digits if used with minutes) | s<br/>mmss | 3<br/>06:03 |
204+
| z | Time zone | z<br/>zzzz | GMT+2<br/>Central European Summer Time |
205+
206+
Patterns can be combined with each other, therefore e.g. `yyyyMMMd` would return "Jul 9, 2024".
208207

209208
### Custom date and time formats
210209

packages/next-intl/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@
114114
"size-limit": [
115115
{
116116
"path": "dist/production/index.react-client.js",
117-
"limit": "13.055 KB"
117+
"limit": "15.735 KB"
118118
},
119119
{
120120
"path": "dist/production/index.react-server.js",
121-
"limit": "13.765 KB"
121+
"limit": "16.5 KB"
122122
},
123123
{
124124
"path": "dist/production/navigation.react-client.js",
@@ -134,7 +134,7 @@
134134
},
135135
{
136136
"path": "dist/production/server.react-server.js",
137-
"limit": "13.05 KB"
137+
"limit": "15.645 KB"
138138
},
139139
{
140140
"path": "dist/production/middleware.js",

packages/use-intl/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
],
6565
"dependencies": {
6666
"@formatjs/ecma402-abstract": "^1.11.4",
67-
"intl-messageformat": "^9.3.18"
67+
"intl-messageformat": "^10.5.11"
6868
},
6969
"peerDependencies": {
7070
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
@@ -90,7 +90,7 @@
9090
"size-limit": [
9191
{
9292
"path": "dist/production/index.js",
93-
"limit": "12.575 kB"
93+
"limit": "15.235 kB"
9494
}
9595
]
9696
}

packages/use-intl/src/core/createBaseTranslator.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,11 @@ function createBaseTranslatorImpl<
262262
{
263263
formatters: {
264264
getNumberFormat(locales, options) {
265-
return new Intl.NumberFormat(locales, options);
265+
return new Intl.NumberFormat(
266+
locales,
267+
// `useGrouping` was changed from a boolean later to a string enum or boolean, the type definition is outdated (https://tc39.es/proposal-intl-numberformat-v3/#grouping-enum-ecma-402-367)
268+
options as Intl.NumberFormatOptions
269+
);
266270
},
267271
getDateTimeFormat(locales, options) {
268272
// Workaround for https://github.com/formatjs/formatjs/issues/4279

packages/use-intl/test/core/createTranslator.test.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,105 @@ it('throws an error for non-alphanumeric value names', () => {
7373
expect(error.code).toBe('INVALID_MESSAGE');
7474
});
7575

76+
describe('dates in messages', () => {
77+
it.each([
78+
['G', '7/9/2024 AD'], // 🤔 Includes date
79+
['GG', '7/9/2024 AD'], // 🤔 Includes date
80+
['GGGG', '7/9/2024 Anno Domini'], // 🤔 Includes date
81+
['GGGGG', '7/9/2024 A'], // 🤔 Includes date
82+
83+
['y', '2024'],
84+
['yy', '24'],
85+
['yyyy', '2024'],
86+
87+
['M', '7'],
88+
['MM', '07'],
89+
['MMM', 'Jul'],
90+
['MMMM', 'July'],
91+
['MMMMM', 'J'],
92+
93+
// Same as M
94+
['L', '7'],
95+
['LL', '07'],
96+
['LLL', 'Jul'],
97+
['LLLL', 'July'],
98+
['LLLLL', 'J'],
99+
100+
['d', '9'],
101+
['dd', '09'],
102+
103+
['E', 'Tue'],
104+
['EE', 'Tue'],
105+
['EEE', 'Tue'],
106+
['EEEE', 'Tuesday'],
107+
['EEEEE', 'T'],
108+
// ['e', '7'] // 🤔 Not supported
109+
// ['ee', '07'] // 🤔 Not supported
110+
// ['eee', 'Jul'] // 🤔 Not supported
111+
112+
// ['eeee', 'Tuesday'], // ❌ "Tue"
113+
// ['eeeee', 'T'], // ❌ "Tuesday"
114+
// ['eeeeee', 'Tu'] // ❌ "T"
115+
116+
// ['c', '7'] // 🤔 Not supported
117+
// ['cc', '07'], // 🤔 Not supported
118+
// ['ccc', 'Jul'] // 🤔 Not supported
119+
120+
// ['cccc', 'Tuesday'] // ❌ "Tue"
121+
// ['ccccc', 'T'], // ❌ "Tuesday"
122+
// ['cccccc', 'Tu'] // ❌ "T"
123+
124+
// 🤔 Only in combination with time?
125+
// ['a', 'PM'] // ❌ "7/9/2024"
126+
// ['aa', 'PM'] // ❌ "7/9/2024"
127+
// ['aaa', 'PM'] // ❌ "7/9/2024"
128+
// ['aaaa', 'PM'] // ❌ "7/9/2024"
129+
// ['aaaaa', 'PM'] // ❌ "7/9/2024"
130+
131+
['h', '12 AM', '2024-07-09T22:00:00.000Z'],
132+
['h', '9 AM'],
133+
['hh', '09 AM'],
134+
135+
// ['H', '9'], // ❌ "09"
136+
['HH', '09'],
137+
['HH', '00', '2024-07-09T22:00:00.000Z'],
138+
139+
['K', '0 AM', '2024-07-09T22:00:00.000Z'],
140+
['KK', '00 AM', '2024-07-09T22:00:00.000Z'],
141+
['K', '9 AM'],
142+
['KK', '09 AM'],
143+
144+
// ['k', '9'], // ❌ "09"
145+
['kk', '09'],
146+
['kk', '24', '2024-07-09T22:00:00.000Z'],
147+
148+
['m', '6'],
149+
// ['mm', '06'] // ❌ "6"
150+
151+
['s', '3'],
152+
// ['ss', '03'], // ❌ "3"
153+
['mmss', '06:03'],
154+
155+
['z', '7/9/2024, GMT+2'], // 🤔 Includes date
156+
['zz', '7/9/2024, GMT+2'], // 🤔 Includes date
157+
['zzz', '7/9/2024, GMT+2'], // 🤔 Includes date
158+
['zzzz', '7/9/2024, Central European Summer Time'], // 🤔 Includes date
159+
160+
['yyyyMMMd', 'Jul 9, 2024'],
161+
[
162+
'GGGGyyyyMMMMEdhmszzzz',
163+
'Tue, July 9, 2024 Anno Domini at 9:06:03 AM Central European Summer Time'
164+
]
165+
])('%s: %s', (value, expected, dateString = '2024-07-09T07:06:03.320Z') => {
166+
const date = new Date(dateString);
167+
const t = createTranslator({
168+
locale: 'en',
169+
messages: {date: `{date, date, ::${value}}`}
170+
});
171+
expect(t('date', {date})).toBe(expected);
172+
});
173+
});
174+
76175
describe('t.rich', () => {
77176
it('can translate a message to a ReactNode', () => {
78177
const t = createTranslator({

packages/use-intl/test/react/useTranslations.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ describe('error handling', () => {
643643

644644
const error: IntlError = onError.mock.calls[0][0];
645645
expect(error.message).toBe(
646-
'INVALID_MESSAGE: Expected "date", "number", "plural", "select", "selectordinal", or "time" but "c" found.'
646+
'INVALID_MESSAGE: INVALID_ARGUMENT_TYPE ({value, currency})'
647647
);
648648
expect(error.code).toBe(IntlErrorCode.INVALID_MESSAGE);
649649
screen.getByText('price');

pnpm-lock.yaml

Lines changed: 45 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)