Changeset 51599
- Timestamp:
- 08/11/2021 09:06:31 AM (4 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/blocks.php
r51501 r51599 189 189 190 190 /** 191 * Gets i18n schema for block's metadata read from `block.json` file. 192 * 193 * @since 5.9.0 194 * 195 * @return array The schema for block's metadata. 196 */ 197 function get_block_metadata_i18n_schema() { 198 static $i18n_block_schema; 199 200 if ( ! isset( $i18n_block_schema ) ) { 201 $i18n_block_schema = wp_json_file_decode( __DIR__ . '/block-i18n.json' ); 202 } 203 204 return $i18n_block_schema; 205 } 206 207 /** 191 208 * Registers a block type from the metadata stored in the `block.json` file. 192 209 * 193 210 * @since 5.5.0 194 * @since 5.9.0 Added support for the `viewScript` field. 211 * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields. 212 * @since 5.9.0 Added support for `variations` and `viewScript` fields. 195 213 * 196 214 * @param string $file_or_folder Path to the JSON file with metadata definition for … … 210 228 } 211 229 212 $metadata = json_decode( file_get_contents( $metadata_file ), true);230 $metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) ); 213 231 if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) { 214 232 return false; … … 239 257 $settings = array(); 240 258 $property_mappings = array( 259 'apiVersion' => 'api_version', 241 260 'title' => 'title', 242 261 'category' => 'category', … … 250 269 'supports' => 'supports', 251 270 'styles' => 'styles', 271 'variations' => 'variations', 252 272 'example' => 'example', 253 'apiVersion' => 'api_version', 254 ); 273 ); 274 $textdomain = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null; 275 $i18n_schema = get_block_metadata_i18n_schema(); 255 276 256 277 foreach ( $property_mappings as $key => $mapped_key ) { 257 278 if ( isset( $metadata[ $key ] ) ) { 258 $value = $metadata[ $key ]; 259 if ( empty( $metadata['textdomain'] ) ) { 260 $settings[ $mapped_key ] = $value; 261 continue; 262 } 263 $textdomain = $metadata['textdomain']; 264 switch ( $key ) { 265 case 'title': 266 case 'description': 267 // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain 268 $settings[ $mapped_key ] = translate_with_gettext_context( $value, sprintf( 'block %s', $key ), $textdomain ); 269 break; 270 case 'keywords': 271 $settings[ $mapped_key ] = array(); 272 if ( ! is_array( $value ) ) { 273 continue 2; 274 } 275 276 foreach ( $value as $keyword ) { 277 // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain 278 $settings[ $mapped_key ][] = translate_with_gettext_context( $keyword, 'block keyword', $textdomain ); 279 } 280 281 break; 282 case 'styles': 283 $settings[ $mapped_key ] = array(); 284 if ( ! is_array( $value ) ) { 285 continue 2; 286 } 287 288 foreach ( $value as $style ) { 289 if ( ! empty( $style['label'] ) ) { 290 // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain 291 $style['label'] = translate_with_gettext_context( $style['label'], 'block style label', $textdomain ); 292 } 293 $settings[ $mapped_key ][] = $style; 294 } 295 296 break; 297 default: 298 $settings[ $mapped_key ] = $value; 279 $settings[ $mapped_key ] = $metadata[ $key ]; 280 if ( $textdomain && isset( $i18n_schema->$key ) ) { 281 $settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain ); 299 282 } 300 283 } -
trunk/src/wp-includes/class-wp-theme-json-resolver.php
r51472 r51599 41 41 42 42 /** 43 * Structure to hold i18n metadata.44 * 45 * @since 5. 8.043 * Container to keep loaded i18n schema for `theme.json`. 44 * 45 * @since 5.9.0 46 46 * @var array 47 47 */ 48 private static $ theme_json_i18n= null;48 private static $i18n_schema = null; 49 49 50 50 /** … … 60 60 $config = array(); 61 61 if ( $file_path ) { 62 $decoded_file = json_decode( 63 file_get_contents( $file_path ), 64 true 65 ); 66 67 $json_decoding_error = json_last_error(); 68 if ( JSON_ERROR_NONE !== $json_decoding_error ) { 69 trigger_error( "Error when decoding a theme.json schema at path $file_path " . json_last_error_msg() ); 70 return $config; 71 } 72 62 $decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) ); 73 63 if ( is_array( $decoded_file ) ) { 74 64 $config = $decoded_file; … … 79 69 80 70 /** 81 * Converts a tree as in i18n-theme.json into a linear array82 * containing metadata to translate a theme.json file.83 *84 * For example, given this input:85 *86 * {87 * "settings": {88 * "*": {89 * "typography": {90 * "fontSizes": [ { "name": "Font size name" } ],91 * "fontStyles": [ { "name": "Font size name" } ]92 * }93 * }94 * }95 * }96 *97 * will return this output:98 *99 * [100 * 0 => [101 * 'path' => [ 'settings', '*', 'typography', 'fontSizes' ],102 * 'key' => 'name',103 * 'context' => 'Font size name'104 * ],105 * 1 => [106 * 'path' => [ 'settings', '*', 'typography', 'fontStyles' ],107 * 'key' => 'name',108 * 'context' => 'Font style name'109 * ]110 * ]111 *112 * @since 5.8.0113 *114 * @param array $i18n_partial A tree that follows the format of i18n-theme.json.115 * @param array $current_path Optional. Keeps track of the path as we walk down the given tree.116 * Default empty array.117 * @return array A linear array containing the paths to translate.118 */119 private static function extract_paths_to_translate( $i18n_partial, $current_path = array() ) {120 $result = array();121 foreach ( $i18n_partial as $property => $partial_child ) {122 if ( is_numeric( $property ) ) {123 foreach ( $partial_child as $key => $context ) {124 $result[] = array(125 'path' => $current_path,126 'key' => $key,127 'context' => $context,128 );129 }130 return $result;131 }132 $result = array_merge(133 $result,134 self::extract_paths_to_translate( $partial_child, array_merge( $current_path, array( $property ) ) )135 );136 }137 return $result;138 }139 140 /**141 71 * Returns a data structure used in theme.json translation. 142 72 * 143 73 * @since 5.8.0 74 * @deprecated 5.9.0 144 75 * 145 76 * @return array An array of theme.json fields that are translatable and the keys that are translatable. 146 77 */ 147 78 public static function get_fields_to_translate() { 148 if ( null === self::$theme_json_i18n ) { 149 $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); 150 self::$theme_json_i18n = self::extract_paths_to_translate( $file_structure ); 151 } 152 return self::$theme_json_i18n; 153 } 154 155 /** 156 * Translates a chunk of the loaded theme.json structure. 157 * 158 * @since 5.8.0 159 * 160 * @param array $array_to_translate The chunk of theme.json to translate. 161 * @param string $key The key of the field that contains the string to translate. 162 * @param string $context The context to apply in the translation call. 163 * @param string $domain Text domain. Unique identifier for retrieving translated strings. 164 * @return array Returns the modified $theme_json chunk. 165 */ 166 private static function translate_theme_json_chunk( array $array_to_translate, $key, $context, $domain ) { 167 foreach ( $array_to_translate as $item_key => $item_to_translate ) { 168 if ( empty( $item_to_translate[ $key ] ) ) { 169 continue; 170 } 171 172 // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain 173 $array_to_translate[ $item_key ][ $key ] = translate_with_gettext_context( $array_to_translate[ $item_key ][ $key ], $context, $domain ); 174 } 175 176 return $array_to_translate; 79 _deprecated_function( __METHOD__, '5.9.0' ); 80 return array(); 177 81 } 178 82 … … 189 93 */ 190 94 private static function translate( $theme_json, $domain = 'default' ) { 191 $fields = self::get_fields_to_translate(); 192 foreach ( $fields as $field ) { 193 $path = $field['path']; 194 $key = $field['key']; 195 $context = $field['context']; 196 197 /* 198 * We need to process the paths that include '*' separately. 199 * One example of such a path would be: 200 * [ 'settings', 'blocks', '*', 'color', 'palette' ] 201 */ 202 $nodes_to_iterate = array_keys( $path, '*', true ); 203 if ( ! empty( $nodes_to_iterate ) ) { 204 /* 205 * At the moment, we only need to support one '*' in the path, so take it directly. 206 * - base will be [ 'settings', 'blocks' ] 207 * - data will be [ 'color', 'palette' ] 208 */ 209 $base_path = array_slice( $path, 0, $nodes_to_iterate[0] ); 210 $data_path = array_slice( $path, $nodes_to_iterate[0] + 1 ); 211 $base_tree = _wp_array_get( $theme_json, $base_path, array() ); 212 foreach ( $base_tree as $node_name => $node_data ) { 213 $array_to_translate = _wp_array_get( $node_data, $data_path, null ); 214 if ( is_null( $array_to_translate ) ) { 215 continue; 216 } 217 218 // Whole path will be [ 'settings', 'blocks', 'core/paragraph', 'color', 'palette' ]. 219 $whole_path = array_merge( $base_path, array( $node_name ), $data_path ); 220 $translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ); 221 _wp_array_set( $theme_json, $whole_path, $translated_array ); 222 } 223 } else { 224 $array_to_translate = _wp_array_get( $theme_json, $path, null ); 225 if ( is_null( $array_to_translate ) ) { 226 continue; 227 } 228 229 $translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ); 230 _wp_array_set( $theme_json, $path, $translated_array ); 231 } 232 } 233 234 return $theme_json; 95 if ( null === self::$i18n_schema ) { 96 $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); 97 self::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema; 98 } 99 100 return translate_settings_using_i18n_schema( self::$i18n_schema, $theme_json, $domain ); 235 101 } 236 102 … … 366 232 self::$theme = null; 367 233 self::$theme_has_support = null; 368 self::$theme_json_i18n = null;369 234 } 370 235 -
trunk/src/wp-includes/functions.php
r51557 r51599 4269 4269 4270 4270 /** 4271 * Reads and decodes a JSON file. 4272 * 4273 * @since 5.9.0 4274 * 4275 * @param string $filename Path to the JSON file. 4276 * @param array $options { 4277 * Optional. Options to be used with `json_decode()`. 4278 * 4279 * @type bool associative Optional. When `true`, JSON objects will be returned as associative arrays. 4280 * When `false`, JSON objects will be returned as objects. 4281 * } 4282 * 4283 * @return mixed Returns the value encoded in JSON in appropriate PHP type. 4284 * `null` is returned if the file is not found, or its content can't be decoded. 4285 */ 4286 function wp_json_file_decode( $filename, $options = array() ) { 4287 $result = null; 4288 $filename = wp_normalize_path( realpath( $filename ) ); 4289 if ( ! file_exists( $filename ) ) { 4290 trigger_error( 4291 sprintf( 4292 /* translators: %s: Path to the JSON file. */ 4293 __( "File %s doesn't exist!" ), 4294 $filename 4295 ) 4296 ); 4297 return $result; 4298 } 4299 4300 $options = wp_parse_args( $options, array( 'associative' => false ) ); 4301 $decoded_file = json_decode( file_get_contents( $filename ), $options['associative'] ); 4302 4303 if ( JSON_ERROR_NONE !== json_last_error() ) { 4304 trigger_error( 4305 sprintf( 4306 /* translators: 1: Path to the JSON file, 2: Error message. */ 4307 __( 'Error when decoding a JSON file at path %1$s: %2$s' ), 4308 $filename, 4309 json_last_error_msg() 4310 ) 4311 ); 4312 return $result; 4313 } 4314 4315 return $decoded_file; 4316 } 4317 4318 /** 4271 4319 * Retrieve the WordPress home page URL. 4272 4320 * -
trunk/src/wp-includes/l10n.php
r51298 r51599 1713 1713 return $wp_locale_switcher->is_switched(); 1714 1714 } 1715 1716 /** 1717 * Translates the provided settings value using its i18n schema. 1718 * 1719 * @since 5.9.0 1720 * @access private 1721 * 1722 * @param string|string[]|array[]|object $i18n_schema I18n schema for the setting. 1723 * @param string|string[]|array[] $settings Value for the settings. 1724 * @param string $textdomain Textdomain to use with translations. 1725 * 1726 * @return string|string[]|array[] Translated settings. 1727 */ 1728 function translate_settings_using_i18n_schema( $i18n_schema, $settings, $textdomain ) { 1729 if ( empty( $i18n_schema ) || empty( $settings ) || empty( $textdomain ) ) { 1730 return $settings; 1731 } 1732 1733 if ( is_string( $i18n_schema ) && is_string( $settings ) ) { 1734 return translate_with_gettext_context( $settings, $i18n_schema, $textdomain ); 1735 } 1736 if ( is_array( $i18n_schema ) && is_array( $settings ) ) { 1737 $translated_settings = array(); 1738 foreach ( $settings as $value ) { 1739 $translated_settings[] = translate_settings_using_i18n_schema( $i18n_schema[0], $value, $textdomain ); 1740 } 1741 return $translated_settings; 1742 } 1743 if ( is_object( $i18n_schema ) && is_array( $settings ) ) { 1744 $group_key = '*'; 1745 $translated_settings = array(); 1746 foreach ( $settings as $key => $value ) { 1747 if ( isset( $i18n_schema->$key ) ) { 1748 $translated_settings[ $key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $value, $textdomain ); 1749 } elseif ( isset( $i18n_schema->$group_key ) ) { 1750 $translated_settings[ $key ] = translate_settings_using_i18n_schema( $i18n_schema->$group_key, $value, $textdomain ); 1751 } else { 1752 $translated_settings[ $key ] = $value; 1753 } 1754 } 1755 return $translated_settings; 1756 } 1757 return $settings; 1758 } -
trunk/tests/phpunit/data/blocks/notice/block.json
r51501 r51599 42 42 } 43 43 ], 44 "variations": [ 45 { 46 "name": "error", 47 "title": "Error", 48 "description": "Shows error.", 49 "keywords": [ "failure" ] 50 } 51 ], 44 52 "example": { 45 53 "attributes": { -
trunk/tests/phpunit/data/languages/plugins/notice-pl_PL.po
r49981 r51599 3 3 "Project-Id-Version: \n" 4 4 "POT-Creation-Date: 2015-12-31 16:31+0100\n" 5 "PO-Revision-Date: 2021-0 1-14 18:26+0100\n"5 "PO-Revision-Date: 2021-07-15 13:36+0200\n" 6 6 "Language: pl_PL\n" 7 7 "MIME-Version: 1.0\n" 8 8 "Content-Type: text/plain; charset=UTF-8\n" 9 9 "Content-Transfer-Encoding: 8bit\n" 10 "X-Generator: Poedit 2.4.2\n"10 "X-Generator: Poedit 3.0\n" 11 11 "X-Poedit-Basepath: .\n" 12 12 "Plural-Forms: nplurals=2; plural=(n != 1);\n" … … 42 42 msgid "Other" 43 43 msgstr "Inny" 44 45 msgctxt "block variation title" 46 msgid "Error" 47 msgstr "Błąd" 48 49 msgctxt "block variation description" 50 msgid "Shows error." 51 msgstr "Wyświetla błąd." 52 53 msgctxt "block variation keyword" 54 msgid "failure" 55 msgstr "niepowodzenie" -
trunk/tests/phpunit/tests/blocks/register.php
r51568 r51599 68 68 69 69 parent::tear_down(); 70 } 71 72 /** 73 * Returns Polish locale string. 74 * 75 * @return string 76 */ 77 function filter_set_locale_to_polish() { 78 return 'pl_PL'; 70 79 } 71 80 … … 373 382 $this->assertSame( 374 383 array( 384 array( 385 'name' => 'error', 386 'title' => 'Error', 387 'description' => 'Shows error.', 388 'keywords' => array( 'failure' ), 389 ), 390 ), 391 $result->variations 392 ); 393 $this->assertSame( 394 array( 375 395 'attributes' => array( 376 396 'message' => 'This is a notice!', … … 408 428 */ 409 429 function test_block_registers_with_metadata_i18n_support() { 410 function filter_set_locale_to_polish() { 411 return 'pl_PL'; 412 } 413 add_filter( 'locale', 'filter_set_locale_to_polish' ); 430 add_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); 414 431 load_textdomain( 'notice', WP_LANG_DIR . '/plugins/notice-pl_PL.mo' ); 415 432 … … 419 436 420 437 unload_textdomain( 'notice' ); 421 remove_filter( 'locale', 'filter_set_locale_to_polish');438 remove_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); 422 439 423 440 $this->assertInstanceOf( 'WP_Block_Type', $result ); … … 440 457 $result->styles 441 458 ); 459 $this->assertSame( 460 array( 461 array( 462 'name' => 'error', 463 'title' => 'Błąd', 464 'description' => 'Wyświetla błąd.', 465 'keywords' => array( 'niepowodzenie' ), 466 ), 467 ), 468 $result->variations 469 ); 442 470 } 443 471 -
trunk/tests/phpunit/tests/functions.php
r51565 r51599 1036 1036 $json = wp_json_encode( $data, 0, 1 ); 1037 1037 $this->assertFalse( $json ); 1038 } 1039 1040 /** 1041 * @ticket 53238 1042 */ 1043 function test_wp_json_file_decode() { 1044 $result = wp_json_file_decode( 1045 DIR_TESTDATA . '/blocks/notice/block.json' 1046 ); 1047 1048 $this->assertIsObject( $result ); 1049 $this->assertSame( 'tests/notice', $result->name ); 1050 } 1051 1052 /** 1053 * @ticket 53238 1054 */ 1055 function test_wp_json_file_decode_associative_array() { 1056 $result = wp_json_file_decode( 1057 DIR_TESTDATA . '/blocks/notice/block.json', 1058 array( 'associative' => true ) 1059 ); 1060 1061 $this->assertIsArray( $result ); 1062 $this->assertSame( 'tests/notice', $result['name'] ); 1038 1063 } 1039 1064 -
trunk/tests/phpunit/tests/theme/wpThemeJsonResolver.php
r51568 r51599 43 43 public function filter_set_locale_to_polish() { 44 44 return 'pl_PL'; 45 }46 47 /**48 * @ticket 5299149 */50 public function test_fields_are_extracted() {51 $actual = WP_Theme_JSON_Resolver::get_fields_to_translate();52 53 $expected = array(54 array(55 'path' => array( 'settings', 'typography', 'fontSizes' ),56 'key' => 'name',57 'context' => 'Font size name',58 ),59 array(60 'path' => array( 'settings', 'color', 'palette' ),61 'key' => 'name',62 'context' => 'Color name',63 ),64 array(65 'path' => array( 'settings', 'color', 'gradients' ),66 'key' => 'name',67 'context' => 'Gradient name',68 ),69 array(70 'path' => array( 'settings', 'color', 'duotone' ),71 'key' => 'name',72 'context' => 'Duotone name',73 ),74 array(75 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontSizes' ),76 'key' => 'name',77 'context' => 'Font size name',78 ),79 array(80 'path' => array( 'settings', 'blocks', '*', 'color', 'palette' ),81 'key' => 'name',82 'context' => 'Color name',83 ),84 array(85 'path' => array( 'settings', 'blocks', '*', 'color', 'gradients' ),86 'key' => 'name',87 'context' => 'Gradient name',88 ),89 );90 91 $this->assertSame( $expected, $actual );92 45 } 93 46
Note: See TracChangeset
for help on using the changeset viewer.