@scaleway/use-i18n
Version:
A small hook to handle i18n
282 lines (281 loc) • 8.35 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
import { formatDistanceToNowStrict } from "date-fns/formatDistanceToNowStrict";
import { createContext, useContext, useEffect, useMemo, useState, useCallback } from "react";
import formatDate from "./formatDate.js";
import formatUnit from "./formatUnit.js";
import formatters from "./formatters.js";
const LOCALE_ITEM_STORAGE = "locale";
const areNamespacesLoaded = (namespaces, loadedNamespaces = []) => namespaces.every((n) => loadedNamespaces.includes(n));
const getCurrentLocale = ({
defaultLocale,
isLocaleSupported,
localeItemStorage
}) => {
if (typeof window !== "undefined") {
const { languages: browserLocales } = navigator;
const currentLocalFromlocalStorage = localStorage.getItem(localeItemStorage);
if (currentLocalFromlocalStorage && isLocaleSupported(currentLocalFromlocalStorage)) {
return currentLocalFromlocalStorage;
}
localStorage.removeItem(localeItemStorage);
const findedBrowserLocal = browserLocales.find(
(locale) => isLocaleSupported(locale)
);
if (findedBrowserLocal) {
localStorage.setItem(localeItemStorage, findedBrowserLocal);
return findedBrowserLocal;
}
if (defaultLocale && isLocaleSupported(defaultLocale)) {
localStorage.setItem(localeItemStorage, defaultLocale);
return defaultLocale;
}
}
return defaultLocale;
};
const I18nContext = createContext(void 0);
function useI18n() {
const context = useContext(I18nContext);
if (context === void 0) {
throw new Error("useI18n must be used within a I18nProvider");
}
return context;
}
function useTranslation(namespaces = [], load = void 0) {
const context = useContext(I18nContext);
if (context === void 0) {
throw new Error("useTranslation must be used within a I18nProvider");
}
const { loadTranslations, namespaces: loadedNamespaces } = context;
const key = namespaces.join(",");
useEffect(() => {
key.split(",").map(async (namespace) => loadTranslations(namespace, load));
}, [loadTranslations, key, load]);
const isLoaded = useMemo(
() => areNamespacesLoaded(namespaces, loadedNamespaces),
[loadedNamespaces, namespaces]
);
return {
...context,
isLoaded
};
}
const initialDefaultTranslations = {};
const I18nContextProvider = ({
children,
defaultLoad,
defaultLocale,
defaultTranslations = initialDefaultTranslations,
enableDebugKey = false,
enableDefaultLocale = false,
loadDateLocale,
loadDateLocaleAsync,
localeItemStorage = LOCALE_ITEM_STORAGE,
onLoadDateLocaleError,
onTranslateError,
isLocaleSupported
}) => {
const [currentLocale, setCurrentLocale] = useState(
getCurrentLocale({
defaultLocale,
localeItemStorage,
isLocaleSupported
})
);
const [translations, setTranslations] = useState(defaultTranslations);
const [namespaces, setNamespaces] = useState([]);
const [dateFnsLocale, setDateFnsLocale] = useState(
loadDateLocale?.(currentLocale) ?? void 0
);
const loadDateFNS = loadDateLocale ?? loadDateLocaleAsync;
const setDateFns = useCallback(
async (locale) => {
try {
const dateFns = await loadDateFNS(locale);
setDateFnsLocale(dateFns);
} catch (err) {
if (err instanceof Error && onLoadDateLocaleError) {
onLoadDateLocaleError(err);
}
setDateFnsLocale(dateFnsLocale);
}
},
[loadDateFNS, setDateFnsLocale, onLoadDateLocaleError, dateFnsLocale]
);
useEffect(() => {
if (!dateFnsLocale) {
setDateFns(currentLocale).then().catch(() => null);
}
}, [currentLocale, dateFnsLocale, setDateFns, setDateFnsLocale]);
const loadTranslations = useCallback(
async (namespace, load = defaultLoad) => {
const result = {
[currentLocale]: { default: {} },
defaultLocale: { default: {} }
};
if (enableDefaultLocale && currentLocale !== defaultLocale) {
result.defaultLocale = await load({
locale: defaultLocale,
namespace
});
}
result[currentLocale] = await load({
locale: currentLocale,
namespace
});
const trad = {
...result.defaultLocale.default,
...result[currentLocale].default
};
setTranslations((prevState) => ({
...prevState,
[defaultLocale]: {
...prevState[defaultLocale],
...result.defaultLocale.default
},
[currentLocale]: {
...prevState[currentLocale],
...trad
}
}));
setNamespaces((prevState) => [.../* @__PURE__ */ new Set([...prevState, namespace])]);
return namespace;
},
[defaultLoad, currentLocale, enableDefaultLocale, defaultLocale]
);
const switchLocale = useCallback(
async (locale) => {
if (isLocaleSupported(locale)) {
localStorage.setItem(localeItemStorage, locale);
setCurrentLocale(locale);
await setDateFns(locale);
}
},
[setDateFns, localeItemStorage, setCurrentLocale, isLocaleSupported]
);
const formatNumber = useCallback(
(numb, options) => formatters.getNumberFormat(currentLocale, options).format(numb),
[currentLocale]
);
const formatList = useCallback(
(listFormat, options) => formatters.getListFormat(currentLocale, options).format(listFormat),
[currentLocale]
);
const formatUnit$1 = useCallback(
(value2, options) => formatUnit(currentLocale, value2, options),
[currentLocale]
);
const formatDate$1 = useCallback(
(value2, options = "short") => formatDate(currentLocale, value2, options),
[currentLocale]
);
const datetime = useCallback(
(date, options) => formatters.getDateTimeFormat(currentLocale, options).format(date),
[currentLocale]
);
const relativeTimeStrict = useCallback(
(date, options = {
addSuffix: true,
unit: "day"
}) => {
const finalDate = new Date(date);
return formatDistanceToNowStrict(finalDate, {
locale: dateFnsLocale,
...options
});
},
[dateFnsLocale]
);
const relativeTime = useCallback(
(date, options = { addSuffix: true }) => {
const finalDate = new Date(date);
return formatDistanceToNow(finalDate, {
locale: dateFnsLocale,
...options
});
},
[dateFnsLocale]
);
const translate = useCallback(
(key, context) => {
const value2 = translations[currentLocale]?.[key];
if (enableDebugKey) {
return key;
}
if (!value2) {
return "";
}
if (context) {
try {
return formatters.getTranslationFormat(value2, currentLocale).format(context);
} catch (err) {
onTranslateError?.({
error: err,
currentLocale,
defaultLocale,
value: value2,
key
});
const defaultValue = translations[defaultLocale]?.[key];
return formatters.getTranslationFormat(defaultValue, defaultLocale).format(context);
}
}
return value2;
},
[
currentLocale,
translations,
enableDebugKey,
defaultLocale,
onTranslateError
]
);
const namespaceTranslation = useCallback(
(scope, t = translate) => (key, context) => t(`${scope}.${key}`, context) || t(key, context),
[translate]
);
const value = useMemo(
() => ({
currentLocale,
dateFnsLocale,
datetime,
formatDate: formatDate$1,
formatList,
formatNumber,
formatUnit: formatUnit$1,
loadTranslations,
namespaces,
namespaceTranslation,
relativeTime,
relativeTimeStrict,
setTranslations,
switchLocale,
t: translate,
translations
}),
[
currentLocale,
dateFnsLocale,
datetime,
formatDate$1,
formatList,
formatNumber,
formatUnit$1,
loadTranslations,
namespaceTranslation,
namespaces,
relativeTime,
relativeTimeStrict,
setTranslations,
switchLocale,
translate,
translations
]
);
return /* @__PURE__ */ jsx(I18nContext.Provider, { value, children });
};
export {
I18nContextProvider as default,
useI18n,
useTranslation
};