@react-form-builder/core
Version:
React JSON Schema Form Builder to create complex, validated, reusable forms with no deep React knowledge required
280 lines (279 loc) • 8.87 kB
JavaScript
import { FluentResource as E } from "@fluent/bundle";
import { isLocalizedProperty as I } from "../../../stores/ComponentStore.js";
import { getEditableFormData as M } from "../../../utils/contexts/ComponentDataContext.js";
import { isString as p } from "../../../utils/isString.js";
import { isUndefined as f, isNull as C, isBoolean as v } from "../../../utils/tools.js";
import { getValidatorPropertyBlockType as $ } from "../../ui/PropertyBlockType.js";
import { globalDefaultLanguage as k } from "../default.js";
import { LocalizationError as V } from "../LocalizationError.js";
import { createFluentBundle as h } from "./createFluentBundle.js";
import { restoreDots as P, replaceDots as _, dotInternalValue as j } from "./dots.js";
import { FluentBundleCache as w, setupLocalizationBundles as F, populateFluentBundle as O } from "./fluentBundleSetup.js";
import { logFluentErrors as b } from "./fluentErrors.js";
import { getMessageWithFallback as D, resolveLocalizedMessageValue as T } from "./fluentMessageResolution.js";
import { fluentEncodeValue as q } from "./fluentResource.js";
import { isFluentVariable as A } from "./isFluentVariable.js";
import { createLocalizationDataProxy as R } from "./localizationDataProxy.js";
const B = "interpolate-tmp";
class se {
#l = new w();
#e = k.fullCode;
/**
* Constructor.
* @param locale the language full code, i.e. 'en-US'.
*/
constructor(e) {
this.#e = e ?? this.#e;
}
/**
* @inheritDoc
*/
set language(e) {
this.#e = e;
}
/**
* @inheritDoc
*/
get language() {
return this.#e;
}
/**
* @inheritDoc
*/
addMessages(e, t) {
return this.#l.addMessages(e, t, { allowOverrides: !0 });
}
/**
* @inheritDoc
*/
localizeProperties(e, t, r, s, o = "component") {
const n = {}, i = [], a = F(e, r.fullCode, i), u = this.#c(
e,
t,
s,
o,
r.fullCode,
a
);
return Object.entries(s.props).forEach(([l, m]) => {
I(m) && (n[l] = this.#m(l, u));
}), n;
}
/**
* @inheritDoc
*/
getCompatibleId(e) {
return e.replace(/\s/g, "_");
}
/**
* @inheritDoc
*/
localizeErrorMessage(e, t, r, s, o) {
const n = $(o), a = `${this.getCompatibleId(s.key)}_${n}_message`, u = [], { defaultBundle: l, defaultItems: m, formBundle: d, formItems: g } = F(e, r.fullCode, u), c = D(a, d, l, g, m);
return c ? this.#o(c.message, c.bundle, t) : void 0;
}
/**
* @inheritDoc
*/
testLocalization(e, t, r, s) {
const o = [], n = r.fullCode, i = O(
{ [t]: e },
o,
h(n)
), u = i.getMessage(t)?.value ?? "", l = [], { fluentData: m, missingProperties: d } = this.#r(s), g = P(i.formatPattern(u, m, l));
return b(l, d, !0), l.length > 0 ? l.map((c) => new V("MessageFormatError", c.message)) : g;
}
/**
* Formats a message pattern with form data.
* @param message the message to format.
* @param bundle the bundle to use for formatting.
* @param formData the form data for variable substitution.
* @param options formatting options.
* @param options.logMissing when true, logs missing property names used in placeholders.
* @returns the formatted result or undefined.
*/
#o(e, t, r, s) {
const o = [], { fluentData: n, missingProperties: i } = this.#r(r), a = t?.formatPattern(e, n, o);
return b(o, i, s?.logMissing), f(a) ? void 0 : P(a);
}
/**
* Builds fluent data proxy for the given form data.
* @param formData form data.
* @returns fluent data proxy and missing property collector.
*/
#r(e) {
const t = M(e), r = [];
return { fluentData: R(
this.#a(t),
r
), missingProperties: r };
}
/**
* Returns a lazy getter for fluent interpolation data.
* @param formData form data.
* @returns getter for fluent data proxy.
*/
#u(e) {
let t;
return () => (t ??= this.#r(e).fluentData, t);
}
/**
* Builds shared context for localizing all properties of one component.
* @param form the form instance.
* @param formData current form data.
* @param componentStore the component store.
* @param type localization type.
* @param requestedFullCode requested language full code.
* @param bundleSetup prepared fluent bundles and item maps.
* @returns component localization context.
*/
#c(e, t, r, s, o, n) {
const i = this.getCompatibleId(r.key);
return {
form: e,
formData: t,
componentStore: r,
type: s,
requestedFullCode: o,
messageIdPrefix: `${i}_${s}_`,
getFluentData: this.#u(t),
...n
};
}
/**
* Returns the bundle used for interpolation when form bundles are unavailable.
* @param context component localization context.
* @returns bundle for interpolation.
*/
#s(e) {
return e.formBundle ?? e.defaultBundle ?? h(e.requestedFullCode);
}
/**
* Resolves one localized component property.
* @param propertyKey component property key.
* @param context shared localization context for the component.
* @returns resolved property value.
*/
#m(e, t) {
const {
form: r,
formData: s,
componentStore: o,
type: n,
requestedFullCode: i,
messageIdPrefix: a,
formBundle: u,
defaultBundle: l,
formItems: m,
defaultItems: d,
getFluentData: g
} = t, c = r.localization.getLocalization(
i,
o.key,
e,
n
);
if (!f(c) && !p(c))
return this.#t(c, this.#s(t), g());
const y = `${a}${this.getCompatibleId(e)}`, z = this.#d(y, m, d);
if (!f(z))
return this.#t(z, this.#s(t), g());
const L = D(y, u, l, m, d);
return this.#f(L, e, r, o, s);
}
/**
* Resolves the localized component property value.
* @param localizedMessage localized message and source bundle.
* @param propertyKey component property key.
* @param form the form instance.
* @param componentStore the component store.
* @param formData current form data.
* @returns the resolved value.
*/
#f(e, t, r, s, o) {
return T(
e,
(n, i) => this.#o(n, i, o),
() => {
const n = s.props[t]?.value;
if (!f(n)) return n;
const i = r.componentTree?.findByKey(s.key)?.model?.defaultProps?.[t];
return f(i) ? s.type : i;
}
);
}
#d(e, t, r) {
const s = t?.[e], o = this.#n(s);
if (!f(o)) return o;
const n = r?.[e];
return this.#n(n);
}
#n(e) {
if (f(e)) return;
if (!p(e)) return e;
const t = e.trim();
if (t.startsWith("{") || t.startsWith("["))
try {
return JSON.parse(t);
} catch {
return;
}
}
/**
* Interpolates a single string through a temporary Fluent bundle.
* @param value the string potentially containing Fluent placeholders.
* @param bundle the Fluent bundle to use for formatting context.
* @param fluentData the flattened form data for variable substitution.
* @returns the interpolated string, or the original string if it contains no placeholders.
*/
#i(e, t, r) {
if (!e.includes("{")) return e;
const s = _(e), o = h(t.locales[0]), n = new E(`${B} = ${q(s)}`);
o.addResource(n);
const i = o.getMessage(B);
if (!i?.value) return e;
const a = [], u = o.formatPattern(i.value, r, a);
return P(u);
}
/**
* Recursively traverses data of any shape and interpolates Fluent placeholders in string keys and values.
* @param data the data to interpolate (can be any shape: primitive, array, or object).
* @param bundle the Fluent bundle to use for formatting context.
* @param fluentData the flattened form data for variable substitution.
* @returns the data with all string keys and values interpolated.
*/
#t(e, t, r) {
if (p(e))
return this.#i(e, t, r);
if (Array.isArray(e))
return e.map((s) => this.#t(s, t, r));
if (typeof e == "object" && !C(e)) {
const s = {};
for (const [o, n] of Object.entries(e)) {
const i = this.#i(o, t, r);
s[i] = this.#t(n, t, r);
}
return s;
}
return e;
}
/**
* Converts the form data to a Fluent compatible. **Internal use only.**
* @param data the form data.
* @param parentKey the parent property key.
* @returns all the form data that is of the FluentVariable type.
* Additionally, the keys of the returned object are converted to the snake case.
*/
#a = (e, t = "") => {
const r = {};
for (const [s, o] of Object.entries(e)) {
const n = t ? `${t}${j}${s}` : s;
A(o) ? r[this.getCompatibleId(n)] = o : v(o) ? r[this.getCompatibleId(n)] = o ? "true" : "false" : typeof o == "object" && !C(o) && Object.assign(r, this.#a(o, n));
}
return r;
};
}
export {
se as FluentLocalizationEngine
};
//# sourceMappingURL=FluentLocalizationEngine.js.map