如果我们需要直接在 WordPress 编辑器中向用户显示文档,最好的方法是什么?
由于块编辑器基于 React,我们可能会倾向于使用 React 组件和 HTML 代码来编写文档。这正是我在我之前的文章中采用的方法,该文章演示了一种 在模态窗口中显示文档 的方法。

但此解决方案并非完美无缺,因为通过 React 组件和 HTML 代码添加文档可能会变得非常冗长,更不用说难以维护了。例如,上面图像中的模态窗口包含一个 React 组件中的文档,如下所示
const CacheControlDescription = () => {
return (
<p>The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or <code>no-store</code> if the max-age is 0</p>
)
}
使用 Markdown 代替 HTML 可以使工作更轻松。例如,上面的文档可以从 React 组件中移出,并放入一个 Markdown 文件中,例如 /docs/cache-control.md
The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or `no-store` if the max-age is 0
与纯 HTML 相比,使用 Markdown 的优缺点是什么?
优点 | 缺点 |
---|---|
✅ 编写 Markdown 比 HTML 更轻松快捷 | ❌ 文档不能包含 React 组件 |
✅ 文档可以与块的源代码分开保存(甚至在单独的仓库中) | ❌ 我们无法使用 __ 函数(它通过 .po 文件帮助本地化内容)来输出文本 |
✅ 校对员可以修改文档,而不用担心破坏代码 | |
✅ 文档代码不会添加到块的 JavaScript 资产中,从而可以更快地加载 |
关于缺点,至少对于简单的文档而言,无法使用 React 组件可能不是问题。但是,缺少本地化是一个主要问题。通过 JavaScript __
函数添加到 React 组件中的文本可以 提取并使用来自 POT 文件的翻译进行替换。Markdown 中的内容无法访问此功能。
支持文档本地化是强制性的,因此我们需要弥补这一点。在本文中,我们将追求两个目标
- 使用 Markdown 编写文档(由 WordPress 编辑器的块显示)
- 将文档翻译成用户的语言
让我们开始吧!
加载 Markdown 内容
创建了一个 Markdown 文件 /docs/cache-control.md
后,我们可以导入其内容(已渲染为 HTML)并将其注入到 React 组件中,如下所示
import CacheControlDocumentation from '../docs/cache-control.md';
const CacheControlDescription = () => {
return (
<div
dangerouslySetInnerHTML={ { __html: CacheControlDocumentation } }
/>
);
}
此解决方案依赖于 webpack,它是 WordPress 编辑器核心中的模块打包器。
请注意,WordPress 编辑器目前使用 webpack 4.42。但是,webpack 网站上显示的前端文档对应于版本 5(它 仍处于测试阶段)。版本 4 的文档位于 子站点。
内容通过 webpack 的 加载器 从 Markdown 转换为 HTML,为此,块需要 自定义其 webpack 配置,添加使用 markdown-loader 和 html-loader 的规则。
为此,在块的根目录下添加一个文件 webpack.config.js
,其中包含以下代码
// This is the default webpack configuration from Gutenberg
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
// Customize adding the required rules for the block
module.exports = {
...defaultConfig,
module: {
...defaultConfig.module,
rules: [
...defaultConfig.module.rules,
{
test: /\.md$/,
use: [
{
loader: "html-loader"
},
{
loader: "markdown-loader"
}
]
}
],
},
};
并安装相应的包
npm install --save-dev markdown-loader html-loader
趁此机会,让我们应用一个小改进。docs 文件夹可以包含项目中任何位置的组件文档。为了避免必须从每个组件计算到该文件夹的相对路径,我们可以在 webpack.config.js
中添加一个 别名 @docs
,以解析到文件夹 /docs
const path = require( 'path' );
config.resolve.alias[ '@docs' ] = path.resolve( process.cwd(), 'docs/' )
现在,导入操作得到了简化
import CacheControlDocumentation from '@docs/cache-control.md';
就是这样!现在我们可以将来自外部 Markdown 文件的文档注入到 React 组件中。
将文档翻译成用户的语言
我们无法通过 .po
文件为 Markdown 内容翻译字符串,但有一种替代方法:为不同的语言生成不同的 Markdown 文件。然后,我们可以为每种语言创建一个文件,而不是只有一个文件(/docs/cache-control.md
),每个文件都存储在其对应的语言代码下
/docs/en/cache-control.md
/docs/fr/cache-control.md
/docs/zh/cache-control.md
- 等等。
我们还可以支持语言和地区的翻译,以便美式英语和英式英语可以有不同的版本,并在未提供地区的翻译时默认为仅语言版本(例如,"en_CA"
由 "en"
处理)
/docs/en_US/cache-control.md
/docs/en_GB/cache-control.md
/docs/en/cache-control.md
为了简化问题,我将只解释如何支持不同的语言,而不支持地区。但是代码基本相同。
本文中演示的代码也可以在 我制作的 WordPress 插件的源代码 中看到。
将用户的语言提供给块
WordPress 中用户的语言可以通过 get_locale()
获取。由于语言环境包括语言代码和地区(例如 "en_US"
),因此我们对其进行解析以单独提取语言代码
function get_locale_language(): string
{
$localeParts = explode( '_', get_locale() );
return $localeParts[0];
}
通过 wp_localize_script()
,我们将语言代码提供给块,作为全局变量(在本例中为 graphqlApiCacheControl
)下的 userLang
属性
// The block was registered as $blockScriptRegistrationName
wp_localize_script(
$blockScriptRegistrationName,
'graphqlApiCacheControl',
[
'userLang' => get_locale_language(),
]
);
现在,用户的语言代码在块中可用
const lang = window.graphqlApiCacheControl.userLang;
动态导入
我们只能在运行时知道用户的语言。但是,import
语句是静态的,而不是动态的。因此,我们无法执行以下操作
// `lang` contains the user's language
import CacheControlDocumentation from '@docs/${ lang }/cache-control.md';
也就是说,webpack 允许我们通过 import
函数动态加载模块,该函数默认将请求的模块拆分为一个单独的块(即它不包含在主编译的 build/index.js
文件中),以便延迟加载。
此行为适用于在模态窗口中显示文档,该窗口由用户操作触发,而不是预先加载。import
必须接收有关模块所在位置的一些信息,因此此代码有效
import( `@docs/${ lang }/cache-control.md` ).then( module => {
// ...
});
但这段看似相似的代码无效
const dynamicModule = `@docs/${ lang }/cache-control.md`
import( dynamicModule ).then( module => {
// ...
});
文件中的内容可在导入对象的 default
键下访问
const cacheControlContent = import( `@docs/${ lang }/cache-control.md` ).then( obj => obj.default )
我们可以将此逻辑概括为一个名为 getMarkdownContent
的函数,并传递 Markdown 文件的名称以及语言
const getMarkdownContent = ( fileName, lang ) => {
return import( `@docs/${ lang }/${ fileName }.md` )
.then( obj => obj.default )
}
管理块
为了保持块资产的组织性,让我们将文档块分组到 /docs
子文件夹中(将在 build/
文件夹内创建),并为其提供描述性文件名。
然后,在三种语言(英语、法语和中文)中包含两个文档(cache-control.md
和 cache-purging.md
),将生成以下块
build/docs/en-cache-control-md.js
build/docs/fr-cache-control-md.js
build/docs/zh-cache-control-md.js
build/docs/en-cache-purging-md.js
build/docs/fr-cache-purging-md.js
build/docs/zh-cache-purging-md.js
这是通过在 import
参数之前使用 魔法注释 /* webpackChunkName: "docs/[request]" */
来实现的
const getMarkdownContent = ( fileName, lang ) => {
return import( /* webpackChunkName: "docs/[request]" */ `@docs/${ lang }/${ fileName }.md` )
.then(obj => obj.default)
}
设置块的公共路径
webpack 知道在哪里获取块,这要归功于 publicPath
配置选项。如果未提供,则使用 WordPress 编辑器中的当前 URL /wp-admin/
,因为块位于其他位置,因此会产生 404 错误。对于我的块,它们位于 /wp-content/plugins/graphql-api/blocks/cache-control/build/
下。
如果块用于我们自己的用途,我们可以在 webpack.config.js
中硬编码 publicPath
,或者通过 ASSET_PATH
环境变量提供它。否则,我们需要在运行时将公共路径传递给块。为此,我们计算块的 build/
文件夹的 URL
$blockPublicPath = plugin_dir_url( __FILE__ ) . '/blocks/cache-control/build/';
然后,我们通过本地化块将其注入到 JavaScript 端
// The block was registered as $blockScriptRegistrationName
wp_localize_script(
$blockScriptRegistrationName,
'graphqlApiCacheControl',
[
//...
'publicPath' => $blockPublicPath,
]
);
然后,我们将公共路径提供给 __webpack_public_path__
JavaScript 变量
__webpack_public_path__ = window.graphqlApiCacheControl.publicPath;
回退到默认语言
如果用户语言没有翻译,会发生什么?在这种情况下,调用getMarkdownContent
将会抛出一个错误。
例如,当语言设置为德语时,浏览器控制台将显示以下内容
Uncaught (in promise) Error: Cannot find module './de/cache-control.md'
解决方法是捕获错误,然后返回默认语言的内容,该内容始终由代码块满足
const getMarkdownContentOrUseDefault = ( fileName, defaultLang, lang ) => {
return getMarkdownContent( fileName, lang )
.catch( err => getMarkdownContent( fileName, defaultLang ) )
}
请注意,当翻译不完整时,作为 React 组件内部的 HTML 和作为外部 Markdown 文件的代码文档的行为有所不同。在第一种情况下,如果一个字符串已被翻译,但另一个字符串尚未翻译(在.po
文件中),那么 React 组件最终将显示混合语言。在第二种情况下,要么文档完全翻译,要么不翻译。
将文档设置到模态框中
现在,我们可以从 Markdown 文件中检索文档。让我们看看如何在模态框中显示它。
我们首先包装 Gutenberg 的Modal
组件,以将内容注入为 HTML
import { Modal } from '@wordpress/components';
const ContentModal = ( props ) => {
const { content } = props;
return (
<Modal
{ ...props }
>
<div
dangerouslySetInnerHTML={ { __html: content } }
/>
</Modal>
);
};
然后我们从 Markdown 文件中检索内容,并使用名为page
的状态钩子将其作为 prop 传递到模态框中。动态加载内容是一个异步操作,因此我们还必须使用效果钩子在组件中执行副作用。我们需要仅读取 Markdown 文件中的内容一次,因此我们将空数组作为第二个参数传递给useEffect
(否则钩子将持续触发)
import { useState, useEffect } from '@wordpress/element';
const CacheControlContentModal = ( props ) => {
const fileName = 'cache-control'
const lang = window.graphqlApiCacheControl.userLang
const defaultLang = 'en'
const [ page, setPage ] = useState( [] );
useEffect(() => {
getMarkdownContentOrUseDefault( fileName, defaultLang, lang ).then( value => {
setPage( value )
});
}, [] );
return (
<ContentModal
{ ...props }
content={ page }
/>
);
};
让我们看看它是如何工作的。请注意,包含文档的块是如何延迟加载的(即,当编辑块时触发)

搞定了 🎉
编写文档可能不是你最喜欢的事情,但使编写和维护文档变得容易可以帮助你减轻痛苦。
使用 Markdown 而不是纯 HTML 当然是一种方法。我希望我们刚刚介绍的方法不仅可以改善你的工作流程,还可以为你的 WordPress 用户提供一个不错的增强功能。
这正是我一直在寻找的,全面、易于理解且完美无缺。非常感谢你!解决了我的问题。
很高兴听到这个消息!
解释得很好,非常感谢
谢谢! :)
以这种方式进行国际化无疑是向前迈进的一步,如果插件自己提供所有翻译,我可以看到它可以作为 WordPress.org 代码库之外的插件的合适解决方法。对于通过 WordPress.org 分发的插件,由于不使用 __() 和 PO 文件,您将无法利用大量帮助翻译 translate.wordpress.org 的贡献者,也无法根据用户站点语言自动分发相应的 PO 文件。