Skip to content

Commit

Permalink
fix: Apply time zone for built-in default formats (full, long, `m…
Browse files Browse the repository at this point in the history
…edium`, `short`) (#473)

Fixes #467
  • Loading branch information
amannn authored Aug 23, 2023
1 parent 9038031 commit 244b9b2
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 5 deletions.
2 changes: 2 additions & 0 deletions docs/pages/docs/usage/numbers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Numbers can be embedded within messages by using the ICU syntax.

Note the leading `::` that is used to indicate that a skeleton should be used. See the [ICU docs about number skeletons](https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html) to learn more about this.

These formats are supported out of the box: `currency` and `percent`.

<Callout>

If you work with translators, it can be helpful for them to use an editor that supports the ICU syntax for numbers (e.g. the <PartnerContentLink name="numbers-messages" href="https://support.crowdin.com/icu-message-syntax/#number">Crowdin Editor</PartnerContentLink>).
Expand Down
22 changes: 19 additions & 3 deletions packages/use-intl/src/core/convertFormatsToIntlMessageFormat.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Formats as IntlFormats} from 'intl-messageformat';
import IntlMessageFormat, {Formats as IntlFormats} from 'intl-messageformat';
import DateTimeFormatOptions from './DateTimeFormatOptions';
import Formats from './Formats';
import TimeZone from './TimeZone';
Expand Down Expand Up @@ -38,9 +38,25 @@ export default function convertFormatsToIntlMessageFormat(
? {...formats, dateTime: setTimeZoneInFormats(formats.dateTime, timeZone)}
: formats;

const mfDateDefaults = IntlMessageFormat.formats.date as Formats['dateTime'];
const defaultDateFormats = timeZone
? setTimeZoneInFormats(mfDateDefaults, timeZone)
: mfDateDefaults;

const mfTimeDefaults = IntlMessageFormat.formats.time as Formats['dateTime'];
const defaultTimeFormats = timeZone
? setTimeZoneInFormats(mfTimeDefaults, timeZone)
: mfTimeDefaults;

return {
...formatsWithTimeZone,
date: formatsWithTimeZone?.dateTime,
time: formatsWithTimeZone?.dateTime
date: {
...defaultDateFormats,
...formatsWithTimeZone?.dateTime
},
time: {
...defaultTimeFormats,
...formatsWithTimeZone?.dateTime
}
};
}
27 changes: 27 additions & 0 deletions packages/use-intl/test/react/useFormatter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,33 @@ describe('dateTime', () => {
expect(container.textContent).toMatch(/Nov 20 2020/);
});

it('handles missing formats, which are available as defaults for `useTranslations`', () => {
// This is because we can't safely apply defaults for `dateTime`.
// `IntlMessageFormat` has defaults for `date` and `time`, but we
// consider a single `dateTime` namespace to be more useful. Because
// of this, we can't pick or merge default formats.

const onError = vi.fn();

function Component() {
const format = useFormatter();
return <>{format.dateTime(mockDate, 'medium')}</>;
}

const {container} = render(
<MockProvider onError={onError}>
<Component />
</MockProvider>
);

const error: IntlError = onError.mock.calls[0][0];
expect(error.message).toBe(
'MISSING_FORMAT: Format `medium` is not available. You can configure it on the provider or provide custom options.'
);
expect(error.code).toBe(IntlErrorCode.MISSING_FORMAT);
expect(container.textContent).toMatch(/Nov 20 2020/);
});

it('handles formatting errors', () => {
const onError = vi.fn();

Expand Down
26 changes: 24 additions & 2 deletions packages/use-intl/test/react/useTranslations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function renderMessage(
formats={{dateTime: {time: {hour: 'numeric', minute: '2-digit'}}}}
locale="en"
messages={{message}}
timeZone="Europe/London"
timeZone="Etc/UTC"
>
<Component />
</IntlProvider>
Expand Down Expand Up @@ -122,7 +122,29 @@ it('applies a time zone for provided formats', () => {
renderMessage('{now, time, time}', {
now: parseISO('2020-11-19T15:38:43.700Z')
});
screen.getByText('3:38 PM');
});

it('applies a time zone when using a built-in format', () => {
function expectFormatted(
style: 'time' | 'date',
format: 'full' | 'long' | 'medium' | 'short',
result: string
) {
const now = parseISO('2023-05-08T22:50:16.879Z');
const {unmount} = renderMessage(`{now, ${style}, ${format}}`, {now});
screen.getByText(result);
unmount();
}

expectFormatted('time', 'full', '10:50:16 PM UTC');
expectFormatted('time', 'long', '10:50:16 PM UTC');
expectFormatted('time', 'medium', '10:50:16 PM');
expectFormatted('time', 'short', '10:50 PM');

expectFormatted('date', 'full', 'Monday, May 8, 2023');
expectFormatted('date', 'long', 'May 8, 2023');
expectFormatted('date', 'medium', 'May 8, 2023');
expectFormatted('date', 'short', '5/8/23');
});

it('handles pluralisation', () => {
Expand Down

2 comments on commit 244b9b2

@vercel
Copy link

@vercel vercel bot commented on 244b9b2 Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-example-next-13 – ./examples/example-next-13

next-intl-example-next-13-git-main-next-intl.vercel.app
next-intl-example-next-13-next-intl.vercel.app
next-intl-example-next-13.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 244b9b2 Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-docs – ./docs

next-intl-docs.vercel.app
next-intl-docs-git-main-next-intl.vercel.app
next-intl-docs-next-intl.vercel.app

Please sign in to comment.