use-intl
Version:
Internationalization (i18n) for React
185 lines (166 loc) • 6.25 kB
JavaScript
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 };