Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Correctly parse date skeleton EEEE to a long weekday like "Tuesday" (upgrades to intl-messageformat@10 internally) #1039

Merged
merged 6 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/pages/docs/getting-started/pages-router.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Tab, Tabs} from 'nextra-theme-docs';
import Callout from 'components/Callout';

# Next.js internationalization (i18n) with the Pages Router
# Next.js Pages Router internationalization (i18n)

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

Expand Down
37 changes: 18 additions & 19 deletions docs/pages/docs/usage/dates-times.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -179,32 +179,31 @@ You can customize the formatting by using date skeletons:

```json filename="en.json"
{
"ordered": "Ordered on {orderDate, date, ::yyyyMd}"
// Renders e.g. "Ordered on Jul 9, 2024"
"ordered": "Ordered on {orderDate, date, ::yyyyMMMd}"
}
```

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

**These formats from ICU are supported:**

| Symbol | Meaning |
| :----: | :---------------------------- |
| G | Era designator |
| y | Year |
| M | Month in year |
| L | Stand-alone month in year |
| d | Day in month |
| E | Day of week |
| e | Local day of week |
| c | Stand-alone local day of week |
| a | AM/PM marker |
| h | Hour [1-12] |
| H | Hour [0-23] |
| K | Hour [0-11] |
| k | Hour [1-24] |
| m | Minute |
| s | Second |
| z | Time zone |
| Symbol | Meaning | Pattern | Example |
| :----: | :------------------------------------- | ---------------------------------------- | --------------------------------------------------- |
| 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 |
| y | Year | y<br/>yy<br/>yyyy | 2024<br/>24<br/>2024 |
| M | Month in year | M<br/>MM<br/>MMM<br/>MMMM<br/>MMMMM<br/> | 7<br/>07<br/>Jul<br/>July<br/>J |
| d | Day in month | d<br/>dd | 9<br/>09 |
| E | Day of week | E<br/>EEEE<br/>EEEEE | Tue<br/>Tuesday<br/>T |
| h | Hour (1-12) | h<br/>hh | 9 AM<br/>09 AM |
| K | Hour (0-11) | K<br/>KK | 0 AM (12 AM with `h`)<br/>00 AM |
| H | Hour (0-23) | HH | 09 |
| k | Hour (1-24) | kk | 24 (00 with `H`) |
| m | Minute (2 digits if used with seconds) | m<br/>mmss | 6<br/>06:03 |
| s | Second (2 digits if used with minutes) | s<br/>mmss | 3<br/>06:03 |
| z | Time zone | z<br/>zzzz | GMT+2<br/>Central European Summer Time |

Patterns can be combined with each other, therefore e.g. `yyyyMMMd` would return "Jul 9, 2024".

### Custom date and time formats

