diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
index 49e5c8d829..df8a3dcd6d 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
@@ -105,7 +105,7 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
// Excludes invalid directory name characters: `/:<>*?"|`.
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
// Matches the template name.
- '[\/\w-]+'
+ '[\/\w%-]+'
),
array(
'args' => array(
@@ -834,7 +834,7 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
'context' => array( 'embed', 'view', 'edit' ),
'required' => true,
'minLength' => 1,
- 'pattern' => '[a-zA-Z0-9_\-]+',
+ 'pattern' => '[a-zA-Z0-9_\%-]+',
),
'theme' => array(
'description' => __( 'Theme identifier for the template.' ),
diff --git a/tests/phpunit/data/themedir1/block-theme-non-latin/index.php b/tests/phpunit/data/themedir1/block-theme-non-latin/index.php
new file mode 100644
index 0000000000..589adcefcd
--- /dev/null
+++ b/tests/phpunit/data/themedir1/block-theme-non-latin/index.php
@@ -0,0 +1,4 @@
+
+
Small Header Template Part
+
\ No newline at end of file
diff --git a/tests/phpunit/data/themedir1/block-theme-non-latin/parts/small-header-测试.html b/tests/phpunit/data/themedir1/block-theme-non-latin/parts/small-header-测试.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/phpunit/data/themedir1/block-theme-non-latin/style.css b/tests/phpunit/data/themedir1/block-theme-non-latin/style.css
new file mode 100644
index 0000000000..4de5f009a5
--- /dev/null
+++ b/tests/phpunit/data/themedir1/block-theme-non-latin/style.css
@@ -0,0 +1,7 @@
+/*
+Theme Name: Block Theme Non Latin
+Theme URI: https://wordpress.org/
+Description: Has different characters in theme directory name for testing purposes.
+Version: 0.0.1
+Text Domain: block-theme
+*/
diff --git a/tests/phpunit/data/themedir1/block-theme-non-latin/templates/page-τεστ.html b/tests/phpunit/data/themedir1/block-theme-non-latin/templates/page-τεστ.html
new file mode 100644
index 0000000000..ff8979cb4f
--- /dev/null
+++ b/tests/phpunit/data/themedir1/block-theme-non-latin/templates/page-τεστ.html
@@ -0,0 +1,3 @@
+
+(τεστ) Page Template
+
diff --git a/tests/phpunit/data/themedir1/block-theme-non-latin/templates/page-测试.html b/tests/phpunit/data/themedir1/block-theme-non-latin/templates/page-测试.html
new file mode 100644
index 0000000000..1aca9fe8ab
--- /dev/null
+++ b/tests/phpunit/data/themedir1/block-theme-non-latin/templates/page-测试.html
@@ -0,0 +1,3 @@
+
+(测试) Page Template
+
diff --git a/tests/phpunit/data/themedir1/block-theme-non-latin/theme.json b/tests/phpunit/data/themedir1/block-theme-non-latin/theme.json
new file mode 100644
index 0000000000..f0c59a63ab
--- /dev/null
+++ b/tests/phpunit/data/themedir1/block-theme-non-latin/theme.json
@@ -0,0 +1,80 @@
+{
+ "version": 1,
+ "settings": {
+ "color": {
+ "palette": [
+ {
+ "slug": "light",
+ "name": "Light",
+ "color": "#f5f7f9"
+ },
+ {
+ "slug": "dark",
+ "name": "Dark",
+ "color": "#000"
+ }
+ ],
+ "gradients": [
+ {
+ "name": "Custom gradient",
+ "gradient": "linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)",
+ "slug": "custom-gradient"
+ }
+ ],
+ "custom": false,
+ "customGradient": false
+ },
+ "typography": {
+ "fontSizes": [
+ {
+ "name": "Custom",
+ "slug": "custom",
+ "size": "100px"
+ }
+ ],
+ "customFontSize": false,
+ "customLineHeight": true
+ },
+ "spacing": {
+ "units": [
+ "rem"
+ ],
+ "customPadding": true
+ },
+ "blocks": {
+ "core/paragraph": {
+ "color": {
+ "palette": [
+ {
+ "slug": "light",
+ "name": "Light",
+ "color": "#f5f7f9"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "customTemplates": [
+ {
+ "name": "page-τεστ",
+ "title": "Homepage template"
+ },
+ {
+ "name": "page-测试",
+ "title": "Homepage template"
+ }
+ ],
+ "templateParts": [
+ {
+ "name": "small-header-τεστ",
+ "title": "Small Header",
+ "area": "header"
+ },
+ {
+ "name": "small-header-测试",
+ "title": "Small Header",
+ "area": "header"
+ },
+ ]
+}
diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php
index 9347f33256..7b8544d5e2 100644
--- a/tests/phpunit/tests/rest-api/rest-schema-setup.php
+++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php
@@ -146,14 +146,14 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
'/wp/v2/settings',
'/wp/v2/template-parts',
'/wp/v2/template-parts/(?P[\d]+)/autosaves',
- '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w-]+)',
+ '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)',
'/wp/v2/template-parts/(?P[\d]+)/autosaves/(?P[\d]+)',
'/wp/v2/template-parts/(?P[\d]+)/revisions',
'/wp/v2/template-parts/(?P[\d]+)/revisions/(?P[\d]+)',
'/wp/v2/template-parts/lookup',
'/wp/v2/templates',
'/wp/v2/templates/(?P[\d]+)/autosaves',
- '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w-]+)',
+ '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)',
'/wp/v2/templates/(?P[\d]+)/autosaves/(?P[\d]+)',
'/wp/v2/templates/(?P[\d]+)/revisions',
'/wp/v2/templates/(?P[\d]+)/revisions/(?P[\d]+)',
diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php
index fa790b66d1..718cd2f286 100644
--- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php
+++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php
@@ -66,7 +66,7 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc
'Templates route does not exist'
);
$this->assertArrayHasKey(
- '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w-]+)',
+ '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)',
$routes,
'Single template based on the given ID route does not exist'
);
@@ -294,6 +294,46 @@ class Tests_REST_WpRestTemplatesController extends WP_Test_REST_Controller_Testc
'post_excerpt' => 'Description of page home template.',
),
),
+ 'template parts: parent theme with non latin characters' => array(
+ 'theme_dir' => 'themedir1/block-theme-non-latin',
+ 'template' => 'small-header-%cf%84%ce%b5%cf%83%cf%84',
+ 'args' => array(
+ 'post_name' => 'small-header-τεστ',
+ 'post_title' => 'Small Header τεστ Template',
+ 'post_content' => file_get_contents( $theme_root_dir . '/block-theme-non-latin/parts/small-header-τεστ.html' ),
+ 'post_excerpt' => 'Description of small header τεστ template.',
+ ),
+ ),
+ 'template: parent theme with non latin name' => array(
+ 'theme_dir' => 'themedir1/block-theme-non-latin',
+ 'template' => 'page-%cf%84%ce%b5%cf%83%cf%84',
+ 'args' => array(
+ 'post_name' => 'page-τεστ',
+ 'post_title' => 'τεστ Page Template',
+ 'post_content' => file_get_contents( $theme_root_dir . 'block-theme-non-latin/templates/page-τεστ.html' ),
+ 'post_excerpt' => 'Description of page τεστ template.',
+ ),
+ ),
+ 'template parts: parent theme with chinese characters' => array(
+ 'theme_dir' => 'themedir1/block-theme-non-latin',
+ 'template' => 'small-header-%e6%b5%8b%e8%af%95',
+ 'args' => array(
+ 'post_name' => 'small-header-测试',
+ 'post_title' => 'Small Header 测试 Template',
+ 'post_content' => file_get_contents( $theme_root_dir . '/block-theme-non-latin/parts/small-header-测试.html' ),
+ 'post_excerpt' => 'Description of small header 测试 template.',
+ ),
+ ),
+ 'template: parent theme with non latin name using chinese characters' => array(
+ 'theme_dir' => 'themedir1/block-theme-non-latin',
+ 'template' => 'page-%e6%b5%8b%e8%af%95',
+ 'args' => array(
+ 'post_name' => 'page-测试',
+ 'post_title' => '测试 Page Template',
+ 'post_content' => file_get_contents( $theme_root_dir . 'block-theme-non-latin/templates/page-测试.html' ),
+ 'post_excerpt' => 'Description of page 测试 template.',
+ ),
+ ),
'template: parent theme deprecated path' => array(
'theme_dir' => 'themedir1/block-theme-deprecated-path',
'template' => 'page-home',
diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php
index e093da6dda..ea721e5a44 100644
--- a/tests/phpunit/tests/theme/themeDir.php
+++ b/tests/phpunit/tests/theme/themeDir.php
@@ -181,6 +181,7 @@ class Tests_Theme_ThemeDir extends WP_UnitTestCase {
'Block Theme Child with no theme.json',
'Block Theme Child Theme With Fluid Typography',
'Block Theme Child Theme With Fluid Typography Config',
+ 'Block Theme Non Latin',
'Block Theme [0.4.0]',
'Block Theme [1.0.0] in subdirectory',
'Block Theme Deprecated Path',
diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js
index 6ee471a87b..d64b68fa40 100644
--- a/tests/qunit/fixtures/wp-api-generated.js
+++ b/tests/qunit/fixtures/wp-api-generated.js
@@ -5105,7 +5105,7 @@ mockedApiResponse.Schema = {
"description": "Unique slug identifying the template.",
"type": "string",
"minLength": 1,
- "pattern": "[a-zA-Z0-9_\\-]+",
+ "pattern": "[a-zA-Z0-9_\\%-]+",
"required": true
},
"theme": {
@@ -5247,7 +5247,7 @@ mockedApiResponse.Schema = {
]
}
},
- "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w-]+)": {
+ "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": {
"namespace": "wp/v2",
"methods": [
"GET",
@@ -5296,7 +5296,7 @@ mockedApiResponse.Schema = {
"description": "Unique slug identifying the template.",
"type": "string",
"minLength": 1,
- "pattern": "[a-zA-Z0-9_\\-]+",
+ "pattern": "[a-zA-Z0-9_\\%-]+",
"required": false
},
"theme": {
@@ -5610,7 +5610,7 @@ mockedApiResponse.Schema = {
"description": "Unique slug identifying the template.",
"type": "string",
"minLength": 1,
- "pattern": "[a-zA-Z0-9_\\-]+",
+ "pattern": "[a-zA-Z0-9_\\%-]+",
"required": false
},
"theme": {
@@ -5789,7 +5789,7 @@ mockedApiResponse.Schema = {
"description": "Unique slug identifying the template.",
"type": "string",
"minLength": 1,
- "pattern": "[a-zA-Z0-9_\\-]+",
+ "pattern": "[a-zA-Z0-9_\\%-]+",
"required": true
},
"theme": {
@@ -5936,7 +5936,7 @@ mockedApiResponse.Schema = {
]
}
},
- "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w-]+)": {
+ "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": {
"namespace": "wp/v2",
"methods": [
"GET",
@@ -5985,7 +5985,7 @@ mockedApiResponse.Schema = {
"description": "Unique slug identifying the template.",
"type": "string",
"minLength": 1,
- "pattern": "[a-zA-Z0-9_\\-]+",
+ "pattern": "[a-zA-Z0-9_\\%-]+",
"required": false
},
"theme": {
@@ -6304,7 +6304,7 @@ mockedApiResponse.Schema = {
"description": "Unique slug identifying the template.",
"type": "string",
"minLength": 1,
- "pattern": "[a-zA-Z0-9_\\-]+",
+ "pattern": "[a-zA-Z0-9_\\%-]+",
"required": false
},
"theme": {