UNPKG

@exabytellc/utils

Version:

EB react utils to make everything a little easier!

189 lines (170 loc) 5.71 kB
import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react"; import createBlocContext from "../createBlocContext"; import { Cookie } from "../storage"; const TranslationBloc = createBlocContext(({ locales = {}, defaultLocale, browserLocale = true, cookieKey = 'locale' }) => { /** * Detect the locale from cookies if it's supported. * @param {string[]} keys - Supported locale keys. * @returns {string|null} The locale from cookies or null. */ const getCookieLocale = (keys) => keys.includes(Cookie.get(cookieKey)) ? Cookie.get(cookieKey) : null; /** * Detect the locale from the browser if it's supported. * @param {string[]} keys - Supported locale keys. * @returns {string|null} The locale from the browser or null. */ const getBrowserLocale = (keys) => { const browserLocale = (navigator.language || navigator.userLanguage).split( "-" )[0]; return keys.includes(browserLocale) ? browserLocale : null; }; /** * Cleans a string key for consistent usage. * - Converts to lowercase. * - Replaces spaces with hyphens. * - Removes special characters except hyphens. * - Collapses multiple hyphens into one. * @param {string} txt - The key to clean. * @returns {string} The cleaned key. */ const cleanKey = (txt) => (!!txt && txt .toLowerCase() .trim() .replace(/\s+/g, "-") .replace(/[^a-z0-9-]/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, "")) || null; /** * Formats text for display. * - Capitalizes the first letter. * - Replaces hyphens and underscores with spaces. * @param {string} txt - The text to clean. * @returns {string} The formatted text. */ const cleanText = (txt) => (!!txt && txt[0].toUpperCase() + txt.slice(1).replaceAll("-", " ").replaceAll("_", " ")) || ""; // Locale state const [locale, setLocale] = useState(() => { const keys = Object.keys(locales); return ( getCookieLocale(keys) || (browserLocale && getBrowserLocale(keys)) || defaultLocale || keys[0] ); }); // Direction state const [dir, setDir] = useState(() => locales?.[locale]?.dir || "ltr"); // Reference to store untranslated keys const untranslated = useRef({}); /** * Translates a key to its corresponding value in the current locale. * If the key is not found, it adds it to the untranslated list. * @param {string} key - The key to translate. * @returns {string} The translated value or "N/A" if not found. */ const t = useCallback( (key, { l = null } = {}) => { const k = cleanKey(key); const val = locales?.[l || locale]?.data?.[k]; if (k && !val) { untranslated.current = { ...locales?.[l || locale]?.data, ...untranslated.current, [k]: cleanText(key), }; console.log("untranslated", untranslated.current); } return val || "N/A"; }, [locale, locales] ); /** * Retrieves a localized value from an object based on the current locale. * @param {Object} obj - The object containing localized keys. * @param {string} base - The base key to look for. * @returns {any} The localized value or null. */ const tObj = useCallback( (obj, base) => obj?.[`${base}${locale !== "en" ? `_${locale}` : ""}`] || null, [locale] ); /** * Changes the application's current locale and updates relevant settings. * @param {string|function} newLocale - The new locale or a function to derive it. */ const changeLocale = useCallback( (newLocale) => { if (typeof newLocale === "function") newLocale = newLocale(locale); setLocale(newLocale); setDir(locales?.[newLocale]?.dir || "ltr"); document.documentElement.lang = newLocale; document.documentElement.dir = locales?.[newLocale]?.dir || "ltr"; }, [locale, locales] ); /** * Retrieves a value for the current locale from an object, or a fallback. * @param {Object} values - The object containing locale-specific values. * @param {any} fallback - The fallback value if the locale-specific value is missing. * @returns {any} The locale-specific value or the fallback. */ const forLocale = useCallback( (values, fallback) => values?.[locale] || fallback, [locale] ); /** * Returns a value based on the current text direction (LTR or RTL). * @param {any} ltr - The value for left-to-right layouts. * @param {any} rtl - The value for right-to-left layouts. * @returns {any} The value matching the current text direction. */ const forDir = useCallback((ltr, rtl) => (dir === "rtl" ? rtl : ltr), [dir]); // Set initial locale and direction on mount useLayoutEffect(() => { document.documentElement.lang = locale; document.documentElement.dir = dir; Cookie.set(cookieKey, locale); }, [locale, dir, cookieKey]); useEffect(() => { untranslated.current = {}; // clean translation window.cleanTranslation = () => { var clean = {}; for (var l in locales) { clean[l] = {}; for (var k in locales[l]?.data) { clean[l][cleanKey(k)] = locales[l].data[k]; } } return clean; } }); return { locale, dir, localesKeys: Object.keys(locales), localesData: locales, t, tObj, forLocale, forDir, changeLocale, }; }); export default TranslationBloc;