I18N: Improve JavaScript translation support.

Improves upon [43825] by adding unit tests to cover additional use-cases and changes loading translations to only occur when outputting the script to allow setting translations on dependencies.

Props atimmer, omarreiss, nerrad, swissspidy, ocean90.
Fixes #45103.


git-svn-id: https://develop.svn.wordpress.org/branches/5.0@43859 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
herregroen 2018-11-01 13:50:59 +00:00
parent fe56c5deb3
commit c4bffce143
9 changed files with 247 additions and 25 deletions

View File

@ -67,6 +67,22 @@ class _WP_Dependency {
*/
public $extra = array();
/**
* Translation textdomain set for this dependency.
*
* @since 5.0.0
* @var string
*/
public $textdomain;
/**
* Translation path set for this dependency.
*
* @since 5.0.0
* @var string
*/
public $translations_path;
/**
* Setup dependencies.
*
@ -94,4 +110,11 @@ class _WP_Dependency {
return true;
}
public function set_translations( $domain, $path = null ) {
if ( !is_string($domain) )
return false;
$this->textdomain = $domain;
$this->translations_path = $path;
return true;
}
}

View File

@ -325,6 +325,11 @@ class WP_Scripts extends WP_Dependencies {
return true;
}
$translations = $this->print_translations( $handle, false );
if ( $translations ) {
$translations = sprintf( "<script type='text/javascript'>\n%s\n</script>\n", $translations );
}
if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) ) ) {
$src = $this->base_url . $src;
}
@ -338,7 +343,7 @@ class WP_Scripts extends WP_Dependencies {
if ( ! $src )
return true;
$tag = "{$cond_before}{$before_handle}<script type='text/javascript' src='$src'></script>\n{$after_handle}{$cond_after}";
$tag = "{$translations}{$cond_before}{$before_handle}<script type='text/javascript' src='$src'></script>\n{$after_handle}{$cond_after}";
/**
* Filters the HTML script tag of an enqueued script.
@ -478,7 +483,7 @@ class WP_Scripts extends WP_Dependencies {
}
/**
* Register a translation textdomain.
* Sets a translation textdomain.
*
* @since 5.0.0
*
@ -493,23 +498,48 @@ class WP_Scripts extends WP_Dependencies {
return false;
}
/** @var \_WP_Dependency $obj */
$obj = $this->registered[ $handle ];
if ( ! in_array( 'wp-i18n', $obj->deps, true ) ) {
$obj->deps[] = 'wp-i18n';
}
return $obj->set_translations( $domain, $path );
}
/**
* Prints translations set for a specific handle.
*
* @since 5.0.0
*
* @param string $handle Name of the script to add the inline script to. Must be lowercase.
* @param bool $echo Optional. Whether to echo the script instead of just returning it.
* Default true.
* @return string|false Script on success, false otherwise.
*/
public function print_translations( $handle, $echo = true ) {
if ( ! isset( $this->registered[ $handle ] ) || empty( $this->registered[ $handle ]->textdomain ) ) {
return false;
}
$domain = $this->registered[ $handle ]->textdomain;
$path = $this->registered[ $handle ]->translations_path;
$json_translations = load_script_textdomain( $handle, $domain, $path );
if ( ! $json_translations ) {
return false;
}
/** @var \_WP_Dependency $obj */
$obj = $this->registered[ $handle ];
$obj->deps[] = 'wp-i18n';
$output = '(function( translations ){' .
'wp.i18n.setLocaleData( translations.locale_data, "' . $domain . '" );' .
'})(' . $json_translations . ');';
return $this->add_inline_script(
$handle,
'(function( translations ){' .
'wp.i18n.setLocaleData( translations.locale_data, "' . $domain . '" );' .
'})(' . $json_translations . ');',
'before'
);
if ( $echo ) {
printf( "<script type='text/javascript'>\n%s\n</script>\n", $output );
}
return $output;
}
/**

View File

@ -193,12 +193,11 @@ function wp_localize_script( $handle, $object_name, $l10n ) {
}
/**
* Register translated strings for a script.
* Sets translated strings for a script.
*
* Works only if the script has already been added.
*
* @see WP_Scripts::set_translations()
* @link https://core.trac.wordpress.org/ticket/45103
* @global WP_Scripts $wp_scripts The WP_Scripts object for printing scripts.
*
* @since 5.0.0
@ -216,10 +215,6 @@ function wp_set_script_translations( $handle, $domain, $path = null ) {
return false;
}
if ( ! wp_script_is( $handle, 'enqueued' ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Script translations may only be set if the script is enqueued.' ), '5.0.0' );
}
return $wp_scripts->set_translations( $handle, $domain, $path );
}

View File

@ -876,6 +876,8 @@ function load_child_theme_textdomain( $domain, $path = false ) {
* @link https://core.trac.wordpress.org/ticket/45103
* @global WP_Scripts $wp_scripts The WP_Scripts object for printing scripts.
*
* @since 5.0.0
*
* @param string $handle Name of the script to register a translation domain to.
* @param string $domain The textdomain.
* @param string $path Optional. The full file path to the directory containing translation files.
@ -886,10 +888,12 @@ function load_child_theme_textdomain( $domain, $path = false ) {
function load_script_textdomain( $handle, $domain, $path = null ) {
global $wp_scripts;
$path = untrailingslashit( $path );
$locale = is_admin() ? get_locale() : get_user_locale();
// If a path was given and the handle file exists simply return it.
$handle_filename = $domain . '-' . $locale . '-' . $handle . '.json';
$file_base = $domain === 'default' ? $locale : $domain . '-' . $locale;
$handle_filename = $file_base . '-' . $handle . '.json';
if ( $path && file_exists( $path . '/' . $handle_filename ) ) {
return file_get_contents( $path . '/' . $handle_filename );
}
@ -908,7 +912,7 @@ function load_script_textdomain( $handle, $domain, $path = null ) {
// If the host is the same or it's a relative URL.
if (
strpos( $content_url['path'], $src_url['path'] ) === 0 &&
strpos( $src_url['path'], $content_url['path'] ) === 0 &&
( ! isset( $src_url['host'] ) || $src_url['host'] !== $content_url['host'] )
) {
// Make the src relative the specific plugin or theme.
@ -925,7 +929,7 @@ function load_script_textdomain( $handle, $domain, $path = null ) {
) {
$relative = trim( $src_url['path'], '/' );
} else if (
( strpos( $site_url['path'], $src_url['path'] ) === 0 ) &&
( strpos( $src_url['path'], $site_url['path'] ) === 0 ) &&
( ! isset( $src_url['host'] ) || $src_url['host'] !== $site_url['host'] )
) {
// Make the src relative to the WP root.
@ -943,7 +947,7 @@ function load_script_textdomain( $handle, $domain, $path = null ) {
$relative = substr( $relative, 0, -7 ) . '.js';
}
$md5_filename = $domain . '-' . $locale . '-' . md5( $relative ) . '.json';
$md5_filename = $file_base . '-' . md5( $relative ) . '.json';
if ( $path && file_exists( $path . '/' . $md5_filename ) ) {
return file_get_contents( $path . '/' . $md5_filename );
}

View File

@ -0,0 +1,17 @@
{
"translation-revision-data": "+0000",
"generator": "GlotPress/2.3.0-alpha",
"domain": "messages",
"locale_data": {
"messages": {
"": {
"domain": "messages",
"plural-forms": "n != 1",
"lang": "en-gb"
},
"This file is a translation for script-handle.": [
"This file is a translation for script-handle."
]
}
}
}

View File

@ -0,0 +1,17 @@
{
"translation-revision-data": "+0000",
"generator": "GlotPress/2.3.0-alpha",
"domain": "messages",
"locale_data": {
"messages": {
"": {
"domain": "messages",
"plural-forms": "n != 1",
"lang": "en-gb"
},
"This is a dummy plugin.": [
"This is a dummy plugin."
]
}
}
}

View File

@ -0,0 +1,17 @@
{
"translation-revision-data": "+0000",
"generator": "GlotPress/2.3.0-alpha",
"domain": "messages",
"locale_data": {
"messages": {
"": {
"domain": "messages",
"plural-forms": "n != 1",
"lang": "en-gb"
},
"This is a dummy theme.": [
"This is a dummy theme."
]
}
}
}

View File

@ -770,20 +770,139 @@ class Tests_Dependencies_Scripts extends WP_UnitTestCase {
/**
* @ticket 45103
*/
public function test_wp_set_script_translation() {
public function test_wp_set_script_translations() {
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_enqueue_script( 'test-example', '/wp-includes/js/script.js', array(), null );
wp_set_script_translations( 'test-example', 'default', DIR_TESTDATA . '/languages/' );
wp_set_script_translations( 'test-example', 'default', DIR_TESTDATA . '/languages' );
$expected = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
$expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
"wp.i18n.setLocaleData( translations.locale_data, \"default\" );" .
"})(" . file_get_contents( DIR_TESTDATA . '/languages/default-en_US-813e104eb47e13dd4cc5af844c618754.json') . ");\n</script>\n";
"})(" . file_get_contents( DIR_TESTDATA . '/languages/en_US-813e104eb47e13dd4cc5af844c618754.json' ) . ");\n</script>\n";
$expected .= "<script type='text/javascript' src='/wp-includes/js/script.js'></script>\n";
$this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
}
/**
* @ticket 45103
*/
public function test_wp_set_script_translations_for_plugin() {
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_enqueue_script( 'plugin-example', '/wp-content/plugins/my-plugin/js/script.js', array(), null );
wp_set_script_translations( 'plugin-example', 'internationalized-plugin', DIR_TESTDATA . '/languages/plugins' );
$expected = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
$expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
"wp.i18n.setLocaleData( translations.locale_data, \"internationalized-plugin\" );" .
"})(" . file_get_contents( DIR_TESTDATA . '/languages/plugins/internationalized-plugin-en_US-2f86cb96a0233e7cb3b6f03ad573be0b.json' ) . ");\n</script>\n";
$expected .= "<script type='text/javascript' src='/wp-content/plugins/my-plugin/js/script.js'></script>\n";
$this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
}
/**
* @ticket 45103
*/
public function test_wp_set_script_translations_for_theme() {
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_enqueue_script( 'theme-example', '/wp-content/themes/my-theme/js/script.js', array(), null );
wp_set_script_translations( 'theme-example', 'internationalized-theme', DIR_TESTDATA . '/languages/themes' );
$expected = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
$expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
"wp.i18n.setLocaleData( translations.locale_data, \"internationalized-theme\" );" .
"})(" . file_get_contents( DIR_TESTDATA . '/languages/themes/internationalized-theme-en_US-2f86cb96a0233e7cb3b6f03ad573be0b.json' ) . ");\n</script>\n";
$expected .= "<script type='text/javascript' src='/wp-content/themes/my-theme/js/script.js'></script>\n";
$this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
}
/**
* @ticket 45103
*/
public function test_wp_set_script_translations_with_handle_file() {
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_enqueue_script( 'script-handle', '/wp-admin/js/script.js', array(), null );
wp_set_script_translations( 'script-handle', 'admin', DIR_TESTDATA . '/languages/' );
$expected = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
$expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
"wp.i18n.setLocaleData( translations.locale_data, \"admin\" );" .
"})(" . file_get_contents( DIR_TESTDATA . '/languages/admin-en_US-script-handle.json' ) . ");\n</script>\n";
$expected .= "<script type='text/javascript' src='/wp-admin/js/script.js'></script>\n";
$this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
}
/**
* @ticket 45103
*/
public function test_wp_set_script_translations_i18n_dependency() {
global $wp_scripts;
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_enqueue_script( 'test-example', '/wp-includes/js/script.js', array(), null );
wp_set_script_translations( 'test-example', 'default', DIR_TESTDATA . '/languages/' );
$script = $wp_scripts->registered[ 'test-example' ];
$this->assertContains( 'wp-i18n', $script->deps );
}
/**
* @ticket 45103
*/
public function test_wp_set_script_translations_when_translation_file_does_not_exist() {
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_enqueue_script( 'test-example', '/wp-admin/js/script.js', array(), null );
wp_set_script_translations( 'test-example', 'admin', DIR_TESTDATA . '/languages/' );
$expected = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
$expected .= "\n<script type='text/javascript' src='/wp-admin/js/script.js'></script>\n";
$this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
}
/**
* @ticket 45103
*/
public function test_wp_set_script_translations_after_register() {
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_register_script( 'test-example', '/wp-includes/js/script.js', array(), null );
wp_set_script_translations( 'test-example', 'default', DIR_TESTDATA . '/languages' );
wp_enqueue_script( 'test-example' );
$expected = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
$expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
"wp.i18n.setLocaleData( translations.locale_data, \"default\" );" .
"})(" . file_get_contents( DIR_TESTDATA . '/languages/en_US-813e104eb47e13dd4cc5af844c618754.json' ) . ");\n</script>\n";
$expected .= "<script type='text/javascript' src='/wp-includes/js/script.js'></script>\n";
$this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
}
/**
* @ticket 45103
*/
public function test_wp_set_script_translations_dependency() {
wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
wp_register_script( 'test-dependency', '/wp-includes/js/script.js', array(), null );
wp_set_script_translations( 'test-dependency', 'default', DIR_TESTDATA . '/languages' );
wp_enqueue_script( 'test-example', '/wp-includes/js/script2.js', array( 'test-dependency' ), null );
$expected = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
$expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
"wp.i18n.setLocaleData( translations.locale_data, \"default\" );" .
"})(" . file_get_contents( DIR_TESTDATA . '/languages/en_US-813e104eb47e13dd4cc5af844c618754.json' ) . ");\n</script>\n";
$expected .= "<script type='text/javascript' src='/wp-includes/js/script.js'></script>\n";
$expected .= "<script type='text/javascript' src='/wp-includes/js/script2.js'></script>\n";
$this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
}
/**
* Testing `wp_enqueue_code_editor` with file path.
*