Skip to content

Commit

Permalink
Blocks: Introduce registerBlockTypeFromMetadata API (#30293)
Browse files Browse the repository at this point in the history
* Blocks: Introduce registerBlockTypeFromMetadata API

* Add i18n schema for block.json

* Docs: Update documentation for metadata and internationalization

* Test: Add test for registerBlockTypeFromMetadata

* Blocks: Translate metadata loaded from block.json

* Make fields translatable

* Docs: Add changelog entry

* Add i18n support for variations read from block metadata
  • Loading branch information
gziolo authored Apr 27, 2021
1 parent f1dd68c commit 9075808
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 14 deletions.
1 change: 1 addition & 0 deletions bin/plugin/commands/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function calculateVersionBumpFromChangelog(
if (
lineNormalized.startsWith( '### deprecation' ) ||
lineNormalized.startsWith( '### enhancement' ) ||
lineNormalized.startsWith( '### new api' ) ||
lineNormalized.startsWith( '### new feature' )
) {
versionBump = 'minor';
Expand Down
27 changes: 24 additions & 3 deletions docs/reference-guides/block-api/block-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ return array(

## Internationalization

WordPress string discovery automatically will translate fields marked in the documentation as translatable using the `textdomain` property when specified in the `block.json` file. In that case, localized properties will be automatically wrapped in `_x` function calls on the backend of WordPress when executing `register_block_type_from_metadata`. These translations are added as an inline script to the `wp-block-library` script handle in WordPress core or to the plugin's script handle.
WordPress string discovery system can automatically translate fields marked in this document as translatable. First, you need to set the `textdomain` property in the `block.json` file that provides block metadata.

**Example:**

Expand All @@ -451,19 +451,40 @@ WordPress string discovery automatically will translate fields marked in the doc
}
```

The way `register_block_type_from_metadata` processes translatable values is roughly equivalent to:
### PHP

In PHP, localized properties will be automatically wrapped in `_x` function calls on the backend of WordPress when executing `register_block_type_from_metadata`. These translations get added as an inline script to the plugin's script handle or to the `wp-block-library` script handle in WordPress core.

The way `register_block_type_from_metadata` processes translatable values is roughly equivalent to the following code snippet:

```php
<?php
$metadata = array(
'title' => _x( 'My block', 'block title', 'my-plugin' ),
'description' => _x( 'My block is fantastic!', 'block description', 'my-plugin' ),
'keywords' => array( _x( 'fantastic', 'block keywords', 'my-plugin' ) ),
'keywords' => array( _x( 'fantastic', 'block keyword', 'my-plugin' ) ),
);
```

Implementation follows the existing [get_plugin_data](https://codex.wordpress.org/Function_Reference/get_plugin_data) function which parses the plugin contents to retrieve the plugin’s metadata, and it applies translations dynamically.

### JavaScript

In JavaScript, you need to use `registerBlockTypeFromMetadata` method from `@wordpress/blocks` package to process loaded block metadata. All localized properties get automatically wrapped in `_x` (from `@wordpress/i18n` package) function calls similar to how it works in PHP.

**Example:**

```js
import { registerBlockTypeFromMetadata } from '@wordpress/blocks';
import Edit from './edit';
import metadata from './block.json';

registerBlockTypeFromMetadata( metadata, {
edit: Edit,
// ...other client-side settings
} );
```

## Backward Compatibility

The existing registration mechanism (both server side and frontend) will continue to work, it will serve as low-level implementation detail for the `block.json` based registration.
Expand Down
8 changes: 2 additions & 6 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import '@wordpress/core-data';
import '@wordpress/block-editor';
import {
registerBlockType,
registerBlockTypeFromMetadata,
setDefaultBlockName,
setFreeformContentHandlerName,
setUnregisteredTypeHandlerName,
setGroupingBlockName,
unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase
} from '@wordpress/blocks';

/**
Expand Down Expand Up @@ -106,10 +105,7 @@ const registerBlock = ( block ) => {
return;
}
const { metadata, settings, name } = block;
if ( metadata ) {
unstable__bootstrapServerSideBlockDefinitions( { [ name ]: metadata } );
}
registerBlockType( name, settings );
registerBlockTypeFromMetadata( { name, ...metadata }, settings );
};

/**
Expand Down
12 changes: 8 additions & 4 deletions packages/block-library/src/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { sortBy } from 'lodash';
import {
hasBlockSupport,
registerBlockType,
registerBlockTypeFromMetadata,
setDefaultBlockName,
setFreeformContentHandlerName,
setUnregisteredTypeHandlerName,
Expand Down Expand Up @@ -127,10 +128,13 @@ const registerBlock = ( block ) => {
return;
}
const { metadata, settings, name } = block;
registerBlockType( name, {
...metadata,
...settings,
} );
registerBlockTypeFromMetadata(
{
name,
...metadata,
},
settings
);
};

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/blocks/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New API

- `registerBlockTypeFromMetadata` method can be used to register a block type using the metadata loaded from `block.json` file ([#30293](https://github.com/WordPress/gutenberg/pull/30293)).

## 8.0.0 (2021-03-17)

### Breaking Change
Expand Down
20 changes: 20 additions & 0 deletions packages/blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,26 @@ _Returns_

- `?WPBlock`: The block, if it has been successfully registered; otherwise `undefined`.

<a name="registerBlockTypeFromMetadata" href="#registerBlockTypeFromMetadata">#</a> **registerBlockTypeFromMetadata**

Registers a new block provided from metadata stored in `block.json` file.
It uses `registerBlockType` internally.

_Related_

- registerBlockType

_Parameters_

- _metadata_ `Object`: Block metadata loaded from `block.json`.
- _metadata.name_ `string`: Block name.
- _metadata.textdomain_ `string`: Textdomain to use with translations.
- _additionalSettings_ `Object`: Additional block settings.

_Returns_

- `?WPBlock`: The block, if it has been successfully registered; otherwise `undefined`.

<a name="registerBlockVariation" href="#registerBlockVariation">#</a> **registerBlockVariation**

Registers a new block variation for the given block type.
Expand Down
17 changes: 17 additions & 0 deletions packages/blocks/src/api/i18n-block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"title": "block title",
"description": "block description",
"keywords": [ "block keyword" ],
"styles": [
{
"label": "block style label"
}
],
"variations": [
{
"title": "block variation title",
"description": "block variation description",
"keywords": [ "block variation keyword" ]
}
]
}
1 change: 1 addition & 0 deletions packages/blocks/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export { getCategories, setCategories, updateCategory } from './categories';
// children of another block.
export {
registerBlockType,
registerBlockTypeFromMetadata,
registerBlockCollection,
unregisterBlockType,
setFreeformContentHandlerName,
Expand Down
115 changes: 115 additions & 0 deletions packages/blocks/src/api/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
*/
import {
camelCase,
isArray,
isEmpty,
isFunction,
isNil,
isObject,
isPlainObject,
isString,
mapKeys,
omit,
pick,
Expand All @@ -20,11 +24,13 @@ import {
*/
import { applyFilters } from '@wordpress/hooks';
import { select, dispatch } from '@wordpress/data';
import { _x } from '@wordpress/i18n';
import { blockDefault } from '@wordpress/icons';

/**
* Internal dependencies
*/
import i18nBlockSchema from './i18n-block.json';
import { isValidIcon, normalizeIconObject } from './utils';
import { DEPRECATED_ENTRY_KEYS } from './constants';
import { store as blocksStore } from '../store';
Expand Down Expand Up @@ -310,6 +316,115 @@ export function registerBlockType( name, settings ) {
return settings;
}

/**
* Translates block settings provided with metadata using the i18n schema.
*
* @param {string|string[]|Object[]} i18nSchema I18n schema for the block setting.
* @param {string|string[]|Object[]} settingValue Value for the block setting.
* @param {string} textdomain Textdomain to use with translations.
*
* @return {string|string[]|Object[]} Translated setting.
*/
function translateBlockSettingUsingI18nSchema(
i18nSchema,
settingValue,
textdomain
) {
if ( isString( i18nSchema ) && isString( settingValue ) ) {
// eslint-disable-next-line @wordpress/i18n-no-variables, @wordpress/i18n-text-domain
return _x( settingValue, i18nSchema, textdomain );
}
if (
isArray( i18nSchema ) &&
! isEmpty( i18nSchema ) &&
isArray( settingValue )
) {
return settingValue.map( ( value ) =>
translateBlockSettingUsingI18nSchema(
i18nSchema[ 0 ],
value,
textdomain
)
);
}
if (
isObject( i18nSchema ) &&
! isEmpty( i18nSchema ) &&
isObject( settingValue )
) {
return Object.keys( settingValue ).reduce( ( accumulator, key ) => {
if ( ! i18nSchema[ key ] ) {
accumulator[ key ] = settingValue[ key ];
return accumulator;
}
accumulator[ key ] = translateBlockSettingUsingI18nSchema(
i18nSchema[ key ],
settingValue[ key ],
textdomain
);
return accumulator;
}, {} );
}
return settingValue;
}

/**
* Registers a new block provided from metadata stored in `block.json` file.
* It uses `registerBlockType` internally.
*
* @see registerBlockType
*
* @param {Object} metadata Block metadata loaded from `block.json`.
* @param {string} metadata.name Block name.
* @param {string} metadata.textdomain Textdomain to use with translations.
* @param {Object} additionalSettings Additional block settings.
*
* @return {?WPBlock} The block, if it has been successfully registered;
* otherwise `undefined`.
*/
export function registerBlockTypeFromMetadata(
{ name, textdomain, ...metadata },
additionalSettings
) {
const allowedFields = [
'apiVersion',
'title',
'category',
'parent',
'icon',
'description',
'keywords',
'attributes',
'providesContext',
'usesContext',
'supports',
'styles',
'example',
'variations',
];

const settings = pick( metadata, allowedFields );

if ( textdomain ) {
Object.keys( i18nBlockSchema ).forEach( ( key ) => {
if ( ! settings[ key ] ) {
return;
}
settings[ key ] = translateBlockSettingUsingI18nSchema(
i18nBlockSchema[ key ],
settings[ key ],
textdomain
);
} );
}

unstable__bootstrapServerSideBlockDefinitions( {
[ name ]: settings,
} );

return registerBlockType( name, additionalSettings );
}

/**
* Registers a new block collection to group blocks in the same namespace in the inserter.
*
Expand Down
Loading

0 comments on commit 9075808

Please sign in to comment.