import { useEffect, useState } from "react";
import { Localisation } from "../../plugins/localisation";
import type { TLocalisationResourceAddedEvent } from "../../plugins/localisation/types";
type TUseLocalisationPluginReturn = {
isLoading: boolean;
isReady: boolean;
isError: boolean;
plugin: Localisation | null;
};
/**
* Хук для использования плагина локализации. Возвращает экземпляр вызова и состояние загрузки.
* Если передан `code`, хук будет ожидать доступность указанного ключа локали,
* даже если она подгружается асинхронно после инициализации плагина.
*
* @param code{string} Код локали, доступность которого необходимо проверить
* @returns {TUseLocalisationPluginReturn} Возвращает объект с экземпляром плагина локализации (`plugin`),
* флагом готовности (`isReady`), флагом загрузки (`isLoading`) и флагом ошибки (`isError`).
* @example
* import { useLocalisationPlugin } from "@delement/ui/utils/react";
*
* const App = () => {
* const { plugin, isReady, isLoading } = useLocalisationPlugin("forms.invalidInput");
* return
{isReady ? plugin.getLocale("forms.invalidInput") : "loading"}
;
* };
*/
export const useLocalisationPlugin = (code?: string): TUseLocalisationPluginReturn => {
// экземпляр вызова плагина
const [ plugin, setPlugin ] = useState(() => Localisation.instance);
// Функция для определения начального состояния готовности
const getInitialReadyState = () => {
if (!Localisation.instance) {
return false;
}
if (!code) {
return Localisation.isReady;
}
return Localisation.isReady && Localisation.instance.isLocaleExist(code);
};
// состояние готовности
const [ isReady, setIsReady ] = useState(() => getInitialReadyState());
// состояние загрузки
const [ isLoading, setIsLoading ] = useState(() => !getInitialReadyState());
// состояние ошибки
const [ isError, setIsError ] = useState(false);
useEffect(() => {
let isMounted = true;
let unsubscribeResourceAdded: (() => void) | null = null;
let unsubscribeStoreAdded: (() => void) | null = null;
/**
* Отписывается от всех слушателей событий добавления ресурсов
*/
const unsubscribeAll = () => {
if (unsubscribeResourceAdded) {
unsubscribeResourceAdded();
unsubscribeResourceAdded = null;
}
if (unsubscribeStoreAdded) {
unsubscribeStoreAdded();
unsubscribeStoreAdded = null;
}
};
/**
* Проверяет доступность локали и обновляет состояния.
* Если локаль найдена — отписывается от всех слушателей.
*/
const checkLocaleAvailability = (instance: Localisation) => {
if (!isMounted) {
return;
}
if (!code) {
// Код не передан — плагин готов
setIsReady(true);
setIsLoading(false);
setIsError(false);
return;
}
if (instance.isLocaleExist(code)) {
// Локаль доступна — всё готово, отписываемся от слушателей
setIsReady(true);
setIsLoading(false);
setIsError(false);
unsubscribeAll();
} else {
// Локаль ещё не загружена — продолжаем ждать
setIsReady(false);
setIsLoading(true);
setIsError(false);
}
};
/**
* Обработчик события добавления ресурсов.
* Содержит точную информацию о добавленных ключах.
*/
const handleResourceAdded = (data: TLocalisationResourceAddedEvent) => {
if (!isMounted || !plugin || !code) {
return;
}
if (typeof data.isHasCode === "function" && data.isHasCode(code)) {
checkLocaleAvailability(plugin);
}
};
/**
* Обработчик СТАНДАРТНОГО события i18next store "added".
* Используется как fallback, если ресурсы добавляются не через наши методы.
*/
const handleStoreAdded = (_lng: string, _ns: string) => {
if (!isMounted || !plugin) {
return;
}
// Перепроверяем доступность локали при любом изменении store
checkLocaleAvailability(plugin);
};
/**
* Обработчик готовности плагина
*/
const handlePluginReady = (instance: Localisation) => {
if (!isMounted) {
return;
}
setPlugin(instance);
// Проверяем доступность ДО подписки
if (instance.isLocaleExist(code)) {
// Локаль уже существует — сразу отмечаем как готово
setIsReady(true);
setIsLoading(false);
setIsError(false);
return; // Без подписки
}
// Локали нет — подписываемся и ждём
if (code) {
setIsReady(false);
setIsLoading(true);
setIsError(false);
unsubscribeResourceAdded = Localisation.onResourceAdded(handleResourceAdded);
instance.lib.store.on("added", handleStoreAdded);
unsubscribeStoreAdded = () => {
instance.lib.store.off("added", handleStoreAdded);
};
} else {
// Без кода — просто готово
setIsReady(true);
setIsLoading(false);
setIsError(false);
}
};
// Если плагин уже готов
if (Localisation.instance && Localisation.isReady) {
handlePluginReady(Localisation.instance);
} else {
// Иначе ждём инициализации
setIsReady(false);
setIsLoading(true);
setIsError(false);
Localisation.addReadyCallback(handlePluginReady);
Localisation.create()
.catch((err: unknown) => {
if (!isMounted) {
return;
}
setIsReady(false);
setIsError(true);
setIsLoading(false);
setPlugin(null);
console.error(`[useLocalisationPlugin] Localisation plugin is crushed with error`, err);
});
}
return () => {
isMounted = false;
unsubscribeAll();
};
}, [ code, plugin ]);
return {
isLoading,
isReady,
isError,
plugin,
};
};