UNPKG

@scaleway/use-i18n

Version:
282 lines (281 loc) 8.35 kB
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 };