andrei-bread-i18n
Version:
Small and type-safe package to create multi-language interfaces.
123 lines (105 loc) • 2.97 kB
text/typescript
import {
useCallback,
useContext,
useEffect,
useReducer,
useRef,
useState,
} from "react";
import type { I18N } from "../i18n";
import { I18NContext } from "./context";
function useI18NContext() {
const i18n = useContext(I18NContext);
if (!i18n) {
throw new Error("can not `useI18NContext` outside of the `I18NProvider`");
}
return i18n;
}
interface ReactI18N<I18NType extends I18N<any>> {
readonly lang: ReturnType<I18NType["getLang"]>;
get: I18NType["get"];
/**
* @deprecated use `lang` instead
*/
getLang: I18NType["getLang"];
setLang: I18NType["setLang"];
subscribe: I18NType["subscribe"];
}
export function useI18N<I18NType extends I18N<any>>() {
const i18n = useI18NContext() as I18NType;
const [{ langState, updateCount }, setLangState] = useState(() => ({
langState: i18n.getLang(),
updateCount: 0,
}));
const usesLang = useRef(false);
useEffect(() => {
i18n.subscribe((lang) => {
// only update the state if
// the lang is used
if (!usesLang.current) {
return;
}
setLangState((state) => ({
langState: lang,
updateCount: state.updateCount + 1,
}));
});
}, [i18n]);
const get: typeof i18n.get = useCallback(
(key, ...rest) => {
usesLang.current = true;
return i18n.get(key, ...rest);
},
// include the `updateCount` into the deps array
// so that `get` function changes it's reference whenever
// the languages changes or the translations are loaded
[i18n, updateCount]
);
const getLang: typeof i18n.getLang = useCallback(
() => {
usesLang.current = true;
return i18n.getLang();
},
// include the `langState` into the deps array
// so that `get` function changes it's reference whenever
// the languages changes
[i18n, langState]
);
const setLang: typeof i18n.setLang = useCallback(
(newLang) => i18n.setLang(newLang),
[i18n]
);
const subscribe: typeof i18n.subscribe = useCallback(
(cb, options) => i18n.subscribe(cb, options),
[i18n]
);
const reactI18N = {
get lang() {
usesLang.current = true;
return langState;
},
get,
getLang,
setLang,
subscribe,
} as ReactI18N<I18NType>;
return reactI18N;
}
export function useTranslate<I18NType extends I18N<any>>() {
const i18n = useI18NContext();
const [updateCount, triggerUpdate] = useReducer((v) => v + 1, 0);
useEffect(() => {
return i18n.subscribe(() => {
triggerUpdate();
});
}, []);
const translate: I18NType["get"] = useCallback(
(key, ...rest) => {
return i18n.get(key, ...rest);
},
// include the `updateCount` into the deps array
// so that translate changes it's reference whenever the language changes
[updateCount]
);
return translate;
}