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.
@@ -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
+ }