diff --git a/docs/pages/docs/getting-started/pages-router.mdx b/docs/pages/docs/getting-started/pages-router.mdx
index fcaebee94..3f6f4554d 100644
--- a/docs/pages/docs/getting-started/pages-router.mdx
+++ b/docs/pages/docs/getting-started/pages-router.mdx
@@ -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.
diff --git a/docs/pages/docs/usage/dates-times.mdx b/docs/pages/docs/usage/dates-times.mdx
index b752e2f41..ad81641cb 100644
--- a/docs/pages/docs/usage/dates-times.mdx
+++ b/docs/pages/docs/usage/dates-times.mdx
@@ -179,7 +179,8 @@ 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}"
}
```
@@ -187,24 +188,22 @@ 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
GGGG
GGGGG | 7/9/2024 AD
7/9/2024 Anno Domini
7/9/2024 A |
+| y | Year | y
yy
yyyy | 2024
24
2024 |
+| M | Month in year | M
MM
MMM
MMMM
MMMMM
| 7
07
Jul
July
J |
+| d | Day in month | d
dd | 9
09 |
+| E | Day of week | E
EEEE
EEEEE | Tue
Tuesday
T |
+| h | Hour (1-12) | h
hh | 9 AM
09 AM |
+| K | Hour (0-11) | K
KK | 0 AM (12 AM with `h`)
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
mmss | 6
06:03 |
+| s | Second (2 digits if used with minutes) | s
mmss | 3
06:03 |
+| z | Time zone | z
zzzz | GMT+2
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
diff --git a/packages/next-intl/package.json b/packages/next-intl/package.json
index 67996e60d..7f5980389 100644
--- a/packages/next-intl/package.json
+++ b/packages/next-intl/package.json
@@ -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",
@@ -134,7 +134,7 @@
},
{
"path": "dist/production/server.react-server.js",
- "limit": "13.05 KB"
+ "limit": "15.645 KB"
},
{
"path": "dist/production/middleware.js",
diff --git a/packages/use-intl/package.json b/packages/use-intl/package.json
index fd422b337..cc668bbf7 100644
--- a/packages/use-intl/package.json
+++ b/packages/use-intl/package.json
@@ -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"
@@ -90,7 +90,7 @@
"size-limit": [
{
"path": "dist/production/index.js",
- "limit": "12.575 kB"
+ "limit": "15.235 kB"
}
]
}
diff --git a/packages/use-intl/src/core/createBaseTranslator.tsx b/packages/use-intl/src/core/createBaseTranslator.tsx
index 24f2a1c7b..ddaf5731c 100644
--- a/packages/use-intl/src/core/createBaseTranslator.tsx
+++ b/packages/use-intl/src/core/createBaseTranslator.tsx
@@ -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
diff --git a/packages/use-intl/test/core/createTranslator.test.tsx b/packages/use-intl/test/core/createTranslator.test.tsx
index 821147bee..6fdf5a6b3 100644
--- a/packages/use-intl/test/core/createTranslator.test.tsx
+++ b/packages/use-intl/test/core/createTranslator.test.tsx
@@ -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({
diff --git a/packages/use-intl/test/react/useTranslations.test.tsx b/packages/use-intl/test/react/useTranslations.test.tsx
index 457aca8f3..f70a6c1ae 100644
--- a/packages/use-intl/test/react/useTranslations.test.tsx
+++ b/packages/use-intl/test/react/useTranslations.test.tsx
@@ -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');
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8e80f8ad4..494e643ee 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -623,8 +623,8 @@ importers:
specifier: ^1.11.4
version: 1.11.4
intl-messageformat:
- specifier: ^9.3.18
- version: 9.3.18
+ specifier: ^10.5.11
+ version: 10.5.11
devDependencies:
'@arethetypeswrong/cli':
specifier: ^0.13.5
@@ -6560,12 +6560,40 @@ packages:
tslib: 2.3.1
dev: false
+ /@formatjs/ecma402-abstract@1.18.2:
+ resolution: {integrity: sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==}
+ dependencies:
+ '@formatjs/intl-localematcher': 0.5.4
+ tslib: 2.5.0
+ dev: false
+
/@formatjs/ecma402-abstract@1.4.0:
resolution: {integrity: sha512-Mv027hcLFjE45K8UJ8PjRpdDGfR0aManEFj1KzoN8zXNveHGEygpZGfFf/FTTMl+QEVSrPAUlyxaCApvmv47AQ==}
dependencies:
tslib: 2.5.0
dev: false
+ /@formatjs/fast-memoize@2.2.0:
+ resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==}
+ dependencies:
+ tslib: 2.5.0
+ dev: false
+
+ /@formatjs/icu-messageformat-parser@2.7.6:
+ resolution: {integrity: sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==}
+ dependencies:
+ '@formatjs/ecma402-abstract': 1.18.2
+ '@formatjs/icu-skeleton-parser': 1.8.0
+ tslib: 2.5.0
+ dev: false
+
+ /@formatjs/icu-skeleton-parser@1.8.0:
+ resolution: {integrity: sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==}
+ dependencies:
+ '@formatjs/ecma402-abstract': 1.18.2
+ tslib: 2.5.0
+ dev: false
+
/@formatjs/intl-localematcher@0.2.25:
resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==}
dependencies:
@@ -6578,6 +6606,12 @@ packages:
tslib: 2.5.0
dev: false
+ /@formatjs/intl-localematcher@0.5.4:
+ resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
+ dependencies:
+ tslib: 2.5.0
+ dev: false
+
/@gar/promisify@1.1.3:
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
@@ -17093,6 +17127,15 @@ packages:
tslib: 2.5.0
dev: false
+ /intl-messageformat@10.5.11:
+ resolution: {integrity: sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==}
+ dependencies:
+ '@formatjs/ecma402-abstract': 1.18.2
+ '@formatjs/fast-memoize': 2.2.0
+ '@formatjs/icu-messageformat-parser': 2.7.6
+ tslib: 2.5.0
+ dev: false
+
/intl-messageformat@9.3.18:
resolution: {integrity: sha512-OKrLWppdxXtRdRCPjmRZ9Ru7UZkZJDlMl+1Vpb3sCLWK0mFpr129K+gIlIb5zrWoAH3NiYDzekBXPTRWCyHSIA==}
dependencies: