Skip to content

Commit

Permalink
Themes: Translate custom templates in theme.json (#29828)
Browse files Browse the repository at this point in the history
* Themes: Translate custom templates in theme.json

* Rename preset to field in i18n handling for theme.json

* Add basic test for i18n support for custom templates in theme.json

* Refactor translate implementation in theme.json resolver class

* Refactor handling for template parts and custom templates

* Remove // phpcs:enable
  • Loading branch information
gziolo authored Mar 22, 2021
1 parent 14f5444 commit 85bf3a7
Show file tree
Hide file tree
Showing 10 changed files with 523 additions and 266 deletions.
464 changes: 251 additions & 213 deletions docs/how-to-guides/themes/theme-json.md

Large diffs are not rendered by default.

75 changes: 45 additions & 30 deletions lib/class-wp-theme-json-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ private static function extract_paths_to_translate( $i18n_partial, $current_path
/**
* Returns a data structure used in theme.json translation.
*
* @return array An array of theme.json paths that are translatable and the keys that are translatable
* @return array An array of theme.json fields that are translatable and the keys that are translatable
*/
public static function get_presets_to_translate() {
public static function get_fields_to_translate() {
static $theme_json_i18n = null;
if ( null === $theme_json_i18n ) {
$file_structure = self::read_json_file( __DIR__ . '/experimental-i18n-theme.json' );
Expand All @@ -151,6 +151,33 @@ public static function get_presets_to_translate() {
return $theme_json_i18n;
}

/**
* Translates a chunk of the loaded theme.json structure.
*
* @param array $array_to_translate The chunk of theme.json to translate.
* @param string $key The key of the field that contains the string to translate.
* @param string $context The context to apply in the translation call.
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
*
* @return array Returns the modified $theme_json chunk.
*/
private static function translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ) {
if ( null === $array_to_translate ) {
return $array_to_translate;
}

foreach ( $array_to_translate as $item_key => $item_to_translate ) {
if ( empty( $item_to_translate[ $key ] ) ) {
continue;
}

// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain
$array_to_translate[ $item_key ][ $key ] = translate_with_gettext_context( $array_to_translate[ $item_key ][ $key ], $context, $domain );
}

return $array_to_translate;
}

/**
* Given a theme.json structure modifies it in place
* to update certain values by its translated strings
Expand All @@ -163,37 +190,25 @@ public static function get_presets_to_translate() {
* @return array Returns the modified $theme_json_structure.
*/
private static function translate( $theme_json, $domain = 'default' ) {
if ( ! isset( $theme_json['settings'] ) ) {
return $theme_json;
}

$presets = self::get_presets_to_translate();
foreach ( $theme_json['settings'] as $setting_key => $settings ) {
if ( empty( $settings ) ) {
continue;
}

foreach ( $presets as $preset ) {
$path = array_slice( $preset['path'], 2 );
$key = $preset['key'];
$context = $preset['context'];

$array_to_translate = _wp_array_get( $theme_json['settings'][ $setting_key ], $path, null );
if ( null === $array_to_translate ) {
$fields = self::get_fields_to_translate();
foreach ( $fields as $field ) {
$path = $field['path'];
$key = $field['key'];
$context = $field['context'];
if ( 'settings' === $path[0] ) {
if ( empty( $theme_json['settings'] ) ) {
continue;
}

foreach ( $array_to_translate as $item_key => $item_to_translate ) {
if ( empty( $item_to_translate[ $key ] ) ) {
continue;
}

// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain
$array_to_translate[ $item_key ][ $key ] = translate_with_gettext_context( $array_to_translate[ $item_key ][ $key ], $context, $domain );
// phpcs:enable
$path = array_slice( $path, 2 );
foreach ( $theme_json['settings'] as $setting_key => $setting ) {
$array_to_translate = _wp_array_get( $setting, $path, null );
$translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain );
gutenberg_experimental_set( $theme_json['settings'][ $setting_key ], $path, $translated_array );
}

gutenberg_experimental_set( $theme_json['settings'][ $setting_key ], $path, $array_to_translate );
} else {
$array_to_translate = _wp_array_get( $theme_json, $path, null );
$translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain );
gutenberg_experimental_set( $theme_json, $path, $translated_array );
}
}

Expand Down
28 changes: 23 additions & 5 deletions lib/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -943,11 +943,20 @@ public function get_settings() {
* @return array
*/
public function get_custom_templates() {
$custom_templates = array();
if ( ! isset( $this->theme_json['customTemplates'] ) ) {
return array();
} else {
return $this->theme_json['customTemplates'];
return $custom_templates;
}

foreach ( $this->theme_json['customTemplates'] as $item ) {
if ( isset( $item['name'] ) ) {
$custom_templates[ $item['name'] ] = array(
'title' => isset( $item['title'] ) ? $item['title'] : '',
'postTypes' => isset( $item['postTypes'] ) ? $item['postTypes'] : array( 'page' ),
);
}
}
return $custom_templates;
}

/**
Expand All @@ -956,10 +965,19 @@ public function get_custom_templates() {
* @return array
*/
public function get_template_parts() {
$template_parts = array();
if ( ! isset( $this->theme_json['templateParts'] ) ) {
return array();
return $template_parts;
}

foreach ( $this->theme_json['templateParts'] as $item ) {
if ( isset( $item['name'] ) ) {
$template_parts[ $item['name'] ] = array(
'area' => isset( $item['area'] ) ? $item['area'] : '',
);
}
}
return $this->theme_json['templateParts'];
return $template_parts;
}

/**
Expand Down
55 changes: 46 additions & 9 deletions lib/experimental-i18n-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,54 @@
"settings": {
"*": {
"typography": {
"fontSizes": [ { "name": "Font size name" } ],
"fontStyles": [ { "name": "Font style name" } ],
"fontWeights": [ { "name": "Font weight name" } ],
"fontFamilies": [ { "name": "Font family name" } ],
"textTransforms": [ { "name": "Text transform name" } ],
"textDecorations": [ { "name": "Text decoration name" } ]
"fontSizes": [
{
"name": "Font size name"
}
],
"fontStyles": [
{
"name": "Font style name"
}
],
"fontWeights": [
{
"name": "Font weight name"
}
],
"fontFamilies": [
{
"name": "Font family name"
}
],
"textTransforms": [
{
"name": "Text transform name"
}
],
"textDecorations": [
{
"name": "Text decoration name"
}
]
},
"color": {
"palette": [ { "name": "Color name" } ],
"gradients": [ { "name": "Gradient name" } ]
"palette": [
{
"name": "Color name"
}
],
"gradients": [
{
"name": "Gradient name"
}
]
}
}
}
},
"customTemplates": [
{
"title": "Custom template name"
}
]
}
79 changes: 77 additions & 2 deletions phpunit/class-wp-theme-json-resolver-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,40 @@

class WP_Theme_JSON_Resolver_Test extends WP_UnitTestCase {

function test_presets_are_extracted() {
$actual = WP_Theme_JSON_Resolver::get_presets_to_translate();
function setUp() {
parent::setUp();
$this->theme_root = realpath( __DIR__ . '/data/themedir1' );

$this->orig_theme_dir = $GLOBALS['wp_theme_directories'];

// /themes is necessary as theme.php functions assume /themes is the root if there is only one root.
$GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root );

add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) );
add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) );
add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) );
// Clear caches.
wp_clean_themes_cache();
unset( $GLOBALS['wp_themes'] );
}

function tearDown() {
$GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
wp_clean_themes_cache();
unset( $GLOBALS['wp_themes'] );
parent::tearDown();
}

function filter_set_theme_root() {
return $this->theme_root;
}

function filter_set_locale_to_polish() {
return 'pl_PL';
}

function test_fields_are_extracted() {
$actual = WP_Theme_JSON_Resolver::get_fields_to_translate();

$expected = array(
array(
Expand Down Expand Up @@ -52,8 +84,51 @@ function test_presets_are_extracted() {
'key' => 'name',
'context' => 'Gradient name',
),
array(
'path' => array( 'customTemplates' ),
'key' => 'title',
'context' => 'Custom template name',
),
);

$this->assertEquals( $expected, $actual );
}

function test_translations_are_applied() {
add_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) );
load_textdomain( 'fse', realpath( __DIR__ . '/data/languages/themes/fse-pl_PL.mo' ) );

switch_theme( 'fse' );

$actual = WP_Theme_JSON_Resolver::get_theme_data();

unload_textdomain( 'fse' );
remove_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) );

$this->assertSame( wp_get_theme()->get( 'TextDomain' ), 'fse' );
$this->assertSame(
$actual->get_settings()['root']['color']['palette'],
array(
array(
'slug' => 'light',
'name' => 'Jasny',
'color' => '#f5f7f9',
),
array(
'slug' => 'dark',
'name' => 'Ciemny',
'color' => '#000',
),
)
);
$this->assertSame(
$actual->get_custom_templates(),
array(
'page-home' => array(
'title' => 'Szablon strony głównej',
'postTypes' => array( 'page' ),
),
)
);
}
}
17 changes: 10 additions & 7 deletions phpunit/class-wp-theme-json-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -778,8 +778,9 @@ function test_get_custom_templates() {
$theme_json = new WP_Theme_JSON(
array(
'customTemplates' => array(
'page-home' => array(
'title' => 'Some title',
array(
'name' => 'page-home',
'title' => 'Homepage template',
),
),
)
Expand All @@ -791,7 +792,8 @@ function test_get_custom_templates() {
$page_templates,
array(
'page-home' => array(
'title' => 'Some title',
'title' => 'Homepage template',
'postTypes' => array( 'page' ),
),
)
);
Expand All @@ -801,8 +803,9 @@ function test_get_template_parts() {
$theme_json = new WP_Theme_JSON(
array(
'templateParts' => array(
'header' => array(
'area' => 'Some area',
array(
'name' => 'small-header',
'area' => 'header',
),
),
)
Expand All @@ -813,8 +816,8 @@ function test_get_template_parts() {
$this->assertEqualSetsWithIndex(
$template_parts,
array(
'header' => array(
'area' => 'Some area',
'small-header' => array(
'area' => 'header',
),
)
);
Expand Down
Binary file added phpunit/data/languages/themes/fse-pl_PL.mo
Binary file not shown.
31 changes: 31 additions & 0 deletions phpunit/data/languages/themes/fse-pl_PL.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2015-12-31 16:31+0100\n"
"PO-Revision-Date: 2021-03-15 13:10+0100\n"
"Language: pl_PL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.2\n"
"X-Poedit-Basepath: .\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;"
"_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;"
"esc_html_x:1,2c\n"
"X-Textdomain-Support: yes\n"
"Last-Translator: \n"
"Language-Team: \n"
"X-Poedit-SearchPath-0: .\n"

msgctxt "Custom template name"
msgid "Homepage template"
msgstr "Szablon strony głównej"

msgctxt "Color name"
msgid "Light"
msgstr "Jasny"

msgctxt "Color name"
msgid "Dark"
msgstr "Ciemny"
Loading

0 comments on commit 85bf3a7

Please sign in to comment.