react-intlayer
Version:
Easily internationalize i18n your React applications with type-safe multilingual content management.
234 lines (232 loc) • 9.82 kB
JavaScript
import { useLoadDynamic } from "./client/useLoadDynamic.mjs";
import { ContentSelector } from "./editor/ContentSelector.mjs";
import { renderIntlayerNode } from "./IntlayerNode.mjs";
import { renderReactElement } from "./reactElement/renderReactElement.mjs";
import { editor, internationalization } from "@intlayer/config/built";
import { conditionPlugin, enumerationPlugin, fallbackPlugin, filePlugin, genderPlugin, nestedPlugin, pluralPlugin, splitInsertionTemplate, translationPlugin } from "@intlayer/core/interpreter";
import { getMarkdownMetadata } from "@intlayer/core/markdown";
import * as NodeTypes from "@intlayer/types/nodeType";
import { Fragment, createElement } from "react";
import { jsx } from "react/jsx-runtime";
//#region src/plugins.tsx
const markdownRendererModulePromise = process.env["INTLAYER_NODE_TYPE_MARKDOWN"] !== "false" ? import("./markdown/MarkdownRendererPlugin.mjs").then((m) => m.MarkdownRendererPlugin) : null;
const htmlRendererModulePromise = process.env["INTLAYER_NODE_TYPE_HTML"] !== "false" ? import("./html/HTMLRendererPlugin.mjs").then((m) => m.HTMLRendererPlugin) : null;
/** Translation plugin. Replaces node with a locale string if nodeType = Translation. */
const intlayerNodePlugins = {
id: "intlayer-node-plugin",
canHandle: (node) => typeof node === "bigint" || typeof node === "string" || typeof node === "number",
transform: (_node, { plugins, ...rest }) => renderIntlayerNode({
...rest,
value: rest.children,
children: process.env["INTLAYER_EDITOR_ENABLED"] !== "false" && editor.enabled ? /* @__PURE__ */ jsx(ContentSelector, {
...rest,
children: rest.children
}) : rest.children
})
};
/** Translation plugin. Replaces node with a locale string if nodeType = Translation. */
const reactNodePlugins = process.env["INTLAYER_NODE_TYPE_REACT_NODE"] === "false" ? fallbackPlugin : {
id: "react-node-plugin",
canHandle: (node) => typeof node === "object" && typeof node?.props !== "undefined" && typeof node.key !== "undefined",
transform: (node, { plugins, ...rest }) => renderIntlayerNode({
...rest,
value: "[[react-element]]",
children: process.env["INTLAYER_EDITOR_ENABLED"] !== "false" && editor.enabled ? /* @__PURE__ */ jsx(ContentSelector, {
...rest,
children: renderReactElement(node)
}) : renderReactElement(node)
})
};
/**
* Split insertion string and join with React nodes using shared core logic
*/
const splitAndJoinInsertion = (template, values) => {
const result = splitInsertionTemplate(template, values);
if (result.isSimple) return result.parts;
return createElement(Fragment, null, ...result.parts.map((part, index) => createElement(Fragment, { key: index }, part)));
};
/** Insertion plugin for React. Handles component/node insertion. */
const insertionPlugin = process.env["INTLAYER_NODE_TYPE_INSERTION"] === "false" ? fallbackPlugin : {
id: "insertion-plugin",
canHandle: (node) => typeof node === "object" && node?.nodeType === NodeTypes.INSERTION,
transform: (node, props, deepTransformNode) => {
const newKeyPath = [...props.keyPath, { type: NodeTypes.INSERTION }];
const children = node[NodeTypes.INSERTION];
/** Insertion string plugin. Replaces string node with a component that render the insertion. */
const insertionStringPlugin = {
id: "insertion-string-plugin",
canHandle: (node) => typeof node === "string",
transform: (node, subProps, deepTransformNode) => {
const transformedResult = deepTransformNode(node, {
...subProps,
children: node,
plugins: [...(props.plugins ?? []).filter((plugin) => plugin.id !== "intlayer-node-plugin")]
});
return (values) => {
const result = splitAndJoinInsertion(transformedResult, values);
return deepTransformNode(result, {
...subProps,
plugins: props.plugins,
children: result
});
};
}
};
const result = deepTransformNode(children, {
...props,
children,
keyPath: newKeyPath,
plugins: [insertionStringPlugin, ...props.plugins ?? []]
});
if (typeof children === "object" && children !== null && "nodeType" in children && [NodeTypes.ENUMERATION, NodeTypes.CONDITION].includes(children.nodeType)) return (values) => (arg) => {
const inner = result(arg);
if (typeof inner === "function") return inner(values);
return inner;
};
return result;
}
};
const MarkdownSuspenseRenderer = ({ children, ...props }) => {
return createElement(useLoadDynamic("markdown-renderer-plugin", markdownRendererModulePromise), {
...props,
children
});
};
/** Markdown string plugin. Replaces string node with a component that render the markdown. */
const markdownStringPlugin = process.env["INTLAYER_NODE_TYPE_MARKDOWN"] === "false" ? fallbackPlugin : {
id: "markdown-string-plugin",
canHandle: (node) => typeof node === "string",
transform: (node, props, deepTransformNode) => {
const { plugins, ...rest } = props;
const metadataNodes = deepTransformNode(getMarkdownMetadata(node) ?? {}, {
plugins: [{
id: "markdown-metadata-plugin",
canHandle: (metadataNode) => typeof metadataNode === "string" || typeof metadataNode === "number" || typeof metadataNode === "boolean" || !metadataNode,
transform: (metadataNode, props) => renderIntlayerNode({
...props,
value: metadataNode,
children: process.env["INTLAYER_EDITOR_ENABLED"] !== "false" && editor.enabled ? /* @__PURE__ */ jsx(ContentSelector, {
...rest,
children: node
}) : node
})
}],
dictionaryKey: rest.dictionaryKey,
keyPath: []
});
const render = (components) => renderIntlayerNode({
...props,
value: node,
children: process.env["INTLAYER_EDITOR_ENABLED"] !== "false" && editor.enabled ? /* @__PURE__ */ jsx(ContentSelector, {
...rest,
children: /* @__PURE__ */ jsx(MarkdownSuspenseRenderer, {
...rest,
components,
children: node
})
}) : /* @__PURE__ */ jsx(MarkdownSuspenseRenderer, {
...rest,
components,
children: node
}),
additionalProps: { metadata: metadataNodes }
});
const element = render();
return new Proxy(element, { get(target, prop, receiver) {
if (prop === "value") return node;
if (prop === Symbol.toPrimitive) return () => node;
if (prop === "toString") return () => node;
if (prop === "valueOf") return () => node;
if (typeof prop === "string" && prop !== "constructor") {
const method = String.prototype[prop];
if (typeof method === "function") return method.bind(node);
}
if (prop === "metadata") return metadataNodes;
if (prop === "use") return (components) => render(components);
return Reflect.get(target, prop, receiver);
} });
}
};
const markdownPlugin = process.env["INTLAYER_NODE_TYPE_MARKDOWN"] === "false" ? fallbackPlugin : {
id: "markdown-plugin",
canHandle: (node) => typeof node === "object" && node?.nodeType === NodeTypes.MARKDOWN,
transform: (node, props, deepTransformNode) => {
const newKeyPath = [...props.keyPath, { type: NodeTypes.MARKDOWN }];
const children = node[NodeTypes.MARKDOWN];
return deepTransformNode(children, {
...props,
children,
keyPath: newKeyPath,
plugins: [markdownStringPlugin, ...props.plugins ?? []]
});
}
};
const HTMLSuspenseRenderer = (props) => {
return createElement(useLoadDynamic("html-renderer-plugin", htmlRendererModulePromise), props);
};
/** HTML plugin. Replaces node with a function that takes components => ReactNode. */
const htmlPlugin = process.env["INTLAYER_NODE_TYPE_HTML"] === "false" ? fallbackPlugin : {
id: "html-plugin",
canHandle: (node) => typeof node === "object" && node?.nodeType === NodeTypes.HTML,
transform: (node, props) => {
const html = node[NodeTypes.HTML];
const { plugins, ...rest } = props;
const render = (userComponents) => renderIntlayerNode({
...rest,
value: html,
children: process.env["INTLAYER_EDITOR_ENABLED"] !== "false" && editor.enabled ? /* @__PURE__ */ jsx(ContentSelector, {
...rest,
children: /* @__PURE__ */ jsx(HTMLSuspenseRenderer, {
...rest,
html,
userComponents
})
}) : /* @__PURE__ */ jsx(HTMLSuspenseRenderer, {
...rest,
html,
userComponents
})
});
const element = render();
return new Proxy(element, { get(target, prop, receiver) {
if (prop === "value") return html;
if (prop === Symbol.toPrimitive) return () => html;
if (prop === "toString") return () => html;
if (prop === "valueOf") return () => html;
if (typeof prop === "string" && prop !== "constructor") {
const method = String.prototype[prop];
if (typeof method === "function") return method.bind(html);
}
if (prop === "use") return (userComponents) => render(userComponents);
return Reflect.get(target, prop, receiver);
} });
}
};
const pluginsCache = /* @__PURE__ */ new Map();
/**
* Get the plugins array for React content transformation.
* This function is used by both getIntlayer and getDictionary to ensure consistent plugin configuration.
*/
const getPlugins = (locale, fallback = true) => {
const cacheKey = `${locale ?? internationalization.defaultLocale}_${fallback}`;
if (pluginsCache.has(cacheKey)) return pluginsCache.get(cacheKey);
const plugins = [
translationPlugin(locale ?? internationalization.defaultLocale, fallback ? internationalization.defaultLocale : void 0),
enumerationPlugin,
pluralPlugin(locale ?? internationalization.defaultLocale),
conditionPlugin,
nestedPlugin(locale ?? internationalization.defaultLocale),
filePlugin,
genderPlugin,
intlayerNodePlugins,
reactNodePlugins,
insertionPlugin,
markdownPlugin,
htmlPlugin
];
pluginsCache.set(cacheKey, plugins);
return plugins;
};
//#endregion
export { getPlugins, htmlPlugin, insertionPlugin, intlayerNodePlugins, markdownPlugin, markdownStringPlugin, reactNodePlugins };
//# sourceMappingURL=plugins.mjs.map