Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Themes: Translate custom templates in theme.json #29828

Merged
merged 6 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -981,11 +981,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 @@ -994,10 +1003,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