
# WPML

WPML 只能按照语言配置文件中的声明翻译自定义区块的属性。Blockstudio 会将每个字段值存储在区块注释中的 `blockstudio.attributes` 下，因此每个区块都需要一个条目，将 WPML 指向其文本字段和附件 ID。`wp bs wpml config` 命令会为每个已注册的区块写入这些条目。

## 为什么区块需要配置

WPML会从当前启用的主题和每个已启用插件的根目录中名为`wpml-config.xml`的文件读取配置。对于单文件的必需插件，文件夹名称就是文件名去掉`.php`后的部分。请参阅[语言配置文件](https://wpml.org/documentation/support/language-configuration-files/)和[使自定义Gutenberg区块支持翻译](https://wpml.org/documentation/support/language-configuration-files/make-custom-gutenberg-blocks-translatable/)。

没有条目的区块会交由WPML自动检测字符串，它会将区块的每个字符串属性提供给翻译。对于包含选择值、文件引用、URL 和CSS类的Blockstudio区块，译者会在真实文本旁看到这些技术值，而翻译后的选择值或类可能会导致区块无法正常工作。

## 生成配置

打印配置，并将其保存到提供这些区块的主题或插件的根目录中：

```bash
wp bs wpml config > wp-content/themes/my-theme/wpml-config.xml
```

也可以让命令写入文件：

```bash
wp bs wpml config --file=wp-content/themes/my-theme/wpml-config.xml
```

一个包含文本字段、图像、链接和重复器的区块：

```json title="block.json"
{
  "name": "my-theme/cards",
  "title": "Cards",
  "blockstudio": {
    "attributes": [
      { "id": "heading", "type": "text", "label": "Heading" },
      { "id": "gallery", "type": "files", "label": "Gallery", "multiple": true },
      {
        "id": "style",
        "type": "select",
        "label": "Style",
        "options": [
          { "value": "light", "label": "Light" },
          { "value": "dark", "label": "Dark" }
        ]
      },
      {
        "id": "cards",
        "type": "repeater",
        "label": "Cards",
        "attributes": [
          { "id": "title", "type": "text", "label": "Title" },
          { "id": "image", "type": "files", "label": "Image" },
          { "id": "link", "type": "link", "label": "Button" }
        ]
      }
    ]
  }
}
```

会变成：

```xml title="wpml-config.xml"
<wpml-config>
	<gutenberg-blocks>
		<!-- blockstudio:start -->
		<!-- Generated by wp bs wpml config. Run it again after changing block fields; edits between the markers are replaced. -->
		<gutenberg-block type="my-theme/cards" translate="1" label="Cards">
			<key name="blockstudio">
				<key name="attributes">
					<key name="heading" label="Heading" />
					<key name="gallery">
						<key name="*" type="post-ids" sub-type="attachment" />
					</key>
					<key name="cards">
						<key name="*">
							<key name="title" label="Cards: Title" />
							<key name="image" type="post-ids" sub-type="attachment" />
							<key name="link">
								<key name="title" label="Cards: Button: Link text" />
								<key name="url" type="link" label="Cards: Button: URL" />
							</key>
						</key>
					</key>
				</key>
			</key>
		</gutenberg-block>
		<!-- blockstudio:end -->
	</gutenberg-blocks>
</wpml-config>
```

`style`值未声明，因此WPML会原样复制到翻译中。`gallery`和`image` ID 也会被复制，渲染区块时，WPML会将每个 ID 替换为当前页面语言对应的附件，因此翻译后的图像可以拥有自己的替代文本。请参阅[使自定义Gutenberg区块支持翻译](https://wpml.org/documentation/support/language-configuration-files/make-custom-gutenberg-blocks-translatable/)中的“自动转换区块中的 ID”。如果附件没有对应的翻译，WPML会保留原始 ID。

## 声明内容

命令会读取已注册的区块，此时`custom/`字段和区块字段已展开，并已应用覆盖项和属性筛选器。[扩展](https://blockstudio.dev/zh/docs/extensions/)添加到Blockstudio区块的字段也会包含在内。扩展添加到核心区块或其他插件区块的字段则不会包含，因为这些区块从WPML或各自所属的插件中获取配置。

| 字段 | 声明 |
| --- | --- |
| `text`、`textarea`、`richtext`、`wysiwyg` | 可翻译文本。 |
| `link` | 将`title`作为文本，将`url`与`type="link"`一起使用，因此WPML会在有可用翻译时将链接替换为对应译文。 |
| `files` | 带有`type="post-ids"`和`sub-type="attachment"`的已存附件 ID。使用`multiple`时，为列表指定名为`*`的层级；这也匹配字段选择单个文件时存储的单个 ID。 |
| `repeater` | 指定名为`*`的层级以匹配每一行，并包含行字段的声明。嵌套重复器的工作方式相同。 |
| 带有`id`的`group` | 组内字段，以组 ID 作为键前缀（组`cta`中的字段`text`使用`cta_text`）。 |
| 没有`id`的`group`，`tabs` | 这些字段没有自己的层级。 |
| 注册时将`translate`设为`true`的自定义字段类型 | 可翻译文本，类似`text`字段。 |
| 其他所有内置类型 | 不声明。WPML会原样复制该值。 |

没有声明文本字段的区块会获得`translate="0"`。随后WPML会复制其中的所有值，不会提供任何内容供翻译。其`files`字段仍会在条目中声明，因为WPML无论`translate`如何设置，都会转换附件 ID。内部区块仍按常规方式翻译。

未声明的字段：

- 带有`hidden`的字段，因为编辑者不会写入这些值。
- 带有`"translate": false`的字段，见下文。
- [自定义字段类型](https://blockstudio.dev/zh/docs/blocks/attributes/custom-field-types/)中未将`translate`设为`true`的字段。命令会为每个此类字段打印警告，因为只有字段类型才知道其值是否包含文本。对字段显式设置`"translate": false`也会抑制该字段的警告。见下文。
- 区块在原生`attributes`中声明、而非作为Blockstudio字段声明的属性。
- 字段类型不同的存储键，见下文。

每个文本字段都会被声明，包括保存技术值的字段，例如CSS类，除非它设置了`"translate": false`。

### 排除字段

文本字段可能保存必须在所有语言中保持一致的值，例如账号名称、电子邮件地址、锚点或CSS类。WPML会像处理其他文本一样将其提供给翻译，而翻译后的值可能会导致区块无法正常工作。将`translate`设为`false`，即可从配置中排除该字段，让WPML将其值原样复制到翻译中：

```json title="block.json"
{
  "id": "account",
  "type": "text",
  "label": "Account handle",
  "translate": false
}
```

对于`group`、`tabs`或`repeater`，`"translate": false`适用于其中的每个字段，包括嵌套容器。子项无法通过`"translate": true`覆盖已选择排除的容器。对于`files`字段，`false`也会排除附件 ID，因此翻译会保留原始附件。

省略`translate`或将其设为`true`会保留现有行为：由字段类型决定。它不会让隐藏字段或`select`等值类型变得可翻译。此选项适用于区块字段和扩展字段，不会隐藏或禁用其编辑器控件，也不会更改其存储值。更改设置后，请再次运行`wp bs wpml config`，并使用生成的配置更新你的`wpml-config.xml`。

每个键的`label`是字段标签，前面带有具有 ID 的外层重复器和组的标签。WPML会在翻译编辑器中将其显示为字段名称。`link`字段的两个键会在标签中添加`Link text`和`URL`。这些后缀会翻译成站点语言，因此请使用译者工作的语言区域设置运行命令。

### 自定义字段类型

存储纯文本字符串的字段类型会在注册时声明这一点。在定义中将`translate`设为`true`，并确保`attribute`为`string`：

```php title="functions.php"
bs_register_field_type('acme/headline', [
  'attribute' => 'string',
  'translate' => true,
]);
```

该命令随后会将此类型的每个字段都声明为可翻译文本。字段仍可设置 `"translate": false`，将自身排除在外。未启用此选项时，此类型的字段会像以前一样被排除并报告。

### 更改标签

字段标签是为区块编辑器编写的，可能带有“图像（可选）”之类的提示，而这些提示在翻译编辑器中毫无意义。`blockstudio/wpml/config/label` 过滤器会在标签写入之前接收每个标签，并附带键的上下文：

- `block`：区块名称。
- `path`：`blockstudio.attributes` 下方字段的存储路径，每个重复器行还会有一个 `*` 层级，例如 `cards/*/link`。
- `field`：字段定义。
- `parents`：外层重复器和带 ID 的分组标签。
- `part`：文本字段的 `text`，链接字段两个键的 `title` 或 `url`。

```php title="functions.php"
add_filter('blockstudio/wpml/config/label', function ($label, $context) {
  return str_replace(' (optional)', '', $label);
}, 10, 2);
```

返回空字符串即可排除某个键的标签，这样 WPML 就会显示区块标签。返回非字符串值则会保留原标签。更改过滤器后，请再次运行该命令。

### 存储键相同的字段

两个字段最终可能使用相同的存储键，例如区块字段和扩展字段具有相同的 `id`，或者 ID 为 `cta` 的分组中有字段 `text`，同时还有 ID 为 `cta_text` 的字段。只有在明确该键存储的内容时，命令才会声明此类键：

- 在区块字段中，重复器行之外，Blockstudio 会为每个存储键注册一个属性，因此后面的字段会替换前面的字段，无论它们的类型如何。区块扩展中的字段也遵循相同规则：后面的字段，或后注册的扩展中的字段，会替换前面的字段。
- 具有相同键的区块字段和扩展字段都会生效。区块模板接收按区块字段定义的值，而扩展则使用自己的字段应用该值。只有当两个字段需要相同声明时，该键才只声明一次：两个字段都是文本字段（`text`、`textarea`、`richtext` 或 `wysiwyg`），两个都是链接，两个都是 `files` 字段（任一字段带有 `multiple` 时，该键会采用 `*` 层级），或者两个字段都不声明，例如一个 `select` 和一个 `number`。两个重复器会按行逐字段比较。标签取自区块字段。
- 同一重复器各行中具有相同 `id` 的两个字段也都会生效，并按相同方式比较。

类型不同时，例如区块中的 `text` 字段与扩展中的 `select`，存储值可能是文本，也可能是选择项，具体取决于写入该值的控件。命令不会声明该键，并会打印一条警告：

```bash
Warning: Not declared, the fields that store this key have different types: my-theme/cards: tone (text in the block, select in an extension)
```

重命名其中一个字段，或将两个字段设为相同类型，即可声明该键。

## 保持配置为最新状态

添加、移除或重命名字段后，请再次运行该命令，并将更新后的文件随主题或插件一同发布。重新生成在 7.7.5 之前创建的配置，以纳入附件 ID 声明。对于原始内容更新后才出现的新字段，已有内容即可进行翻译，具体如上方 WPML 指南所述。

使用 `--file` 时，命令会创建缺失的文件。对于已有文件，命令只会替换 `<!-- blockstudio:start -->` 与 `<!-- blockstudio:end -->` 之间的文本，因此其他区块的章节和条目会保持原样：

```xml title="wpml-config.xml"
<wpml-config>
	<custom-types>
		<custom-type translate="1">book</custom-type>
	</custom-types>
	<gutenberg-blocks>
		<gutenberg-block type="my-plugin/map" translate="1">
			<key name="caption" />
		</gutenberg-block>
		<!-- blockstudio:start -->
		<!-- blockstudio:end -->
	</gutenberg-blocks>
</wpml-config>
```

要使用尚无标记的现有文件，请在其 `gutenberg-blocks` 元素内添加这两行标记，或者在文件尚无 `gutenberg-blocks` 元素时添加一个带有标记的元素。如果缺少标记，或者结果不是有效配置，命令会保持文件不变并报错退出。

手动编写的 Blockstudio 区块条目（例如用于隐藏技术值的 `translate="0"` 条目）在生成章节涵盖该区块后便不再需要。命令会对每个重复声明的区块发出警告，因此可以移除这些条目。

## 翻译任务中的图像文本

WPML 会将页面所用图像的文本加入该页面的翻译任务：标题、替代文本、说明文字和描述。任务完成后，WPML 会将译文写入目标语言的附件；如果目标语言尚无对应附件，则会创建一个。请参阅[如何翻译媒体](https://wpml.org/documentation/translating-your-contents/media/)。
WPML 会查找内容中的 `<img>` 标签、图库、特色图像以及它支持的区块中的图像。Blockstudio 区块会在区块注释中存储附件 ID，因此 WPML 找不到其中的图像。

启用 WPML 后，Blockstudio 会将页面区块字段中存储的附件加入已找到的图像 WPML：

- `files` 字段（包括隐藏字段），以及带有 `media` 的 `attributes` 字段；区块编辑器会将这些相同字段作为媒体加载。
- 分组、选项卡、重复器行和内部区块中的字段。
- [扩展](https://blockstudio.dev/zh/docs/extensions/)的字段，包括核心区块和其他插件的区块中的字段，因为这些值存储在被扩展区块的属性中。

非附件 ID 的值会被排除。无需进行任何配置，也不会涉及 `wpml-config.xml`。未启用 WPML 时，不会扫描翻译任务，也不会加载媒体集成类。

这与附件 ID 转换彼此独立：生成的 XML 会告知 WPML 应渲染哪个译文附件，而此集成会告知它应将哪些附件文本加入翻译任务。字符串引用和非附件文章的 ID 不会加入媒体任务。在 Blockstudio 区块中，已保存的嵌套字段值优先于顶层值，渲染时也是如此。

只有启用了 WPML > Settings > Media Translation 下的某个图像文本选项时，这些文本才会成为任务的一部分：“自动检测翻译图像文本的最佳选项”或“翻译内容时翻译媒体库文本”。核心区块中的图像也是如此。

Blockstudio 通过筛选器报告图像
`wpml_ids_of_media_used_in_post`，该筛选器 WPML 会应用于
`PostWithMediaFiles` 中帖子的媒体。若要将某张图像排除在任务之外，请在优先级高于 10 的同一筛选器回调中移除其 ID：

```php
add_filter(
	'wpml_ids_of_media_used_in_post',
	function ( $ids ) {
		return array_values( array_diff( $ids, array( 42 ) ) );
	},
	20
);
```
