UNPKG

@gulibs/react-vintl

Version:

Type-safe i18n library for React with Vite plugin and automatic type inference

227 lines (226 loc) 6.79 kB
import { createContext, useCallback, useContext, useMemo, useState } from "react"; import { jsx } from "react/jsx-runtime"; function getNestedValue(e, h) { return h.split(".").reduce((e, h) => { if (typeof e == "object" && e && h in e) return e[h]; }, e); } function replaceParams(e, h = {}) { return e.replace(/\{\{([^}]+)\}\}/g, (e, g) => { try { return processInterpolation(g.trim(), h); } catch (h) { return console.warn(`[react-vintl] Failed to process interpolation "${e}":`, h), e; } }); } function processInterpolation(e, h) { let g = e.split(",").map((e) => e.trim()), _ = g[0]; if (!_) throw Error("Missing parameter key"); let v = getNestedParamValue(h, _); if (v === void 0) throw Error(`Parameter "${_}" not found`); if (g.length === 1) return String(v); let y = g[1], b = g.slice(2); switch (y) { case "plural": return handlePlural(v, b); case "select": return handleSelect(v, b); case "number": return handleNumber(v, b); case "date": return handleDate(v, b); case "currency": return handleCurrency(v, b); default: return handleCustomFormat(v, y, b); } } function getNestedParamValue(e, h) { return h.split(".").reduce((e, h) => { if (typeof e == "object" && e && h in e) return e[h]; }, e); } function handlePlural(e, h) { let g = Number(e); if (isNaN(g)) return String(e); let _ = {}; for (let e of h) { let [h, g] = e.split("=", 2).map((e) => e.trim()); h && g && (_[h] = g); } let v = "other"; return g === 1 && "one" in _ ? v = "one" : g === 0 && "zero" in _ ? v = "zero" : g > 1 && "few" in _ ? v = "few" : g > 1 && "many" in _ && (v = "many"), (_[v] || _.other || String(e)).replace(/#/g, String(g)); } function handleSelect(e, h) { let g = String(e), _ = {}; for (let e of h) { let [h, g] = e.split("=", 2).map((e) => e.trim()); h && g && (_[h] = g); } return _[g] || _.other || g; } function handleNumber(e, h) { let g = Number(e); if (isNaN(g)) return String(e); let _ = {}; for (let e of h) { let [h, g] = e.split("=", 2).map((e) => e.trim()); if (h && g) { let e = Number(g); isNaN(e) || (_[h] = e); } } try { return new Intl.NumberFormat(void 0, _).format(g); } catch { return String(g); } } function handleDate(e, h) { let g; if (e instanceof Date) g = e; else if (typeof e == "string" || typeof e == "number") g = new Date(e); else return String(e); if (isNaN(g.getTime())) return String(e); let _ = {}; for (let e of h) { let [h, g] = e.split("=", 2).map((e) => e.trim()); h && g && (_[h] = g); } try { return new Intl.DateTimeFormat(void 0, _).format(g); } catch { return g.toLocaleDateString(); } } function handleCurrency(e, h) { let g = Number(e); if (isNaN(g)) return String(e); let _ = { style: "currency" }; for (let e of h) { let [h, g] = e.split("=", 2).map((e) => e.trim()); if (h && g) if (h === "currency") _.currency = g; else { let e = Number(g); isNaN(e) || (_[h] = e); } } try { return new Intl.NumberFormat(void 0, _).format(g); } catch { return String(g); } } function handleCustomFormat(e, h, g) { switch (h) { case "uppercase": return String(e).toUpperCase(); case "lowercase": return String(e).toLowerCase(); case "capitalize": return String(e).charAt(0).toUpperCase() + String(e).slice(1).toLowerCase(); default: return String(e); } } function validateParams(e) { if (!e || typeof e != "object") return !1; try { return JSON.stringify(e), !0; } catch { return console.warn("[react-vintl] Invalid params object: contains circular reference"), !1; } } function createTranslationFunction(e, h) { return (g, _) => { let v = e[h]; if (!v) return console.warn(`[react-vintl] Locale "${h}" not found in resources`), g; let y = getNestedValue(v, g); if (typeof y != "string") return console.warn(`[react-vintl] Translation key "${g}" not found or not a string in locale "${h}"`), g; if (_ && !validateParams(_)) return console.warn(`[react-vintl] Invalid params for key "${g}":`, _), y; try { return _ ? replaceParams(y, _) : y; } catch (e) { return console.error(`[react-vintl] Failed to process translation for key "${g}":`, e), y; } }; } function validateResources(e) { if (!e || typeof e != "object") return console.error("Resources must be an object"), !1; let h = Object.keys(e); if (h.length === 0) return console.error("Resources must contain at least one locale"), !1; let g = h[0], _ = getAllKeys(e[g]); for (let g of h.slice(1)) { let h = getAllKeys(e[g]), v = _.filter((e) => !h.includes(e)), y = h.filter((e) => !_.includes(e)); v.length > 0 && console.warn(`Locale "${g}" is missing keys: ${v.join(", ")}`), y.length > 0 && console.warn(`Locale "${g}" has extra keys: ${y.join(", ")}`); } return !0; } function getAllKeys(e, h = "") { let g = []; if (typeof e != "object" || !e || Array.isArray(e)) return g; for (let _ in e) if (Object.prototype.hasOwnProperty.call(e, _)) { let v = h ? `${h}.${_}` : _, y = e[_]; typeof y == "object" && y && !Array.isArray(y) ? g.push(...getAllKeys(y, v)) : g.push(v); } return g; } var I18nContext = createContext(null); function I18nProvider({ resources: e, defaultLocale: h, supportedLocales: g, children: b }) { if (!validateResources(e)) throw Error("Invalid i18n resources"); let x = g || Object.keys(e), S = h || x[0]; if (!x.includes(S)) throw Error(`Default locale "${S}" is not in supported locales: ${x.join(", ")}`); let [C, w] = useState(S), T = useMemo(() => createTranslationFunction(e, C), [e, C]), E = useMemo(() => ({ locale: C, supportedLocales: x, t: T, setLocale: w, resources: e }), [ C, x, T, e ]); return /* @__PURE__ */ jsx(I18nContext.Provider, { value: E, children: b }); } function useI18nContext() { let e = useContext(I18nContext); if (!e) throw Error("useI18nContext must be used within an I18nProvider"); return e; } function useTranslation(e) { let h = useI18nContext(); return e ? (g, _) => { let v = `${e}.${g}`; return h.t(v, _); } : h.t; } function useI18n() { let e = useI18nContext(); return { locale: e.locale, supportedLocales: e.supportedLocales, setLocale: e.setLocale, resources: e.resources }; } function useTranslationKey(e, g) { let _ = useTranslation(), v = useCallback((h) => _(e, h || g), [ _, e, g ]); return { translation: useCallback(() => _(e, g), [ _, e, g ])(), t: v }; } function useLocale() { let { locale: e, supportedLocales: g, setLocale: _ } = useI18n(), v = useCallback((e) => g.includes(e), [g]); return { locale: e, supportedLocales: g, setLocale: _, isSupportedLocale: v }; } export { I18nProvider, createTranslationFunction, getNestedValue, replaceParams, useI18n, useI18nContext, useLocale, useTranslation, useTranslationKey, validateResources };