UNPKG

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
"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