react-i18next
Version:
Internationalization for react done right. Using the i18next i18n ecosystem.
194 lines (193 loc) • 7.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useTranslation = exports.useRerenderOnEvents = void 0;
var _react = require("react");
var _i18next = require("i18next");
var _context = require("./context.js");
var _utils = require("./utils.js");
const notReadyT = (k, optsOrDefaultValue) => {
if ((0, _utils.isString)(optsOrDefaultValue)) return optsOrDefaultValue;
if ((0, _utils.isObject)(optsOrDefaultValue) && (0, _utils.isString)(optsOrDefaultValue.defaultValue)) return optsOrDefaultValue.defaultValue;
if (typeof k === 'function') return '';
if (Array.isArray(k)) {
const last = k[k.length - 1];
return typeof last === 'function' ? '' : last;
}
return k;
};
const languageKey = inst => `${inst.language}|${inst.resolvedLanguage}|${inst.languages?.join(',')}`;
const createI18nWrapper = original => new Proxy(original, {
get: (target, prop) => prop === '__original' ? target : Reflect.get(target, prop)
});
const notReadySnapshot = {
t: notReadyT,
ready: false
};
const dummySubscribe = () => () => {};
const subscribeToEvents = (i18n, {
bindI18n,
bindI18nStore
}, callback) => {
if (bindI18n) i18n.on(bindI18n, callback);
if (bindI18nStore) i18n.store.on(bindI18nStore, callback);
return () => {
if (bindI18n) i18n.off(bindI18n, callback);
if (bindI18nStore) i18n.store.off(bindI18nStore, callback);
};
};
const useRerenderOnEvents = i18n => {
const {
bindI18n,
bindI18nStore
} = {
...(0, _context.getDefaults)(),
...i18n?.options?.react
};
const revisionRef = (0, _react.useRef)(0);
const subscribe = (0, _react.useCallback)(callback => i18n ? subscribeToEvents(i18n, {
bindI18n,
bindI18nStore
}, () => {
revisionRef.current += 1;
callback();
}) : () => {}, [i18n, bindI18n, bindI18nStore]);
const getSnapshot = () => bindI18n && i18n ? `${revisionRef.current}|${languageKey(i18n)}` : String(revisionRef.current);
(0, _react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
};
exports.useRerenderOnEvents = useRerenderOnEvents;
const useTranslation = (ns, props = {}) => {
const {
i18n: i18nFromProps
} = props;
const {
i18n: i18nFromContext,
defaultNS: defaultNSFromContext
} = (0, _react.useContext)(_context.I18nContext) || {};
const i18n = i18nFromProps || i18nFromContext || (0, _context.getI18n)();
if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new _context.ReportNamespaces();
if (!i18n) {
(0, _utils.warnOnce)(i18n, 'NO_I18NEXT_INSTANCE', 'useTranslation: You will need to pass in an i18next instance by using initReactI18next or by passing it via props or context. In monorepo setups, make sure there is only one instance of react-i18next.');
}
const i18nOptions = (0, _react.useMemo)(() => ({
...(0, _context.getDefaults)(),
...i18n?.options?.react,
...props
}), [i18n, props]);
const {
useSuspense,
keyPrefix,
bindI18n,
bindI18nStore
} = i18nOptions;
const nsOrContext = ns || defaultNSFromContext || i18n?.options?.defaultNS;
const unstableNamespaces = (0, _utils.isString)(nsOrContext) ? [nsOrContext] : nsOrContext || ['translation'];
const namespacesKey = JSON.stringify(unstableNamespaces);
const namespaces = (0, _react.useMemo)(() => unstableNamespaces, [namespacesKey]);
i18n?.reportNamespaces?.addUsedNamespaces?.(namespaces);
const revisionRef = (0, _react.useRef)(0);
const subscribe = (0, _react.useCallback)(callback => {
if (!i18n) return dummySubscribe;
const wrappedCallback = () => {
revisionRef.current += 1;
callback();
};
return subscribeToEvents(i18n, {
bindI18n,
bindI18nStore
}, wrappedCallback);
}, [i18n, bindI18n, bindI18nStore]);
const snapshotRef = (0, _react.useRef)();
const getSnapshot = (0, _react.useCallback)(() => {
if (!i18n) {
return notReadySnapshot;
}
const calculatedReady = !!(i18n.isInitialized || i18n.initializedStoreOnce) && namespaces.every(n => (0, _utils.hasLoadedNamespace)(n, i18n, i18nOptions));
const currentLng = props.lng || i18n.language;
const currentRevision = revisionRef.current;
const keyPrefixKey = typeof keyPrefix === 'function' ? (0, _i18next.keyFromSelector)(keyPrefix, {
...i18n.options,
ns: namespaces
}) : keyPrefix;
const lastSnapshot = snapshotRef.current;
if (lastSnapshot && lastSnapshot.i18n === i18n && lastSnapshot.ready === calculatedReady && lastSnapshot.lng === currentLng && lastSnapshot.namespaces === namespaces && lastSnapshot.keyPrefix === keyPrefixKey && lastSnapshot.revision === currentRevision) {
return lastSnapshot;
}
const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === 'fallback' ? namespaces : namespaces[0], keyPrefix, {
scopeNs: namespaces
});
const newSnapshot = {
t: calculatedT,
i18n,
ready: calculatedReady,
lng: currentLng,
namespaces,
keyPrefix: keyPrefixKey,
revision: currentRevision
};
snapshotRef.current = newSnapshot;
return newSnapshot;
}, [i18n, namespaces, keyPrefix, i18nOptions, props.lng]);
const [loadCount, setLoadCount] = (0, _react.useState)(0);
let {
t,
ready
} = (0, _react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
let pendingTranslationLoad;
if (i18n && useSuspense && !ready) {
pendingTranslationLoad = new Promise(resolve => {
const onLoaded = () => resolve();
if (props.lng) {
(0, _utils.loadLanguages)(i18n, props.lng, namespaces, onLoaded);
} else {
(0, _utils.loadNamespaces)(i18n, namespaces, onLoaded);
}
});
({
t,
ready
} = getSnapshot());
}
(0, _react.useEffect)(() => {
if (i18n && !ready && !useSuspense) {
const onLoaded = () => setLoadCount(c => c + 1);
if (props.lng) {
(0, _utils.loadLanguages)(i18n, props.lng, namespaces, onLoaded);
} else {
(0, _utils.loadNamespaces)(i18n, namespaces, onLoaded);
}
}
}, [i18n, props.lng, namespaces, ready, useSuspense, loadCount]);
const finalI18n = i18n || {};
const wrapperRef = (0, _react.useRef)(null);
const ret = (0, _react.useMemo)(() => {
const original = finalI18n;
const lang = languageKey(original);
if (wrapperRef.current?.original !== original || wrapperRef.current.lang !== lang) {
wrapperRef.current = {
original,
lang,
wrapper: createI18nWrapper(original)
};
}
const i18nWrapper = wrapperRef.current.wrapper;
const effectiveT = !ready && !useSuspense ? (...args) => {
(0, _utils.warnOnce)(i18n, 'USE_T_BEFORE_READY', 'useTranslation: t was called before ready. When using useSuspense: false, make sure to check the ready flag before using t.');
return t(...args);
} : t;
const arr = [effectiveT, i18nWrapper, ready];
arr.t = effectiveT;
arr.i18n = i18nWrapper;
arr.ready = ready;
return arr;
}, [t, finalI18n, ready, finalI18n.resolvedLanguage, finalI18n.language, finalI18n.languages]);
if (i18n && useSuspense && !ready) {
if ((0, _utils.inDevelopment)()) {
(0, _utils.warnOnce)(i18n, 'SUSPENDED_WHILE_LOADING', 'useTranslation: suspended while translations are loading (useSuspense is true by default). Add a <Suspense> boundary above this component, or set react.useSuspense: false in the i18next init options. https://react.i18next.com/latest/usetranslation-hook');
}
throw pendingTranslationLoad;
}
return ret;
};
exports.useTranslation = useTranslation;