diff --git a/packages/docusaurus/src/client/exports/Interpolate.tsx b/packages/docusaurus/src/client/exports/Interpolate.tsx index 99f0b76e1f..dbb7a14c77 100644 --- a/packages/docusaurus/src/client/exports/Interpolate.tsx +++ b/packages/docusaurus/src/client/exports/Interpolate.tsx @@ -9,7 +9,6 @@ import React, {isValidElement, type ReactNode} from 'react'; import type { InterpolateProps, InterpolateValues, - ExtractInterpolatePlaceholders, } from '@docusaurus/Interpolate'; /* @@ -18,8 +17,6 @@ We don't ship a markdown parser nor a feature-complete i18n library on purpose. More details here: https://github.com/facebook/docusaurus/pull/4295 */ -const ValueFoundMarker = '{}'; // does not care much - // If all the values are plain strings, then interpolate returns a simple string export function interpolate( text: Str, @@ -36,46 +33,26 @@ export function interpolate( text: Str, values?: InterpolateValues, ): ReactNode { - const elements: (Value | string)[] = []; - - const processedText = text.replace( - // eslint-disable-next-line prefer-named-capture-group - /\{(\w+)\}/g, - (match, key: ExtractInterpolatePlaceholders) => { - const value = values?.[key]; - - if (typeof value !== 'undefined') { - const element = isValidElement(value) - ? value - : // For non-React elements: basic primitive->string conversion - String(value); - elements.push(element); - return ValueFoundMarker; + // eslint-disable-next-line prefer-named-capture-group + const segments = text.split(/(\{\w+\})/).map((seg, index) => { + // Odd indices (1, 3, 5...) of the segments are (potentially) interpolatable + if (index % 2 === 1) { + const value = values?.[seg.slice(1, -1) as keyof typeof values]; + if (value !== undefined) { + return value; } - return match; // no match? add warning? - }, - ); - - // No interpolation to be done: just return the text - if (elements.length === 0) { - return text; + // No match: add warning? There's no way to "escape" interpolation though + } + return seg; + }); + if (segments.some((seg) => isValidElement(seg))) { + return segments + .map((seg, index) => + isValidElement(seg) ? React.cloneElement(seg, {key: index}) : seg, + ) + .filter((seg) => seg !== ''); } - // Basic string interpolation: returns interpolated string - if (elements.every((el): el is string => typeof el === 'string')) { - return processedText - .split(ValueFoundMarker) - .reduce( - (str, value, index) => str.concat(value).concat(elements[index] ?? ''), - '', - ); - } - // JSX interpolation: returns ReactNode - return processedText.split(ValueFoundMarker).map((value, index) => ( - - {value} - {elements[index]} - - )); + return segments.join(''); } export default function Interpolate({ @@ -83,9 +60,10 @@ export default function Interpolate({ values, }: InterpolateProps): JSX.Element { if (typeof children !== 'string') { - console.warn('Illegal children', children); throw new Error( - 'The Docusaurus component only accept simple string values', + `The Docusaurus component only accept simple string values. Received: ${ + isValidElement(children) ? 'React element' : typeof children + }`, ); } return <>{interpolate(children, values)}; diff --git a/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx index fdf1033103..ac9d430955 100644 --- a/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx +++ b/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx @@ -130,7 +130,7 @@ describe('', () => { , ), ).toThrowErrorMatchingInlineSnapshot( - `"The Docusaurus component only accept simple string values"`, + `"The Docusaurus component only accept simple string values. Received: React element"`, ); }); }); diff --git a/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap index 9136a8e12d..0674ed24de 100644 --- a/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap +++ b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap @@ -8,62 +8,48 @@ exports[` acceptance test 1`] = ` today , - "? Another {unprovidedValue}!", + "? Another ", + "{unprovidedValue}", + "!", ] `; exports[`interpolate acceptance test 1`] = ` [ - - Hello - Sébastien - , - - how are you - - today - - , - - ? Another {unprovidedValue}! - , + "Hello ", + "Sébastien", + " how are you ", + + today + , + "? Another ", + "{unprovidedValue}", + "!", ] `; exports[`interpolate placeholders with JSX values 1`] = ` [ - - Hello - - Sébastien - - , - - how are you - - today - - , - - ? - , + "Hello ", + + Sébastien + , + " how are you ", + + today + , + "?", ] `; exports[`interpolate placeholders with mixed vales 1`] = ` [ - - Hello - Sébastien - , - - how are you - - today - - , - - ? - , + "Hello ", + "Sébastien", + " how are you ", + + today + , + "?", ] `; diff --git a/website/_dogfooding/_pages tests/error-boundary-tests.tsx b/website/_dogfooding/_pages tests/error-boundary-tests.tsx index 2edbd8ff53..a669c7f2af 100644 --- a/website/_dogfooding/_pages tests/error-boundary-tests.tsx +++ b/website/_dogfooding/_pages tests/error-boundary-tests.tsx @@ -7,6 +7,7 @@ import React from 'react'; import Layout from '@theme/Layout'; +import Interpolate from '@docusaurus/Interpolate'; import ErrorBoundaryTestButton from '@site/src/components/ErrorBoundaryTestButton'; @@ -22,6 +23,9 @@ export default function ErrorBoundaryTests(): JSX.Element { Crash inside layout + FooFoo, bar: BarBar}}> + {'{foo} is {bar}'} +