diff --git a/extensions/shared/element-to-php.js b/extensions/shared/element-to-php.js new file mode 100644 index 0000000000000..ccfdbd70aa494 --- /dev/null +++ b/extensions/shared/element-to-php.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import React from 'react'; + +export * from '@wordpress/element'; +export { Platform, renderToString, RawHTML } from '@wordpress/element'; + +// export * from '@wordpress/element/react'; +// export * from '@wordpress/element/react-platform'; +// export * from '@wordpress/element/utils'; + +// How (and why) does this transform work? +// The idea here is to replace every call to each of `@wordpress/i18n`'s translation functions +// (`__()`, `_n()`, `_x()`, `_nx()`) with their PHP counterpart, wrapped in a `` +// pseudo-tag, and using `echo` to output the var, plus some escaping for sanitization. +// The most puzzling part might be the `echo`: after all, we can't know _how_ the translated +// strings are used in the Javascript source -- e.g. they might be assigned to a variable +// for later usage, rather than rendered directly. +// The answer is that this happens during React's rendering (to static markup, in this case), +// where the entire component logic is essentially flattened to a component string. This means +// that even translated strings that have gone through any intermediate steps will end up in +// that rendered markup -- and thus, we'll have our `` +// statements right where they belong. +// A note on implementation: +// Ideally, our replaced translation function would simply return ``. +// However, React sanitizes strings by escaping chars like `<`. As a consequence, we need to use +// `dangerouslySetInnerHTML` to bypass the escaping. This also requires to be attached as a prop +// to a DOM element. I've chosen `` since this likely has the smallest footprint for +// rendering strings (e.g. shouldn't normally get in the way of styling). + +// We have to stub `sprintf` with the identity function here, since the original +// `sprintf from '@wordpress/i18n'` only accepts strings as its first argument -- but +// our replaced translation functions return a React element (``, see above). +// This means that our rendered markup will contain `sprintf`-style `%(placeholder)s` +// for which we need to add an extra `str_replace()` step. This is done in `components.php`. +// TODO: Provide a wrapper around `@wordpress/i18n`'s `sprintf` that accepts React elements +// as first argument, and remove the `str_replace()` call in `components.php`. +export const createInterpolateElement = x => x; diff --git a/webpack.config.extensions.js b/webpack.config.extensions.js index a88da86d6f2b1..cffc3400d3591 100644 --- a/webpack.config.extensions.js +++ b/webpack.config.extensions.js @@ -144,6 +144,10 @@ module.exports = [ /^@wordpress\/i18n$/, path.join( __dirname, './extensions/shared/i18n-to-php' ) ), + new webpack.NormalModuleReplacementPlugin( + /^@wordpress\/element$/, + path.join( __dirname, './extensions/shared/element-to-php' ) + ), new StaticSiteGeneratorPlugin( { // The following mocks are required to make `@wordpress/` npm imports work with server-side rendering. // Hopefully, most of them can be dropped once https://github.com/WordPress/gutenberg/pull/16227 lands.