UNPKG

react-intlayer

Version:

Easily internationalize i18n your React applications with type-safe multilingual content management.

234 lines (232 loc) 9.82 kB
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