react-i18next
Version:
Internationalization for react done right. Using the i18next i18n ecosystem.
84 lines (71 loc) • 2.87 kB
JavaScript
import { useState, useEffect, useContext } from 'react';
import {
getI18n,
getDefaults,
ReportNamespaces,
getHasUsedI18nextProvider,
I18nContext,
} from './context';
import { warnOnce, loadNamespaces, hasLoadedNamespace } from './utils';
export function useTranslation(ns, props = {}) {
// assert we have the needed i18nInstance
const { i18n: i18nFromProps } = props;
const { i18n: i18nFromContext } = getHasUsedI18nextProvider() ? useContext(I18nContext) : {};
const i18n = i18nFromProps || i18nFromContext || getI18n();
if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces();
if (!i18n) {
warnOnce('You will need pass in an i18next instance by using i18nextReactModule');
const retNotReady = [k => k, {}, true];
retNotReady.t = k => k;
retNotReady.i18n = {};
retNotReady.ready = true;
return retNotReady;
}
const i18nOptions = { ...getDefaults(), ...i18n.options.react };
// prepare having a namespace
let namespaces = ns || (i18n.options && i18n.options.defaultNS);
namespaces = typeof namespaces === 'string' ? [namespaces] : namespaces || ['translation'];
// report namespaces as used
if (i18n.reportNamespaces.addUsedNamespaces) i18n.reportNamespaces.addUsedNamespaces(namespaces);
// are we ready? yes if all namespaces in first language are loaded already (either with data or empty objedt on failed load)
const ready =
(i18n.isInitialized || i18n.initializedStoreOnce) &&
namespaces.every(n => hasLoadedNamespace(n, i18n));
// set states
const [t, setT] = useState({ t: i18n.getFixedT(null, namespaces[0]) }); // seems we can't have functions as value -> wrap it in obj
function resetT() {
setT({ t: i18n.getFixedT(null, namespaces[0]) });
}
useEffect(() => {
const { bindI18n, bindI18nStore } = i18nOptions;
// bind events to trigger change, like languageChanged
if (bindI18n && i18n) i18n.on(bindI18n, resetT);
if (bindI18nStore && i18n) i18n.store.on(bindI18nStore, resetT);
// unbinding
return () => {
if (bindI18n && i18n) bindI18n.split(' ').forEach(e => i18n.off(e, resetT));
if (bindI18nStore && i18n) bindI18nStore.split(' ').forEach(e => i18n.store.off(e, resetT));
};
});
const ret = [t.t, i18n, ready];
ret.t = t.t;
ret.i18n = i18n;
ret.ready = ready;
// return hook stuff if ready
if (ready) return ret;
// not yet loaded namespaces -> load them -> and return if useSuspense option set false
const { useSuspense = i18nOptions.useSuspense } = props;
if (!ready && !useSuspense) {
loadNamespaces(i18n, namespaces, () => {
resetT();
});
return ret;
}
// not yet loaded namespaces -> load them -> and trigger suspense
throw new Promise(resolve => {
loadNamespaces(i18n, namespaces, () => {
resetT();
resolve();
});
});
}