Expand Down
6 changes: 3 additions & 3 deletions packages/next-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@
"size-limit": [
{
"path": "dist/production/index.react-client.js",
"limit": "13.055 KB"
"limit": "15.735 KB"
},
{
"path": "dist/production/index.react-server.js",
"limit": "13.765 KB"
"limit": "16.5 KB"
},
{
"path": "dist/production/navigation.react-client.js",
Expand All @@ -134,7 +134,7 @@
},
{
"path": "dist/production/server.react-server.js",
"limit": "13.05 KB"
"limit": "15.645 KB"
},
{
"path": "dist/production/middleware.js",
Expand Down
4 changes: 2 additions & 2 deletions packages/use-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
],
"dependencies": {
"@formatjs/ecma402-abstract": "^1.11.4",
"intl-messageformat": "^9.3.18"
"intl-messageformat": "^10.5.11"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
Expand All @@ -90,7 +90,7 @@
"size-limit": [
{
"path": "dist/production/index.js",
"limit": "12.575 kB"
"limit": "15.235 kB"
}
]
}
6 changes: 5 additions & 1 deletion packages/use-intl/src/core/createBaseTranslator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,11 @@ function createBaseTranslatorImpl<
{
formatters: {
getNumberFormat(locales, options) {
return new Intl.NumberFormat(locales, options);
return new Intl.NumberFormat(
locales,
// `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)
options as Intl.NumberFormatOptions
);
},
getDateTimeFormat(locales, options) {
// Workaround for https://github.com/formatjs/formatjs/issues/4279
Expand Down
99 changes: 99 additions & 0 deletions packages/use-intl/test/core/createTranslator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,105 @@ it('throws an error for non-alphanumeric value names', () => {
expect(error.code).toBe('INVALID_MESSAGE');
});

describe('dates in messages', () => {
it.each([
['G', '7/9/2024 AD'], // 🤔 Includes date
['GG', '7/9/2024 AD'], // 🤔 Includes date
['GGGG', '7/9/2024 Anno Domini'], // 🤔 Includes date
['GGGGG', '7/9/2024 A'], // 🤔 Includes date

['y', '2024'],
['yy', '24'],
['yyyy', '2024'],

['M', '7'],
['MM', '07'],
['MMM', 'Jul'],
['MMMM', 'July'],
['MMMMM', 'J'],

// Same as M
['L', '7'],
['LL', '07'],
['LLL', 'Jul'],
['LLLL', 'July'],
['LLLLL', 'J'],

['d', '9'],
['dd', '09'],

['E', 'Tue'],
['EE', 'Tue'],
['EEE', 'Tue'],
['EEEE', 'Tuesday'],
['EEEEE', 'T'],
// ['e', '7'] // 🤔 Not supported
// ['ee', '07'] // 🤔 Not supported
// ['eee', 'Jul'] // 🤔 Not supported

// ['eeee', 'Tuesday'], // ❌ "Tue"
// ['eeeee', 'T'], // ❌ "Tuesday"
// ['eeeeee', 'Tu'] // ❌ "T"

// ['c', '7'] // 🤔 Not supported
// ['cc', '07'], // 🤔 Not supported
// ['ccc', 'Jul'] // 🤔 Not supported

// ['cccc', 'Tuesday'] // ❌ "Tue"
// ['ccccc', 'T'], // ❌ "Tuesday"
// ['cccccc', 'Tu'] // ❌ "T"

// 🤔 Only in combination with time?
// ['a', 'PM'] // ❌ "7/9/2024"
// ['aa', 'PM'] // ❌ "7/9/2024"
// ['aaa', 'PM'] // ❌ "7/9/2024"
// ['aaaa', 'PM'] // ❌ "7/9/2024"
// ['aaaaa', 'PM'] // ❌ "7/9/2024"

['h', '12 AM', '2024-07-09T22:00:00.000Z'],
['h', '9 AM'],
['hh', '09 AM'],

// ['H', '9'], // ❌ "09"
['HH', '09'],
['HH', '00', '2024-07-09T22:00:00.000Z'],

['K', '0 AM', '2024-07-09T22:00:00.000Z'],
['KK', '00 AM', '2024-07-09T22:00:00.000Z'],
['K', '9 AM'],
['KK', '09 AM'],

// ['k', '9'], // ❌ "09"
['kk', '09'],
['kk', '24', '2024-07-09T22:00:00.000Z'],

['m', '6'],
// ['mm', '06'] // ❌ "6"

['s', '3'],
// ['ss', '03'], // ❌ "3"
['mmss', '06:03'],

['z', '7/9/2024, GMT+2'], // 🤔 Includes date
['zz', '7/9/2024, GMT+2'], // 🤔 Includes date
['zzz', '7/9/2024, GMT+2'], // 🤔 Includes date
['zzzz', '7/9/2024, Central European Summer Time'], // 🤔 Includes date

['yyyyMMMd', 'Jul 9, 2024'],
[
'GGGGyyyyMMMMEdhmszzzz',
'Tue, July 9, 2024 Anno Domini at 9:06:03 AM Central European Summer Time'
]
])('%s: %s', (value, expected, dateString = '2024-07-09T07:06:03.320Z') => {
const date = new Date(dateString);
const t = createTranslator({
locale: 'en',
messages: {date: `{date, date, ::${value}}`}
});
expect(t('date', {date})).toBe(expected);
});
});

describe('t.rich', () => {
it('can translate a message to a ReactNode', () => {
const t = createTranslator({
Expand Down
2 changes: 1 addition & 1 deletion packages/use-intl/test/react/useTranslations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ describe('error handling', () => {

const error: IntlError = onError.mock.calls[0][0];
expect(error.message).toBe(
'INVALID_MESSAGE: Expected "date", "number", "plural", "select", "selectordinal", or "time" but "c" found.'
'INVALID_MESSAGE: INVALID_ARGUMENT_TYPE ({value, currency})'
);
expect(error.code).toBe(IntlErrorCode.INVALID_MESSAGE);
screen.getByText('price');
Expand Down
47 changes: 45 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading