react-intl 3.7.0 → 3.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -9
- package/dist/components/plural.d.ts +1 -1
- package/dist/formatters/list.d.ts +1 -2
- package/dist/react-intl.api.md +3 -1
- package/dist/react-intl.d.ts +3 -7
- package/dist/react-intl.js +245 -8
- package/dist/react-intl.js.map +1 -1
- package/dist/react-intl.min.js +1 -1
- package/dist/react-intl.min.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/lib/components/plural.d.ts +1 -1
- package/lib/formatters/list.d.ts +1 -2
- package/lib/react-intl.d.ts +2 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/lib/types.d.ts +1 -0
- package/package.json +28 -27
- package/src/components/createFormattedComponent.tsx +114 -0
- package/src/components/html-message.tsx +68 -0
- package/src/components/injectIntl.tsx +111 -0
- package/src/components/message.tsx +120 -0
- package/src/components/plural.tsx +50 -0
- package/src/components/provider.tsx +183 -0
- package/src/components/relative.tsx +223 -0
- package/src/components/useIntl.ts +10 -0
- package/src/formatters/dateTime.ts +143 -0
- package/src/formatters/list.ts +72 -0
- package/src/formatters/message.ts +241 -0
- package/src/formatters/number.ts +77 -0
- package/src/formatters/plural.ts +31 -0
- package/src/formatters/relativeTime.ts +63 -0
- package/src/index.ts +52 -0
- package/src/tsconfig.cjs.json +8 -0
- package/src/tsconfig.json +8 -0
- package/src/types.ts +160 -0
- package/src/utils.ts +153 -0
- package/src/vendor.d.ts +1 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2015, Yahoo Inc.
|
|
3
|
+
* Copyrights licensed under the New BSD License.
|
|
4
|
+
* See the accompanying LICENSE file for terms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import {Provider} from './injectIntl';
|
|
9
|
+
import {
|
|
10
|
+
createError,
|
|
11
|
+
DEFAULT_INTL_CONFIG,
|
|
12
|
+
createFormatters,
|
|
13
|
+
invariantIntlContext,
|
|
14
|
+
createIntlCache,
|
|
15
|
+
} from '../utils';
|
|
16
|
+
import {IntlConfig, IntlShape, Omit, IntlCache} from '../types';
|
|
17
|
+
import areIntlLocalesSupported from 'intl-locales-supported';
|
|
18
|
+
import {formatNumber, formatNumberToParts} from '../formatters/number';
|
|
19
|
+
import {formatRelativeTime} from '../formatters/relativeTime';
|
|
20
|
+
import {
|
|
21
|
+
formatDate,
|
|
22
|
+
formatTime,
|
|
23
|
+
formatDateToParts,
|
|
24
|
+
formatTimeToParts,
|
|
25
|
+
} from '../formatters/dateTime';
|
|
26
|
+
import {formatPlural} from '../formatters/plural';
|
|
27
|
+
import {formatMessage, formatHTMLMessage} from '../formatters/message';
|
|
28
|
+
import * as shallowEquals_ from 'shallow-equal/objects';
|
|
29
|
+
import {formatList} from '../formatters/list';
|
|
30
|
+
const shallowEquals: typeof shallowEquals_ =
|
|
31
|
+
(shallowEquals_ as any).default || shallowEquals_;
|
|
32
|
+
|
|
33
|
+
interface State {
|
|
34
|
+
/**
|
|
35
|
+
* Explicit intl cache to prevent memory leaks
|
|
36
|
+
*/
|
|
37
|
+
cache: IntlCache;
|
|
38
|
+
/**
|
|
39
|
+
* Intl object we created
|
|
40
|
+
*/
|
|
41
|
+
intl?: IntlShape;
|
|
42
|
+
/**
|
|
43
|
+
* list of memoized config we care about.
|
|
44
|
+
* This is important since creating intl is
|
|
45
|
+
* very expensive
|
|
46
|
+
*/
|
|
47
|
+
prevConfig: OptionalIntlConfig;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type OptionalIntlConfig = Omit<
|
|
51
|
+
IntlConfig,
|
|
52
|
+
keyof typeof DEFAULT_INTL_CONFIG
|
|
53
|
+
> &
|
|
54
|
+
Partial<typeof DEFAULT_INTL_CONFIG>;
|
|
55
|
+
|
|
56
|
+
function processIntlConfig<P extends OptionalIntlConfig = OptionalIntlConfig>(
|
|
57
|
+
config: P
|
|
58
|
+
): OptionalIntlConfig {
|
|
59
|
+
return {
|
|
60
|
+
locale: config.locale,
|
|
61
|
+
timeZone: config.timeZone,
|
|
62
|
+
formats: config.formats,
|
|
63
|
+
textComponent: config.textComponent,
|
|
64
|
+
messages: config.messages,
|
|
65
|
+
defaultLocale: config.defaultLocale,
|
|
66
|
+
defaultFormats: config.defaultFormats,
|
|
67
|
+
onError: config.onError,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create intl object
|
|
73
|
+
* @param config intl config
|
|
74
|
+
* @param cache cache for formatter instances to prevent memory leak
|
|
75
|
+
*/
|
|
76
|
+
export function createIntl(
|
|
77
|
+
config: OptionalIntlConfig,
|
|
78
|
+
cache?: IntlCache
|
|
79
|
+
): IntlShape {
|
|
80
|
+
const formatters = createFormatters(cache);
|
|
81
|
+
const resolvedConfig = {...DEFAULT_INTL_CONFIG, ...config};
|
|
82
|
+
if (
|
|
83
|
+
!resolvedConfig.locale ||
|
|
84
|
+
!areIntlLocalesSupported(resolvedConfig.locale)
|
|
85
|
+
) {
|
|
86
|
+
const {locale, defaultLocale, onError} = resolvedConfig;
|
|
87
|
+
if (typeof onError === 'function') {
|
|
88
|
+
onError(
|
|
89
|
+
createError(
|
|
90
|
+
`Missing locale data for locale: "${locale}". ` +
|
|
91
|
+
`Using default locale: "${defaultLocale}" as fallback.`
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Since there's no registered locale data for `locale`, this will
|
|
97
|
+
// fallback to the `defaultLocale` to make sure things can render.
|
|
98
|
+
// The `messages` are overridden to the `defaultProps` empty object
|
|
99
|
+
// to maintain referential equality across re-renders. It's assumed
|
|
100
|
+
// each <FormattedMessage> contains a `defaultMessage` prop.
|
|
101
|
+
resolvedConfig.locale = resolvedConfig.defaultLocale || 'en';
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
...resolvedConfig,
|
|
105
|
+
formatters,
|
|
106
|
+
formatNumber: formatNumber.bind(
|
|
107
|
+
null,
|
|
108
|
+
resolvedConfig,
|
|
109
|
+
formatters.getNumberFormat
|
|
110
|
+
),
|
|
111
|
+
formatNumberToParts: formatNumberToParts.bind(
|
|
112
|
+
null,
|
|
113
|
+
resolvedConfig,
|
|
114
|
+
formatters.getNumberFormat
|
|
115
|
+
),
|
|
116
|
+
formatRelativeTime: formatRelativeTime.bind(
|
|
117
|
+
null,
|
|
118
|
+
resolvedConfig,
|
|
119
|
+
formatters.getRelativeTimeFormat
|
|
120
|
+
),
|
|
121
|
+
formatDate: formatDate.bind(
|
|
122
|
+
null,
|
|
123
|
+
resolvedConfig,
|
|
124
|
+
formatters.getDateTimeFormat
|
|
125
|
+
),
|
|
126
|
+
formatDateToParts: formatDateToParts.bind(
|
|
127
|
+
null,
|
|
128
|
+
resolvedConfig,
|
|
129
|
+
formatters.getDateTimeFormat
|
|
130
|
+
),
|
|
131
|
+
formatTime: formatTime.bind(
|
|
132
|
+
null,
|
|
133
|
+
resolvedConfig,
|
|
134
|
+
formatters.getDateTimeFormat
|
|
135
|
+
),
|
|
136
|
+
formatTimeToParts: formatTimeToParts.bind(
|
|
137
|
+
null,
|
|
138
|
+
resolvedConfig,
|
|
139
|
+
formatters.getDateTimeFormat
|
|
140
|
+
),
|
|
141
|
+
formatPlural: formatPlural.bind(
|
|
142
|
+
null,
|
|
143
|
+
resolvedConfig,
|
|
144
|
+
formatters.getPluralRules
|
|
145
|
+
),
|
|
146
|
+
formatMessage: formatMessage.bind(null, resolvedConfig, formatters),
|
|
147
|
+
formatHTMLMessage: formatHTMLMessage.bind(null, resolvedConfig, formatters),
|
|
148
|
+
formatList: formatList.bind(null, resolvedConfig, formatters.getListFormat),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default class IntlProvider extends React.PureComponent<
|
|
153
|
+
OptionalIntlConfig,
|
|
154
|
+
State
|
|
155
|
+
> {
|
|
156
|
+
static displayName = 'IntlProvider';
|
|
157
|
+
static defaultProps = DEFAULT_INTL_CONFIG;
|
|
158
|
+
private cache: IntlCache = createIntlCache();
|
|
159
|
+
state: State = {
|
|
160
|
+
cache: this.cache,
|
|
161
|
+
intl: createIntl(processIntlConfig(this.props), this.cache),
|
|
162
|
+
prevConfig: processIntlConfig(this.props),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
static getDerivedStateFromProps(
|
|
166
|
+
props: OptionalIntlConfig,
|
|
167
|
+
{prevConfig, cache}: State
|
|
168
|
+
): Partial<State> | null {
|
|
169
|
+
const config = processIntlConfig(props);
|
|
170
|
+
if (!shallowEquals(prevConfig, config)) {
|
|
171
|
+
return {
|
|
172
|
+
intl: createIntl(config, cache),
|
|
173
|
+
prevConfig: config,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
render(): JSX.Element {
|
|
180
|
+
invariantIntlContext(this.state.intl);
|
|
181
|
+
return <Provider value={this.state.intl!}>{this.props.children}</Provider>;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2015, Yahoo Inc.
|
|
3
|
+
* Copyrights licensed under the New BSD License.
|
|
4
|
+
* See the accompanying LICENSE file for terms.
|
|
5
|
+
*/
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import {Context} from './injectIntl';
|
|
8
|
+
import {FormatRelativeTimeOptions} from '../types';
|
|
9
|
+
import {Unit} from '@formatjs/intl-relativetimeformat';
|
|
10
|
+
import {invariantIntlContext} from '../utils';
|
|
11
|
+
|
|
12
|
+
// Since rollup cannot deal with namespace being a function,
|
|
13
|
+
// this is to interop with TypeScript since `invariant`
|
|
14
|
+
// does not export a default
|
|
15
|
+
// https://github.com/rollup/rollup/issues/1267
|
|
16
|
+
import * as invariant_ from 'invariant';
|
|
17
|
+
const invariant: typeof invariant_ = (invariant_ as any).default || invariant_;
|
|
18
|
+
const MINUTE = 60;
|
|
19
|
+
const HOUR = 60 * 60;
|
|
20
|
+
const DAY = 60 * 60 * 24;
|
|
21
|
+
|
|
22
|
+
function selectUnit(seconds: number): Unit {
|
|
23
|
+
const absValue = Math.abs(seconds);
|
|
24
|
+
|
|
25
|
+
if (absValue < MINUTE) {
|
|
26
|
+
return 'second';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (absValue < HOUR) {
|
|
30
|
+
return 'minute';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (absValue < DAY) {
|
|
34
|
+
return 'hour';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return 'day';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getDurationInSeconds(unit?: Unit): number {
|
|
41
|
+
switch (unit) {
|
|
42
|
+
case 'second':
|
|
43
|
+
return 1;
|
|
44
|
+
case 'minute':
|
|
45
|
+
return MINUTE;
|
|
46
|
+
case 'hour':
|
|
47
|
+
return HOUR;
|
|
48
|
+
default:
|
|
49
|
+
return DAY;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function valueToSeconds(value?: number, unit?: Unit): number {
|
|
54
|
+
if (!value) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
switch (unit) {
|
|
58
|
+
case 'second':
|
|
59
|
+
return value;
|
|
60
|
+
case 'minute':
|
|
61
|
+
return value * MINUTE;
|
|
62
|
+
default:
|
|
63
|
+
return value * HOUR;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Props extends FormatRelativeTimeOptions {
|
|
68
|
+
value?: number;
|
|
69
|
+
unit?: Unit;
|
|
70
|
+
updateIntervalInSeconds?: number;
|
|
71
|
+
children?(value: string): React.ReactChild;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface State {
|
|
75
|
+
prevUnit?: Unit;
|
|
76
|
+
prevValue?: number;
|
|
77
|
+
currentValueInSeconds: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const INCREMENTABLE_UNITS: Unit[] = ['second', 'minute', 'hour'];
|
|
81
|
+
function canIncrement(unit: Unit = 'second'): boolean {
|
|
82
|
+
return INCREMENTABLE_UNITS.includes(unit);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function verifyProps(updateIntervalInSeconds?: number, unit?: Unit): void {
|
|
86
|
+
invariant(
|
|
87
|
+
!updateIntervalInSeconds || (updateIntervalInSeconds && canIncrement(unit)),
|
|
88
|
+
'Cannot schedule update with unit longer than hour'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class FormattedRelativeTime extends React.PureComponent<Props, State> {
|
|
93
|
+
// Public for testing
|
|
94
|
+
_updateTimer: any = null;
|
|
95
|
+
static displayName = 'FormattedRelativeTime';
|
|
96
|
+
static defaultProps: Pick<Props, 'unit' | 'value'> = {
|
|
97
|
+
value: 0,
|
|
98
|
+
unit: 'second',
|
|
99
|
+
};
|
|
100
|
+
state: State = {
|
|
101
|
+
prevUnit: this.props.unit,
|
|
102
|
+
prevValue: this.props.value,
|
|
103
|
+
currentValueInSeconds: canIncrement(this.props.unit)
|
|
104
|
+
? valueToSeconds(this.props.value, this.props.unit)
|
|
105
|
+
: 0,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
constructor(props: Props) {
|
|
109
|
+
super(props);
|
|
110
|
+
verifyProps(props.updateIntervalInSeconds, props.unit);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
scheduleNextUpdate(
|
|
114
|
+
{updateIntervalInSeconds, unit}: Props,
|
|
115
|
+
{currentValueInSeconds}: State
|
|
116
|
+
): void {
|
|
117
|
+
clearTimeout(this._updateTimer);
|
|
118
|
+
this._updateTimer = null;
|
|
119
|
+
// If there's no interval and we cannot increment this unit, do nothing
|
|
120
|
+
if (!updateIntervalInSeconds || !canIncrement(unit)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Figure out the next interesting time
|
|
124
|
+
const nextValueInSeconds = currentValueInSeconds - updateIntervalInSeconds;
|
|
125
|
+
const nextUnit = selectUnit(nextValueInSeconds);
|
|
126
|
+
// We've reached the max auto incrementable unit, don't schedule another update
|
|
127
|
+
if (nextUnit === 'day') {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const unitDuration = getDurationInSeconds(nextUnit);
|
|
132
|
+
const remainder = nextValueInSeconds % unitDuration;
|
|
133
|
+
const prevInterestingValueInSeconds = nextValueInSeconds - remainder;
|
|
134
|
+
const nextInterestingValueInSeconds =
|
|
135
|
+
prevInterestingValueInSeconds >= currentValueInSeconds
|
|
136
|
+
? prevInterestingValueInSeconds - unitDuration
|
|
137
|
+
: prevInterestingValueInSeconds;
|
|
138
|
+
const delayInSeconds = Math.abs(
|
|
139
|
+
nextInterestingValueInSeconds - currentValueInSeconds
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
this._updateTimer = setTimeout(
|
|
143
|
+
() =>
|
|
144
|
+
this.setState({
|
|
145
|
+
currentValueInSeconds: nextInterestingValueInSeconds,
|
|
146
|
+
}),
|
|
147
|
+
delayInSeconds * 1e3
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
componentDidMount(): void {
|
|
152
|
+
this.scheduleNextUpdate(this.props, this.state);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
componentDidUpdate(): void {
|
|
156
|
+
this.scheduleNextUpdate(this.props, this.state);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
componentWillUnmount(): void {
|
|
160
|
+
clearTimeout(this._updateTimer);
|
|
161
|
+
this._updateTimer = null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static getDerivedStateFromProps(
|
|
165
|
+
props: Props,
|
|
166
|
+
state: State
|
|
167
|
+
): Partial<State> | null {
|
|
168
|
+
if (props.unit !== state.prevUnit || props.value !== state.prevValue) {
|
|
169
|
+
return {
|
|
170
|
+
prevValue: props.value,
|
|
171
|
+
prevUnit: props.unit,
|
|
172
|
+
currentValueInSeconds: canIncrement(props.unit)
|
|
173
|
+
? valueToSeconds(props.value, props.unit)
|
|
174
|
+
: 0,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
render(): JSX.Element {
|
|
181
|
+
return (
|
|
182
|
+
<Context.Consumer>
|
|
183
|
+
{(intl): React.ReactNode => {
|
|
184
|
+
invariantIntlContext(intl);
|
|
185
|
+
|
|
186
|
+
const {formatRelativeTime, textComponent: Text} = intl;
|
|
187
|
+
const {children, value, unit, updateIntervalInSeconds} = this.props;
|
|
188
|
+
const {currentValueInSeconds} = this.state;
|
|
189
|
+
let currentValue = value || 0;
|
|
190
|
+
let currentUnit = unit;
|
|
191
|
+
|
|
192
|
+
if (
|
|
193
|
+
canIncrement(unit) &&
|
|
194
|
+
typeof currentValueInSeconds === 'number' &&
|
|
195
|
+
updateIntervalInSeconds
|
|
196
|
+
) {
|
|
197
|
+
currentUnit = selectUnit(currentValueInSeconds);
|
|
198
|
+
const unitDuration = getDurationInSeconds(currentUnit);
|
|
199
|
+
currentValue = Math.round(currentValueInSeconds / unitDuration);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const formattedRelativeTime = formatRelativeTime(
|
|
203
|
+
currentValue,
|
|
204
|
+
currentUnit,
|
|
205
|
+
{
|
|
206
|
+
...this.props,
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (typeof children === 'function') {
|
|
211
|
+
return children(formattedRelativeTime);
|
|
212
|
+
}
|
|
213
|
+
if (Text) {
|
|
214
|
+
return <Text>{formattedRelativeTime}</Text>;
|
|
215
|
+
}
|
|
216
|
+
return formattedRelativeTime;
|
|
217
|
+
}}
|
|
218
|
+
</Context.Consumer>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export default FormattedRelativeTime;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {useContext} from 'react';
|
|
2
|
+
import {Context} from './injectIntl';
|
|
3
|
+
import {invariantIntlContext} from '../utils';
|
|
4
|
+
import {IntlShape} from '../types';
|
|
5
|
+
|
|
6
|
+
export default function useIntl(): IntlShape {
|
|
7
|
+
const intl = useContext(Context);
|
|
8
|
+
invariantIntlContext(intl);
|
|
9
|
+
return intl;
|
|
10
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2015, Yahoo Inc.
|
|
3
|
+
* Copyrights licensed under the New BSD License.
|
|
4
|
+
* See the accompanying LICENSE file for terms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Formatters, IntlConfig, IntlFormatters} from '../types';
|
|
8
|
+
|
|
9
|
+
import {createError, filterProps, getNamedFormat} from '../utils';
|
|
10
|
+
|
|
11
|
+
const DATE_TIME_FORMAT_OPTIONS: Array<keyof Intl.DateTimeFormatOptions> = [
|
|
12
|
+
'localeMatcher',
|
|
13
|
+
'formatMatcher',
|
|
14
|
+
|
|
15
|
+
'timeZone',
|
|
16
|
+
'hour12',
|
|
17
|
+
|
|
18
|
+
'weekday',
|
|
19
|
+
'era',
|
|
20
|
+
'year',
|
|
21
|
+
'month',
|
|
22
|
+
'day',
|
|
23
|
+
'hour',
|
|
24
|
+
'minute',
|
|
25
|
+
'second',
|
|
26
|
+
'timeZoneName',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function getFormatter(
|
|
30
|
+
{
|
|
31
|
+
locale,
|
|
32
|
+
formats,
|
|
33
|
+
onError,
|
|
34
|
+
timeZone,
|
|
35
|
+
}: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
|
|
36
|
+
type: 'date' | 'time',
|
|
37
|
+
getDateTimeFormat: Formatters['getDateTimeFormat'],
|
|
38
|
+
options: Parameters<IntlFormatters['formatDate']>[1] = {}
|
|
39
|
+
): Intl.DateTimeFormat {
|
|
40
|
+
const {format} = options;
|
|
41
|
+
const defaults = {
|
|
42
|
+
...(timeZone && {timeZone}),
|
|
43
|
+
...(format && getNamedFormat(formats!, type, format, onError)),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let filteredOptions = filterProps(
|
|
47
|
+
options,
|
|
48
|
+
DATE_TIME_FORMAT_OPTIONS,
|
|
49
|
+
defaults
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
type === 'time' &&
|
|
54
|
+
!filteredOptions.hour &&
|
|
55
|
+
!filteredOptions.minute &&
|
|
56
|
+
!filteredOptions.second
|
|
57
|
+
) {
|
|
58
|
+
// Add default formatting options if hour, minute, or second isn't defined.
|
|
59
|
+
filteredOptions = {...filteredOptions, hour: 'numeric', minute: 'numeric'};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return getDateTimeFormat(locale, filteredOptions);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatDate(
|
|
66
|
+
config: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
|
|
67
|
+
getDateTimeFormat: Formatters['getDateTimeFormat'],
|
|
68
|
+
value?: Parameters<IntlFormatters['formatDate']>[0],
|
|
69
|
+
options: Parameters<IntlFormatters['formatDate']>[1] = {}
|
|
70
|
+
): string {
|
|
71
|
+
const date = typeof value === 'string' ? new Date(value || 0) : value;
|
|
72
|
+
try {
|
|
73
|
+
return getFormatter(config, 'date', getDateTimeFormat, options).format(
|
|
74
|
+
date
|
|
75
|
+
);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
config.onError(createError('Error formatting date.', e));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return String(date);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function formatTime(
|
|
84
|
+
config: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
|
|
85
|
+
getDateTimeFormat: Formatters['getDateTimeFormat'],
|
|
86
|
+
value?: Parameters<IntlFormatters['formatTime']>[0],
|
|
87
|
+
options: Parameters<IntlFormatters['formatTime']>[1] = {}
|
|
88
|
+
): string {
|
|
89
|
+
const date = typeof value === 'string' ? new Date(value || 0) : value;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
return getFormatter(config, 'time', getDateTimeFormat, options).format(
|
|
93
|
+
date
|
|
94
|
+
);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
config.onError(createError('Error formatting time.', e));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return String(date);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function formatDateToParts(
|
|
103
|
+
config: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
|
|
104
|
+
getDateTimeFormat: Formatters['getDateTimeFormat'],
|
|
105
|
+
value?: Parameters<IntlFormatters['formatDate']>[0],
|
|
106
|
+
options: Parameters<IntlFormatters['formatDate']>[1] = {}
|
|
107
|
+
): Intl.DateTimeFormatPart[] {
|
|
108
|
+
const date = typeof value === 'string' ? new Date(value || 0) : value;
|
|
109
|
+
try {
|
|
110
|
+
return getFormatter(
|
|
111
|
+
config,
|
|
112
|
+
'date',
|
|
113
|
+
getDateTimeFormat,
|
|
114
|
+
options
|
|
115
|
+
).formatToParts(date);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
config.onError(createError('Error formatting date.', e));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function formatTimeToParts(
|
|
124
|
+
config: Pick<IntlConfig, 'locale' | 'formats' | 'onError' | 'timeZone'>,
|
|
125
|
+
getDateTimeFormat: Formatters['getDateTimeFormat'],
|
|
126
|
+
value?: Parameters<IntlFormatters['formatTime']>[0],
|
|
127
|
+
options: Parameters<IntlFormatters['formatTime']>[1] = {}
|
|
128
|
+
): Intl.DateTimeFormatPart[] {
|
|
129
|
+
const date = typeof value === 'string' ? new Date(value || 0) : value;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
return getFormatter(
|
|
133
|
+
config,
|
|
134
|
+
'time',
|
|
135
|
+
getDateTimeFormat,
|
|
136
|
+
options
|
|
137
|
+
).formatToParts(date);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
config.onError(createError('Error formatting time.', e));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {IntlConfig, Formatters, IntlFormatters} from '../types';
|
|
3
|
+
import {filterProps, createError} from '../utils';
|
|
4
|
+
import IntlListFormat, {IntlListFormatOptions} from '@formatjs/intl-listformat';
|
|
5
|
+
|
|
6
|
+
const LIST_FORMAT_OPTIONS: Array<keyof IntlListFormatOptions> = [
|
|
7
|
+
'localeMatcher',
|
|
8
|
+
'type',
|
|
9
|
+
'style',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
|
|
14
|
+
function generateToken(i: number): string {
|
|
15
|
+
return `${now}_${i}_${now}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatList(
|
|
19
|
+
{locale, onError}: Pick<IntlConfig, 'locale' | 'onError'>,
|
|
20
|
+
getListFormat: Formatters['getListFormat'],
|
|
21
|
+
values: Array<string>,
|
|
22
|
+
options: Parameters<IntlFormatters['formatList']>[1]
|
|
23
|
+
): string;
|
|
24
|
+
export function formatList(
|
|
25
|
+
{locale, onError}: Pick<IntlConfig, 'locale' | 'onError'>,
|
|
26
|
+
getListFormat: Formatters['getListFormat'],
|
|
27
|
+
values: Parameters<IntlFormatters['formatList']>[0],
|
|
28
|
+
options: Parameters<IntlFormatters['formatList']>[1] = {}
|
|
29
|
+
): React.ReactNode {
|
|
30
|
+
const ListFormat: typeof IntlListFormat = (Intl as any).ListFormat;
|
|
31
|
+
if (!ListFormat) {
|
|
32
|
+
onError(
|
|
33
|
+
createError(`Intl.ListFormat is not available in this environment.
|
|
34
|
+
Try polyfilling it using "@formatjs/intl-listformat"
|
|
35
|
+
`)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const filteredOptions = filterProps(options, LIST_FORMAT_OPTIONS);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const richValues: Record<string, React.ReactNode> = {};
|
|
42
|
+
const serializedValues = values.map((v, i) => {
|
|
43
|
+
if (typeof v === 'object') {
|
|
44
|
+
const id = generateToken(i);
|
|
45
|
+
richValues[id] = v;
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
return String(v);
|
|
49
|
+
});
|
|
50
|
+
if (!Object.keys(richValues).length) {
|
|
51
|
+
return getListFormat(locale, filteredOptions).format(serializedValues);
|
|
52
|
+
}
|
|
53
|
+
const parts = getListFormat(locale, filteredOptions).formatToParts(
|
|
54
|
+
serializedValues
|
|
55
|
+
);
|
|
56
|
+
return parts.reduce((all: Array<string | React.ReactNode>, el) => {
|
|
57
|
+
const val = el.value;
|
|
58
|
+
if (richValues[val]) {
|
|
59
|
+
all.push(richValues[val]);
|
|
60
|
+
} else if (typeof all[all.length - 1] === 'string') {
|
|
61
|
+
all[all.length - 1] += val;
|
|
62
|
+
} else {
|
|
63
|
+
all.push(val);
|
|
64
|
+
}
|
|
65
|
+
return all;
|
|
66
|
+
}, []);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
onError(createError('Error formatting list.', e));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return values;
|
|
72
|
+
}
|