@exabytellc/utils
Version:
EB react utils to make everything a little easier!
189 lines (170 loc) • 5.71 kB
JavaScript
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;