WPML
WPML translates the attributes of a custom block only as far as a language
configuration file declares them. Blockstudio stores every field value in the
block comment under blockstudio.attributes, so each block needs an entry that
points WPML at its text fields and its attachment IDs. The wp bs wpml config
command writes these entries for every registered block.
Why blocks need a configuration
WPML reads the configuration from a file named wpml-config.xml in the root
folder of the active theme and of each active plugin. For a must-use plugin
that is a single file, the folder has the name of the file without .php. See
Language Configuration Files
and Make Custom Gutenberg Blocks Translatable.
A block without an entry is left to WPML’s automatic string detection, which offers every string attribute of the block for translation. For a Blockstudio block that includes select values, file references, URLs, and CSS classes. Translators see these technical values next to the real text, and a translated select value or class can break the block.
Generate the configuration
Print the configuration and save it in the root folder of the theme or plugin that ships the blocks:
wp bs wpml config > wp-content/themes/my-theme/wpml-config.xml
Or let the command write the file:
wp bs wpml config --file=wp-content/themes/my-theme/wpml-config.xml
A block with a text field, an image, a link, and a repeater:
{
"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" }
]
}
]
}
}
becomes:
<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>
The style value is not declared, so WPML copies it into the translation
unchanged. The gallery and image IDs are copied as well, and WPML replaces
each one with the attachment of the page language when the block is rendered,
so a translated image can have its own alt text. See “Automatically Converting
IDs in Blocks” in
Make Custom Gutenberg Blocks Translatable.
When an attachment has no translation, WPML keeps the original ID.
What is declared
The command reads the registered blocks, after custom/ fields and block fields
are expanded and overrides and attribute filters are applied. Fields that
extensions add to a Blockstudio block are included. Fields
that extensions add to core blocks or to blocks of other plugins are not, because
those blocks get their configuration from WPML or from their own plugin.
| Field | Declaration |
|---|---|
text, textarea, richtext, wysiwyg |
Translatable text. |
link |
title as text and url with type="link", so WPML replaces a link with its translation when one is available. |
files |
The stored attachment ID with type="post-ids" and sub-type="attachment". With multiple, a level named * for the list, which also matches the single ID that the field stores when one file is selected. |
repeater |
A level named * that matches every row, with the declarations of the row fields. Nested repeaters work the same way. |
group with an id |
The fields of the group, with the group ID as key prefix (cta_text for the field text in the group cta). |
group without an id, tabs |
The fields, without a level of their own. |
Custom field type registered with translate set to true |
Translatable text, like a text field. |
| Every other built-in type | Not declared. WPML copies the value unchanged. |
A block without a declared text field gets translate="0". WPML then copies
all of its values and offers nothing from it for translation. Its files
fields are still declared inside the entry, because WPML converts attachment
IDs whatever translate says. Inner blocks are translated as usual.
Not declared:
- Fields with
hidden, because their values are not written by editors. - Fields with
"translate": false, see below. - Fields of custom field types
that are not registered with
translateset totrue. The command prints a warning for each, because only the field type knows whether its value holds text. An explicit"translate": falseon a field also suppresses this warning for that field. See below. - Attributes that a block declares in its native
attributesinstead of as Blockstudio fields. - Storage keys whose fields have different types, see below.
Every text field is declared, including one that holds a technical value such
as a CSS class, unless it sets "translate": false.
Leave a field out
A text field can hold a value that must be the same in every language: an
account handle, an email address, an anchor, a CSS class. WPML offers it for
translation like any other text, and a translated value can break the block.
Set translate to false to leave the field out of the configuration, so
WPML copies its value into the translation unchanged:
{
"id": "account",
"type": "text",
"label": "Account handle",
"translate": false
}
On a group, tabs, or repeater, "translate": false applies to every
field inside, including nested containers. A child cannot override an opted-out
container with "translate": true. On a files field, false leaves out the
attachment IDs as well, so the translation keeps the original attachments.
Omitting translate or setting it to true preserves the existing behavior:
the field type decides. It does not make hidden fields or value types such as
select translatable. The option applies to block and extension fields, does
not hide or disable their editor controls, and does not change their stored
values. Run wp bs wpml config again after changing the setting and update
your wpml-config.xml with the generated configuration.
The label of each key is the field label, preceded by the labels of the
enclosing repeaters and groups with an ID. WPML shows it as the field name in
the translation editor. The two keys of a link field add Link text and
URL to the label. These suffixes are translated into the site language, so
run the command with the locale your translators work in.
Custom field types
A field type that stores a plain string of text says so when it is registered.
Set translate to true in the definition, which requires attribute to be
string:
bs_register_field_type('acme/headline', [
'attribute' => 'string',
'translate' => true,
]);
The command then declares every field of that type as translatable text. A
field can still set "translate": false to leave itself out. Without the
option, the fields of the type are left out and reported, as before.
Change the labels
Field labels are written for the block editor and can carry hints such as
“Image (optional)” that mean nothing in the translation editor. The
blockstudio/wpml/config/label filter receives each label before it is
written, with the context of the key:
block: the block name.path: the storage path of the field belowblockstudio.attributes, with a level*for the rows of each repeater, for examplecards/*/link.field: the field definition.parents: the labels of the enclosing repeaters and groups with an ID.part:textfor a text field,titleorurlfor the two keys of a link field.
add_filter('blockstudio/wpml/config/label', function ($label, $context) {
return str_replace(' (optional)', '', $label);
}, 10, 2);
Return an empty string to leave out the label of a key, so WPML shows the label of the block instead. A return value that is not a string keeps the original label. Run the command again after changing the filter.
Fields with the same storage key
Two fields can end up with the same storage key, for example a block field and
an extension field with the same id, or the field text in a group with the
ID cta and a field with the ID cta_text. The command declares such a key
only when it is clear what is stored there:
- Within the fields of a block, outside repeater rows, Blockstudio registers one attribute per storage key, so a later field replaces an earlier one, whatever its type. The fields of the extensions of a block work the same way: a later field, or the field of an extension registered later, replaces an earlier one.
- A block field and an extension field with the same key both stay in effect.
The block template receives the value as the block’s field defines it, while
the extension is applied with its own field. The key is declared once when
both fields need the same
declaration: both are text fields (
text,textarea,richtext, orwysiwyg), both are links, both arefilesfields (withmultipleon either, the key gets the*level), or neither is declared, such as aselectand anumber. Two repeaters are compared field by field in their rows. The label comes from the block’s field. - Two fields with the same
idin the rows of one repeater both stay in effect as well, and are compared the same way.
When the types differ, for example a text field in the block and a select
in an extension, the stored value is text or a select option, depending on the
control that wrote it. The command does not declare the key and prints a
warning:
Warning: Not declared, the fields that store this key have different types: my-theme/cards: tone (text in the block, select in an extension)
Rename one of the fields, or give both the same type, to have the key declared.
Keep the configuration up to date
Run the command again after adding, removing, or renaming fields, and ship the updated file with the theme or plugin. Regenerate configurations made before 7.7.5 to include attachment-ID declarations. Content that existed before shows new fields for translation once its original is updated, as described in the WPML guide above.
With --file, the command creates a missing file. In an existing file, it
replaces only the text between <!-- blockstudio:start --> and
<!-- blockstudio:end -->, so other sections and entries for other blocks stay
as they are:
<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>
To use an existing file that has no markers yet, add the two marker lines once
inside its gutenberg-blocks element, or add a gutenberg-blocks element with
the markers if the file has none. Without the markers, or when the result would
not be a valid configuration, the command leaves the file unchanged and exits
with an error.
Entries for Blockstudio blocks that were written by hand, such as
translate="0" entries that hide technical values, are no longer needed once
the generated section covers the block. The command warns about every block
that is declared more than once, so these entries can be removed.
Image texts in translation jobs
WPML puts the texts of the images that a page uses into the translation job of
the page: title, alt text, caption, and description. When the job is
completed, WPML writes the translated texts to the attachment of the target
language and creates that attachment if there is none yet. See
How to Translate Media.
WPML finds the images in <img> tags in the content, in galleries, in the
featured image, and in the blocks it knows. A Blockstudio block stores
attachment IDs in the block comment, so WPML finds none of them.
When WPML is active, Blockstudio adds the attachments that its fields store in the blocks of a page to the images WPML found:
filesfields, including hidden ones, andattributesfields withmedia, the same fields the block editor loads as media.- Fields in groups, tabs, repeater rows, and inner blocks.
- Fields of extensions, also on core blocks and blocks of other plugins, because their values are stored in the attributes of the extended block.
Values that are not the ID of an attachment are left out. Nothing needs to be
configured, and the wpml-config.xml is not involved. Without WPML, nothing is
scanned for translation jobs and the media integration class is not loaded.
This is separate from attachment-ID conversion: the generated XML tells WPML which translated attachment to render, while this integration tells it which attachment texts to include in a translation job. String references and IDs of non-attachment posts are not added to media jobs. Saved nested field values take precedence over top-level values on Blockstudio blocks, as on render.
The texts are part of the job only when one of the image text options under WPML > Settings > Media Translation is enabled, “Automatically detect best options for translating image texts” or “Translate Media Library texts when translating content”, as for images in core blocks.
Blockstudio reports the images through the filter
wpml_ids_of_media_used_in_post, which WPML applies to the media of a post in
PostWithMediaFiles. To leave an image out of the jobs, remove its ID in a
callback on the same filter with a priority above 10:
add_filter(
'wpml_ids_of_media_used_in_post',
function ( $ids ) {
return array_values( array_diff( $ids, array( 42 ) ) );
},
20
);