Skip to content
Closed
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
12 changes: 11 additions & 1 deletion packages/use-intl/src/core/createBaseTranslator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ function resolvePath(
return message;
}

// Prepare message by converting `{ var, type }` to `{ var, type, default }`
// to work around absence of default in ICU message syntax
function prepareMessage(message: string) {
return message
.replace(
/\{\s*(\w+),\s*(\w+)\s*}/g,
'{$1, $2, default}'
);
}

function prepareTranslationValues(values: RichTranslationValues) {
// Workaround for https://github.com/formatjs/formatjs/issues/1467
const transformedValues: RichTranslationValues = {};
Expand Down Expand Up @@ -272,7 +282,7 @@ function createBaseTranslatorImpl<

try {
messageFormat = formatters.getMessageFormat(
message,
prepareMessage(message),
locale,
convertFormatsToIntlMessageFormat(globalFormats, formats, timeZone),
{
Expand Down
84 changes: 84 additions & 0 deletions packages/use-intl/src/core/createFormatter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ describe('dateTime', () => {
).toBe('Nov 20, 2020');
});

it('formats a date and time with global default format', () => {
const formatter = createFormatter({
locale: 'en',
timeZone: 'Europe/Berlin',
formats: {
dateTime: {
default: {
dateStyle: 'medium',
}
}
}
});
expect(
formatter.dateTime(parseISO('2020-11-20T10:36:01.516Z'))
).toBe('Nov 20, 2020');
});

it('allows to override a time zone', () => {
const formatter = createFormatter({
locale: 'en',
Expand All @@ -29,6 +46,26 @@ describe('dateTime', () => {
).toBe('Nov 20, 2020, 5:36:01 AM');
});

it('can combine a global default format with an override', () => {
const formatter = createFormatter({
locale: 'en',
timeZone: 'Europe/Berlin',
formats: {
dateTime: {
default: {
dateStyle: 'short',
timeStyle: 'short'
}
}
}
});
expect(
formatter.dateTime(parseISO('2020-11-20T10:36:01.516Z'), {
timeZone: 'America/New_York'
})
).toBe('11/20/20, 5:36 AM');
});

it('can combine a global format with an override', () => {
const formatter = createFormatter({
locale: 'en',
Expand Down Expand Up @@ -59,6 +96,52 @@ describe('number', () => {
expect(formatter.number(123456)).toBe('123,456');
});

it('formats a number with default formatters', () => {
const formatter = createFormatter({
locale: 'en',
timeZone: 'Europe/Berlin',
formats: {
number: {
default: {
useGrouping: false
},
},
},
});
expect(formatter.number(12345678)).toBe('12345678');
});

it('formats a number with default formatters and with an override', () => {
const formatter = createFormatter({
locale: 'en',
timeZone: 'Europe/Berlin',
formats: {
number: {
decimal: {
useGrouping: false
},
},
},
});
expect(formatter.number(12345678, { useGrouping: true })).toBe('12,345,678');
});

it('formats a number with default formatters and with an override for decimal style', () => {
const formatter = createFormatter({
locale: 'en',
timeZone: 'Europe/Berlin',
formats: {
number: {
default: {
maximumSignificantDigits: 3,
useGrouping: false
},
},
},
});
expect(formatter.number(12345678, { style: 'decimal', useGrouping: true })).toBe('12,300,000');
});

it('formats a bigint', () => {
const formatter = createFormatter({
locale: 'en',
Expand Down Expand Up @@ -456,3 +539,4 @@ describe('list', () => {
).toBe('apple, banana, and orange');
});
});
2
4 changes: 2 additions & 2 deletions packages/use-intl/src/core/createFormatter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export default function createFormatter(props: Props) {
}

function resolveFormatOrOptions<Options>(
typeFormats: Record<string, Options> | undefined,
typeFormats: Record<string | 'default', Options> | undefined,
formatOrOptions?: string | Options,
overrides?: Options
) {
Expand All @@ -140,7 +140,7 @@ export default function createFormatter(props: Props) {
options = {...options, ...overrides};
}

return options;
return Object.assign({}, typeFormats?.default, options);
}

function getFormattedValue<Options, Output>(
Expand Down
22 changes: 22 additions & 0 deletions packages/use-intl/src/core/createTranslator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,28 @@ describe('numbers in messages', () => {
t('label', {count: 1.5}, {number: {integer: {minimumFractionDigits: 0}}})
).toBe('1.50000 2');
});

it('can pass a default global format', () => {
const t = createTranslator({
locale: 'en',
messages: {label: 'Test case {count, number}'},
formats: {number: {default: {useGrouping: false}}}
});
expect(
t('label', {count: 123456}, {number: {integer: {minimumFractionDigits: 0}}})
).toBe('Test case 123456');
});

it('can merge an inline format with a default global formats', () => {
const t = createTranslator({
locale: 'en',
messages: {label: 'Test case {count, number} and {count, number, integer}'},
formats: {number: {default: {useGrouping: false}}}
});
expect(
t('label', {count: 123456}, {number: {integer: {maximumSignificantDigits: 3}}})
).toBe('Test case 123456 and 123,000');
});
});

describe('big integers in messages', () => {
Expand Down
30 changes: 30 additions & 0 deletions packages/use-intl/src/react/useFormatter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ describe('dateTime', () => {
screen.getByText('11 AM');
});

it('can use a default global date format', () => {
function Component() {
const format = useFormatter();
return <>{format.dateTime(mockDate)}</>;
}

render(
<MockProvider formats={{dateTime: {default: {year: 'numeric'}}}}>
<Component />
</MockProvider>
);

screen.getByText('2020');
});

it('accepts type-safe custom options', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
() =>
Expand Down Expand Up @@ -336,6 +351,21 @@ describe('number', () => {
screen.getByText('10000');
});

it('can use a default global format', () => {
function Component() {
const format = useFormatter();
return <>{format.number(10000)}</>;
}

render(
<MockProvider formats={{number: {default: {useGrouping: false}}}}>
<Component />
</MockProvider>
);

screen.getByText('10000');
});

it('accepts type-safe custom options', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
() =>
Expand Down
12 changes: 10 additions & 2 deletions packages/use-intl/src/react/useTranslations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ function renderMessage(

return render(
<IntlProvider
formats={{dateTime: {time: {hour: 'numeric', minute: '2-digit'}}}}
formats={{
dateTime: {time: {hour: 'numeric', minute: '2-digit'}},
number: {default: { useGrouping: false }}
}}
locale="en"
messages={{message}}
timeZone="Etc/UTC"
Expand Down Expand Up @@ -86,6 +89,11 @@ it('can escape curly brackets in production', () => {
vi.unstubAllEnvs();
});

it('handles number default formatting', () => {
renderMessage('Correct {value, number} and another {value2, number}', {value: 123456, value2: 654321});
screen.getByText('Correct 123456 and another 654321');
});

it('handles number formatting with percent', () => {
renderMessage('{value, number, percent}', {value: 0.312});
screen.getByText('31%');
Expand Down Expand Up @@ -693,7 +701,7 @@ describe('error handling', () => {

const error: IntlError = onError.mock.calls[0][0];
expect(error.message).toBe(
'INVALID_MESSAGE: INVALID_ARGUMENT_TYPE ({value, currency})'
'INVALID_MESSAGE: INVALID_ARGUMENT_TYPE ({value, currency, default})'
);
expect(error.code).toBe(IntlErrorCode.INVALID_MESSAGE);
screen.getByText('price');
Expand Down