@gulibs/react-vintl
Version:
Type-safe i18n library for React with Vite plugin and automatic type inference
227 lines (226 loc) • 6.79 kB
JavaScript
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 };