Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
232 lines
7.7 KiB
Plaintext
232 lines
7.7 KiB
Plaintext
import { createContext, useContext, useMemo, useState, useEffect } from 'react';
|
|
import { b as createCache, c as createIntlFormatters, I as IntlError, a as IntlErrorCode } from './formatters-r4aAmsMP.js';
|
|
import { i as initializeConfig, r as resolveNamespace, a as createBaseTranslator, c as createFormatter } from './initializeConfig-DzxNJxi9.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]);
|
|
}
|
|
|
|
function getArgs(messageOrParams, ...rest) {
|
|
let message, values, formats;
|
|
if (typeof messageOrParams === 'string') {
|
|
message = messageOrParams;
|
|
values = rest[0];
|
|
formats = rest[1];
|
|
} else {
|
|
message = messageOrParams.message;
|
|
values = messageOrParams.values;
|
|
formats = messageOrParams.formats;
|
|
// `description` is is not used at runtime
|
|
}
|
|
// @ts-expect-error -- Secret fallback parameter
|
|
return [undefined,
|
|
// Always use fallback if not compiled
|
|
values, formats, message ];
|
|
}
|
|
|
|
// Note: This API is usually compiled into `useTranslations`,
|
|
// but there is some fallback handling which allows this hook
|
|
// to still work when not being compiled.
|
|
//
|
|
// This is relevant for:
|
|
// - Isolated environments like tests, Storybook, etc.
|
|
// - Fallbacks in case an extracted message is not yet available
|
|
function useExtracted(namespace) {
|
|
const t = useTranslations(namespace);
|
|
function translateFn(...params) {
|
|
// @ts-expect-error -- Passing `undefined` as an ID is secretly allowed here
|
|
return t(...getArgs(...params));
|
|
}
|
|
translateFn.rich = (...params) =>
|
|
// @ts-expect-error -- Passing `undefined` as an ID is secretly allowed here
|
|
t.rich(...getArgs(...params));
|
|
translateFn.markup = (...params) =>
|
|
// @ts-expect-error -- Passing `undefined` as an ID is secretly allowed here
|
|
t.markup(...getArgs(...params));
|
|
translateFn.has = function translateHasFn(
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
message) {
|
|
// Not really something better we can do here
|
|
return true;
|
|
};
|
|
return translateFn;
|
|
}
|
|
|
|
export { IntlProvider, useExtracted as _useExtracted, useFormatter, useLocale, useMessages, useNow, useTimeZone, useTranslations };
|