react-intl
Version:
Internationalize React apps. This library provides React components and an API to format dates, numbers, and strings, including pluralization and handling translations.
352 lines (351 loc) • 14.6 kB
JavaScript
"use client";
import * as React from "react";
import { DEFAULT_INTL_CONFIG, IntlError as ReactIntlError, IntlErrorCode as ReactIntlErrorCode, InvalidConfigError, MessageFormatError, MissingDataError, MissingTranslationError, UnsupportedFormatterError, createIntl as createIntl$1, createIntlCache, createIntlCache as createIntlCache$1, formatMessage } from "@formatjs/intl";
import { Fragment, jsx } from "react/jsx-runtime";
import { isFormatXMLElementFn } from "intl-messageformat";
//#region packages/react-intl/utils.tsx
function invariant(condition, message, Err = Error) {
if (!condition) throw new Err(message);
}
function invariantIntlContext(intl) {
invariant(intl, "[React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.");
}
const DEFAULT_INTL_CONFIG$1 = {
...DEFAULT_INTL_CONFIG,
textComponent: React.Fragment
};
/**
* Builds an array of {@link React.ReactNode}s with index-based keys, similar to
* {@link React.Children.toArray}. However, this function tells React that it
* was intentional, so they won't produce a bunch of warnings about it.
*
* React doesn't recommend doing this because it makes reordering inefficient,
* but we mostly need this for message chunks, which don't tend to reorder to
* begin with.
*
*/
const toKeyedReactNodeArray = (children) => {
return React.Children.toArray(children).map((child, index) => {
if (React.isValidElement(child)) return /* @__PURE__ */ jsx(React.Fragment, { children: child }, index);
return child;
});
};
/**
* Takes a `formatXMLElementFn`, and composes it in function, which passes
* argument `parts` through, assigning unique key to each part, to prevent
* "Each child in a list should have a unique "key"" React error.
* @param formatXMLElementFn
*/
function assignUniqueKeysToParts(formatXMLElementFn) {
return function(parts) {
return formatXMLElementFn(toKeyedReactNodeArray(parts));
};
}
function shallowEqual(objA, objB) {
if (objA === objB) return true;
if (!objA || !objB) return false;
var aKeys = Object.keys(objA);
var bKeys = Object.keys(objB);
var len = aKeys.length;
if (bKeys.length !== len) return false;
for (var i = 0; i < len; i++) {
var key = aKeys[i];
if (objA[key] !== objB[key] || !Object.prototype.hasOwnProperty.call(objB, key)) return false;
}
return true;
}
//#endregion
//#region packages/react-intl/components/context.ts
const IntlContext = React.createContext(null);
const Provider = IntlContext.Provider;
//#endregion
//#region packages/react-intl/components/useIntl.ts
function useIntl() {
const intl = React.useContext(IntlContext);
invariantIntlContext(intl);
return intl;
}
//#endregion
//#region packages/react-intl/components/createFormattedComponent.tsx
var DisplayName = /* @__PURE__ */ function(DisplayName) {
DisplayName["formatDate"] = "FormattedDate";
DisplayName["formatTime"] = "FormattedTime";
DisplayName["formatNumber"] = "FormattedNumber";
DisplayName["formatList"] = "FormattedList";
DisplayName["formatDisplayName"] = "FormattedDisplayName";
return DisplayName;
}(DisplayName || {});
var DisplayNameParts = /* @__PURE__ */ function(DisplayNameParts) {
DisplayNameParts["formatDate"] = "FormattedDateParts";
DisplayNameParts["formatTime"] = "FormattedTimeParts";
DisplayNameParts["formatNumber"] = "FormattedNumberParts";
DisplayNameParts["formatList"] = "FormattedListParts";
return DisplayNameParts;
}(DisplayNameParts || {});
const FormattedNumberParts = (props) => {
const intl = useIntl();
const { value, children, ...formatProps } = props;
return children(intl.formatNumberToParts(value, formatProps));
};
FormattedNumberParts.displayName = "FormattedNumberParts";
const FormattedListParts = (props) => {
const intl = useIntl();
const { value, children, ...formatProps } = props;
return children(intl.formatListToParts(value, formatProps));
};
FormattedNumberParts.displayName = "FormattedNumberParts";
function createFormattedDateTimePartsComponent(name) {
const ComponentParts = (props) => {
const intl = useIntl();
const { value, children, ...formatProps } = props;
const date = typeof value === "string" ? new Date(value || 0) : value;
return children(name === "formatDate" ? intl.formatDateToParts(date, formatProps) : intl.formatTimeToParts(date, formatProps));
};
ComponentParts.displayName = DisplayNameParts[name];
return ComponentParts;
}
function createFormattedComponent(name) {
const Component = (props) => {
const intl = useIntl();
const { value, children, ...formatProps } = props;
const formattedValue = intl[name](value, formatProps);
if (typeof children === "function") return children(formattedValue);
return /* @__PURE__ */ jsx(intl.textComponent || React.Fragment, { children: formattedValue });
};
Component.displayName = DisplayName[name];
return Component;
}
//#endregion
//#region packages/react-intl/components/createIntl.ts
function assignUniqueKeysToFormatXMLElementFnArgument(values) {
if (!values) return values;
return Object.keys(values).reduce((acc, k) => {
const v = values[k];
acc[k] = isFormatXMLElementFn(v) ? assignUniqueKeysToParts(v) : v;
return acc;
}, {});
}
const formatMessage$1 = (config, formatters, descriptor, rawValues, ...rest) => {
const chunks = formatMessage(config, formatters, descriptor, assignUniqueKeysToFormatXMLElementFnArgument(rawValues), ...rest);
if (Array.isArray(chunks)) return toKeyedReactNodeArray(chunks);
return chunks;
};
/**
* Create intl object
* @param config intl config
* @param cache cache for formatter instances to prevent memory leak
*/
const createIntl = ({ defaultRichTextElements: rawDefaultRichTextElements, ...config }, cache) => {
const defaultRichTextElements = assignUniqueKeysToFormatXMLElementFnArgument(rawDefaultRichTextElements);
const coreIntl = createIntl$1({
...DEFAULT_INTL_CONFIG$1,
...config,
defaultRichTextElements
}, cache);
const resolvedConfig = {
locale: coreIntl.locale,
timeZone: coreIntl.timeZone,
fallbackOnEmptyString: coreIntl.fallbackOnEmptyString,
formats: coreIntl.formats,
defaultLocale: coreIntl.defaultLocale,
defaultFormats: coreIntl.defaultFormats,
messages: coreIntl.messages,
onError: coreIntl.onError,
defaultRichTextElements
};
return {
...coreIntl,
formatMessage: formatMessage$1.bind(null, resolvedConfig, coreIntl.formatters),
$t: formatMessage$1.bind(null, resolvedConfig, coreIntl.formatters)
};
};
//#endregion
//#region packages/react-intl/components/dateTimeRange.tsx
const FormattedDateTimeRange = (props) => {
const intl = useIntl();
const { from, to, children, ...formatProps } = props;
const formattedValue = intl.formatDateTimeRange(from, to, formatProps);
if (typeof children === "function") return children(formattedValue);
return /* @__PURE__ */ jsx(intl.textComponent || React.Fragment, { children: formattedValue });
};
FormattedDateTimeRange.displayName = "FormattedDateTimeRange";
//#endregion
//#region packages/react-intl/components/message.tsx
function areEqual(prevProps, nextProps) {
const { values, ...otherProps } = prevProps;
const { values: nextValues, ...nextOtherProps } = nextProps;
return shallowEqual(nextValues, values) && shallowEqual(otherProps, nextOtherProps);
}
function FormattedMessage(props) {
const { formatMessage, textComponent: Text = React.Fragment } = useIntl();
const { id, description, defaultMessage, values, children, tagName: Component = Text, ignoreTag } = props;
const nodes = formatMessage({
id,
description,
defaultMessage
}, values, { ignoreTag });
if (typeof children === "function") return children(Array.isArray(nodes) ? nodes : [nodes]);
if (Component) return /* @__PURE__ */ jsx(Component, { children: nodes });
return /* @__PURE__ */ jsx(Fragment, { children: nodes });
}
FormattedMessage.displayName = "FormattedMessage";
const MemoizedFormattedMessage = React.memo(FormattedMessage, areEqual);
MemoizedFormattedMessage.displayName = "MemoizedFormattedMessage";
//#endregion
//#region packages/react-intl/components/plural.tsx
const FormattedPlural = (props) => {
const { formatPlural, textComponent: Text } = useIntl();
const { value, other, children } = props;
const formattedPlural = props[formatPlural(value, props)] || other;
if (typeof children === "function") return children(formattedPlural);
if (Text) return /* @__PURE__ */ jsx(Text, { children: formattedPlural });
return formattedPlural;
};
FormattedPlural.displayName = "FormattedPlural";
//#endregion
//#region packages/react-intl/components/provider.tsx
function processIntlConfig(config) {
return {
locale: config.locale,
timeZone: config.timeZone,
fallbackOnEmptyString: config.fallbackOnEmptyString,
formats: config.formats,
textComponent: config.textComponent,
messages: config.messages,
defaultLocale: config.defaultLocale,
defaultFormats: config.defaultFormats,
onError: config.onError,
onWarn: config.onWarn,
wrapRichTextChunksInFragment: config.wrapRichTextChunksInFragment,
defaultRichTextElements: config.defaultRichTextElements
};
}
function IntlProviderImpl(props) {
const cacheRef = React.useRef(createIntlCache$1());
const prevConfigRef = React.useRef(void 0);
const intlRef = React.useRef(void 0);
const filteredProps = {};
for (const key in props) if (props[key] !== void 0) filteredProps[key] = props[key];
const config = processIntlConfig({
...DEFAULT_INTL_CONFIG$1,
...filteredProps
});
if (!prevConfigRef.current || !shallowEqual(prevConfigRef.current, config)) {
prevConfigRef.current = config;
intlRef.current = createIntl(config, cacheRef.current);
}
invariantIntlContext(intlRef.current);
return /* @__PURE__ */ jsx(Provider, {
value: intlRef.current,
children: props.children
});
}
IntlProviderImpl.displayName = "IntlProvider";
const IntlProvider = IntlProviderImpl;
//#endregion
//#region packages/react-intl/components/relative.tsx
const MINUTE = 60;
const HOUR = 3600;
const DAY = 3600 * 24;
function selectUnit(seconds) {
const absValue = Math.abs(seconds);
if (absValue < MINUTE) return "second";
if (absValue < HOUR) return "minute";
if (absValue < DAY) return "hour";
return "day";
}
function getDurationInSeconds(unit) {
switch (unit) {
case "second": return 1;
case "minute": return MINUTE;
case "hour": return HOUR;
default: return DAY;
}
}
function valueToSeconds(value, unit) {
if (!value) return 0;
switch (unit) {
case "second": return value;
case "minute": return value * MINUTE;
default: return value * HOUR;
}
}
const INCREMENTABLE_UNITS = [
"second",
"minute",
"hour"
];
function canIncrement(unit = "second") {
return INCREMENTABLE_UNITS.indexOf(unit) > -1;
}
const SimpleFormattedRelativeTime = (props) => {
const { formatRelativeTime, textComponent: Text } = useIntl();
const { children, value, unit, ...otherProps } = props;
const formattedRelativeTime = formatRelativeTime(value || 0, unit, otherProps);
if (typeof children === "function") return children(formattedRelativeTime);
if (Text) return /* @__PURE__ */ jsx(Text, { children: formattedRelativeTime });
return /* @__PURE__ */ jsx(Fragment, { children: formattedRelativeTime });
};
const FormattedRelativeTime = ({ value = 0, unit = "second", updateIntervalInSeconds, ...otherProps }) => {
invariant(!updateIntervalInSeconds || !!(updateIntervalInSeconds && canIncrement(unit)), "Cannot schedule update with unit longer than hour");
const [prevUnit, setPrevUnit] = React.useState();
const [prevValue, setPrevValue] = React.useState(0);
const [currentValueInSeconds, setCurrentValueInSeconds] = React.useState(0);
const updateTimer = React.useRef(void 0);
if (unit !== prevUnit || value !== prevValue) {
setPrevValue(value || 0);
setPrevUnit(unit);
setCurrentValueInSeconds(canIncrement(unit) ? valueToSeconds(value, unit) : 0);
}
React.useEffect(() => {
function clearUpdateTimer() {
clearTimeout(updateTimer.current);
}
clearUpdateTimer();
if (!updateIntervalInSeconds || !canIncrement(unit)) return clearUpdateTimer;
const nextValueInSeconds = currentValueInSeconds - updateIntervalInSeconds;
const nextUnit = selectUnit(nextValueInSeconds);
if (nextUnit === "day") return clearUpdateTimer;
const unitDuration = getDurationInSeconds(nextUnit);
const prevInterestingValueInSeconds = nextValueInSeconds - nextValueInSeconds % unitDuration;
const nextInterestingValueInSeconds = prevInterestingValueInSeconds >= currentValueInSeconds ? prevInterestingValueInSeconds - unitDuration : prevInterestingValueInSeconds;
const delayInSeconds = Math.abs(nextInterestingValueInSeconds - currentValueInSeconds);
if (currentValueInSeconds !== nextInterestingValueInSeconds) updateTimer.current = setTimeout(() => setCurrentValueInSeconds(nextInterestingValueInSeconds), delayInSeconds * 1e3);
return clearUpdateTimer;
}, [
currentValueInSeconds,
updateIntervalInSeconds,
unit
]);
let currentValue = value || 0;
let currentUnit = unit;
if (canIncrement(unit) && typeof currentValueInSeconds === "number" && updateIntervalInSeconds) {
currentUnit = selectUnit(currentValueInSeconds);
const unitDuration = getDurationInSeconds(currentUnit);
currentValue = Math.round(currentValueInSeconds / unitDuration);
}
return /* @__PURE__ */ jsx(SimpleFormattedRelativeTime, {
value: currentValue,
unit: currentUnit,
...otherProps
});
};
FormattedRelativeTime.displayName = "FormattedRelativeTime";
//#endregion
//#region packages/react-intl/index.ts
function defineMessages(msgs) {
return msgs;
}
function defineMessage(msg) {
return msg;
}
const FormattedDate = createFormattedComponent("formatDate");
const FormattedTime = createFormattedComponent("formatTime");
const FormattedNumber = createFormattedComponent("formatNumber");
const FormattedList = createFormattedComponent("formatList");
const FormattedDisplayName = createFormattedComponent("formatDisplayName");
const FormattedDateParts = createFormattedDateTimePartsComponent("formatDate");
const FormattedTimeParts = createFormattedDateTimePartsComponent("formatTime");
//#endregion
export { FormattedDate, FormattedDateParts, FormattedDateTimeRange, FormattedDisplayName, FormattedList, FormattedListParts, MemoizedFormattedMessage as FormattedMessage, FormattedNumber, FormattedNumberParts, FormattedPlural, FormattedRelativeTime, FormattedTime, FormattedTimeParts, IntlContext, IntlProvider, InvalidConfigError, MessageFormatError, MissingDataError, MissingTranslationError, Provider as RawIntlProvider, ReactIntlError, ReactIntlErrorCode, UnsupportedFormatterError, createIntl, createIntlCache, defineMessage, defineMessages, useIntl };
//# sourceMappingURL=index.js.map