UNPKG

use-intl

Version:

Internationalization (i18n) for React

185 lines (166 loc) 6.25 kB
import { createContext, useContext, useMemo, useState, useEffect } from 'react'; import { d as createCache, b as createIntlFormatters, i as initializeConfig, r as resolveNamespace, I as IntlError, a as IntlErrorCode, e as createBaseTranslator, c as createFormatter } from './initializeConfig-CRD6euuK.js'; import { jsx } from 'react/jsx-runtime'; const IntlContext = /*#__PURE__*/createContext(undefined); function IntlProvider({ children, formats, getMessageFallback, locale, messages, now, onError, timeZone }) { const prevContext = useContext(IntlContext); // The formatter cache is released when the locale changes. For // long-running apps with a persistent `IntlProvider` at the root, // this can reduce the memory footprint (e.g. in React Native). const cache = useMemo(() => { return prevContext?.cache || createCache(); }, [locale, prevContext?.cache]); const formatters = useMemo(() => prevContext?.formatters || createIntlFormatters(cache), [cache, prevContext?.formatters]); // Memoizing this value helps to avoid triggering a re-render of all // context consumers in case the configuration didn't change. However, // if some of the non-primitive values change, a re-render will still // be triggered. Note that there's no need to put `memo` on `IntlProvider` // itself, because the `children` typically change on every render. // There's some burden on the consumer side if it's important to reduce // re-renders, put that's how React works. // See: https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/#context-updates-and-render-optimizations const value = useMemo(() => ({ ...initializeConfig({ locale, // (required by provider) formats: formats === undefined ? prevContext?.formats : formats, getMessageFallback: getMessageFallback || prevContext?.getMessageFallback, messages: messages === undefined ? prevContext?.messages : messages, now: now || prevContext?.now, onError: onError || prevContext?.onError, timeZone: timeZone || prevContext?.timeZone }), formatters, cache }), [cache, formats, formatters, getMessageFallback, locale, messages, now, onError, prevContext, timeZone]); return /*#__PURE__*/jsx(IntlContext.Provider, { value: value, children: children }); } function useIntlContext() { const context = useContext(IntlContext); if (!context) { throw new Error('No intl context found. Have you configured the provider? See https://next-intl.dev/docs/usage/configuration#server-client-components' ); } return context; } let hasWarnedForMissingTimezone = false; const isServer = typeof window === 'undefined'; function useTranslationsImpl(allMessagesPrefixed, namespacePrefixed, namespacePrefix) { const { cache, formats: globalFormats, formatters, getMessageFallback, locale, onError, timeZone } = useIntlContext(); // The `namespacePrefix` is part of the type system. // See the comment in the hook invocation. const allMessages = allMessagesPrefixed[namespacePrefix]; const namespace = resolveNamespace(namespacePrefixed, namespacePrefix); if (!timeZone && !hasWarnedForMissingTimezone && isServer) { // eslint-disable-next-line react-compiler/react-compiler hasWarnedForMissingTimezone = true; onError(new IntlError(IntlErrorCode.ENVIRONMENT_FALLBACK, `There is no \`timeZone\` configured, this can lead to markup mismatches caused by environment differences. Consider adding a global default: https://next-intl.dev/docs/configuration#time-zone` )); } const translate = useMemo(() => createBaseTranslator({ cache, formatters, getMessageFallback, messages: allMessages, namespace, onError, formats: globalFormats, locale, timeZone }), [cache, formatters, getMessageFallback, allMessages, namespace, onError, globalFormats, locale, timeZone]); return translate; } /** * Translates messages from the given namespace by using the ICU syntax. * See https://formatjs.io/docs/core-concepts/icu-syntax. * * If no namespace is provided, all available messages are returned. * The namespace can also indicate nesting by using a dot * (e.g. `namespace.Component`). */ function useTranslations(namespace) { const context = useIntlContext(); const messages = context.messages; // We have to wrap the actual hook so the type inference for the optional // namespace works correctly. See https://stackoverflow.com/a/71529575/343045 // The prefix ("!") is arbitrary. // @ts-expect-error Use the explicit annotation instead return useTranslationsImpl({ '!': messages }, // @ts-expect-error namespace ? `!.${namespace}` : '!', '!'); } function useLocale() { return useIntlContext().locale; } function getNow() { return new Date(); } /** * @see https://next-intl.dev/docs/usage/dates-times#relative-times-usenow */ function useNow(options) { const updateInterval = options?.updateInterval; const { now: globalNow } = useIntlContext(); const [now, setNow] = useState(globalNow || getNow()); useEffect(() => { if (!updateInterval) return; const intervalId = setInterval(() => { setNow(getNow()); }, updateInterval); return () => { clearInterval(intervalId); }; }, [globalNow, updateInterval]); return updateInterval == null && globalNow ? globalNow : now; } function useTimeZone() { return useIntlContext().timeZone; } function useMessages() { const context = useIntlContext(); if (!context.messages) { throw new Error('No messages found. Have you configured them correctly? See https://next-intl.dev/docs/configuration#messages' ); } return context.messages; } function useFormatter() { const { formats, formatters, locale, now: globalNow, onError, timeZone } = useIntlContext(); return useMemo(() => createFormatter({ formats, locale, now: globalNow, onError, timeZone, _formatters: formatters }), [formats, formatters, globalNow, locale, onError, timeZone]); } export { IntlProvider, useFormatter, useLocale, useMessages, useNow, useTimeZone, useTranslations };