WordPress.org

Make WordPress Core

Changeset 44169


Ignore:
Timestamp:
12/14/2018 05:51:31 AM (16 months ago)
Author:
pento
Message:

I18N: Add JavaScript translation support.

Adds the wp_set_script_translations() function which registers translations for a JavaScript file. This function takes a handle, domain and optionally a path and ensures JavaScript translation files are loaded if they exist.

Merges [43825,43828,43859,43898] from the 5.0 branch to trunk.

Props herregroen, atimmer, omarreiss, nerrad, swissspidy, ocean90, georgestephanis.
Fixes #45103, #45256.

Location:
trunk
Files:
6 edited
4 copied

Legend:

Unmodified
Added
Removed
  • trunk

  • trunk/src/wp-includes/class-wp-dependency.php

    r42343 r44169  
    6969
    7070    /**
     71     * Translation textdomain set for this dependency.
     72     *
     73     * @since 5.0.0
     74     * @var string
     75     */
     76    public $textdomain;
     77
     78    /**
     79     * Translation path set for this dependency.
     80     *
     81     * @since 5.0.0
     82     * @var string
     83     */
     84    public $translations_path;
     85
     86    /**
    7187     * Setup dependencies.
    7288     *
     
    97113    }
    98114
     115    public function set_translations( $domain, $path = null ) {
     116        if ( ! is_string( $domain ) ) {
     117            return false;
     118        }
     119        $this->textdomain        = $domain;
     120        $this->translations_path = $path;
     121        return true;
     122    }
    99123}
  • trunk/src/wp-includes/class.wp-scripts.php

    r43583 r44169  
    331331        }
    332332
     333        $translations = $this->print_translations( $handle, false );
     334        if ( $translations ) {
     335            $translations = sprintf( "<script type='text/javascript'>\n%s\n</script>\n", $translations );
     336        }
     337
    333338        if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) ) ) {
    334339            $src = $this->base_url . $src;
     
    346351        }
    347352
    348         $tag = "{$cond_before}{$before_handle}<script type='text/javascript' src='$src'></script>\n{$after_handle}{$cond_after}";
     353        $tag = "{$translations}{$cond_before}{$before_handle}<script type='text/javascript' src='$src'></script>\n{$after_handle}{$cond_after}";
    349354
    350355        /**
     
    489494
    490495        return parent::set_group( $handle, $recursion, $grp );
     496    }
     497
     498    /**
     499     * Sets a translation textdomain.
     500     *
     501     * @since 5.0.0
     502     *
     503     * @param string $handle Name of the script to register a translation domain to.
     504     * @param string $domain The textdomain.
     505     * @param string $path   Optional. The full file path to the directory containing translation files.
     506     *
     507     * @return bool True if the textdomain was registered, false if not.
     508     */
     509    public function set_translations( $handle, $domain, $path = null ) {
     510        if ( ! isset( $this->registered[ $handle ] ) ) {
     511            return false;
     512        }
     513
     514        /** @var \_WP_Dependency $obj */
     515        $obj = $this->registered[ $handle ];
     516
     517        if ( ! in_array( 'wp-i18n', $obj->deps, true ) ) {
     518            $obj->deps[] = 'wp-i18n';
     519        }
     520        return $obj->set_translations( $domain, $path );
     521    }
     522
     523    /**
     524     * Prints translations set for a specific handle.
     525     *
     526     * @since 5.0.0
     527     *
     528     * @param string $handle Name of the script to add the inline script to. Must be lowercase.
     529     * @param bool   $echo   Optional. Whether to echo the script instead of just returning it.
     530     *                       Default true.
     531     * @return string|false Script on success, false otherwise.
     532     */
     533    public function print_translations( $handle, $echo = true ) {
     534        if ( ! isset( $this->registered[ $handle ] ) || empty( $this->registered[ $handle ]->textdomain ) ) {
     535            return false;
     536        }
     537
     538        $domain = $this->registered[ $handle ]->textdomain;
     539        $path   = $this->registered[ $handle ]->translations_path;
     540
     541        $json_translations = load_script_textdomain( $handle, $domain, $path );
     542
     543        if ( ! $json_translations ) {
     544            // Register empty locale data object to ensure the domain still exists.
     545            $json_translations = '{ "locale_data": { "messages": { "": {} } } }';
     546        }
     547
     548        $output = '(function( translations ){' .
     549                      'wp.i18n.setLocaleData( translations.locale_data, "' . $domain . '" );' .
     550                  '})(' . $json_translations . ');';
     551
     552        if ( $echo ) {
     553            printf( "<script type='text/javascript'>\n%s\n</script>\n", $output );
     554        }
     555
     556        return $output;
    491557    }
    492558
  • trunk/src/wp-includes/functions.wp-scripts.php

    r43661 r44169  
    199199
    200200    return $wp_scripts->localize( $handle, $object_name, $l10n );
     201}
     202
     203/**
     204 * Sets translated strings for a script.
     205 *
     206 * Works only if the script has already been added.
     207 *
     208 * @see WP_Scripts::set_translations()
     209 * @global WP_Scripts $wp_scripts The WP_Scripts object for printing scripts.
     210 *
     211 * @since 5.0.0
     212 *
     213 * @param string $handle Script handle the textdomain will be attached to.
     214 * @param string $domain The textdomain.
     215 * @param string $path   Optional. The full file path to the directory containing translation files.
     216 *
     217 * @return bool True if the textdomain was successfully localized, false otherwise.
     218 */
     219function wp_set_script_translations( $handle, $domain, $path = null ) {
     220    global $wp_scripts;
     221    if ( ! ( $wp_scripts instanceof WP_Scripts ) ) {
     222        _wp_scripts_maybe_doing_it_wrong( __FUNCTION__ );
     223        return false;
     224    }
     225
     226    return $wp_scripts->set_translations( $handle, $domain, $path );
    201227}
    202228
  • trunk/src/wp-includes/l10n.php

    r44134 r44169  
    892892    }
    893893    return load_theme_textdomain( $domain, $path );
     894}
     895
     896/**
     897 * Load the script translated strings.
     898 *
     899 * @see WP_Scripts::set_translations()
     900 * @link https://core.trac.wordpress.org/ticket/45103
     901 * @global WP_Scripts $wp_scripts The WP_Scripts object for printing scripts.
     902 *
     903 * @since 5.0.0
     904 *
     905 * @param string $handle Name of the script to register a translation domain to.
     906 * @param string $domain The textdomain.
     907 * @param string $path   Optional. The full file path to the directory containing translation files.
     908 *
     909 * @return false|string False if the script textdomain could not be loaded, the translated strings
     910 *                      in JSON encoding otherwise.
     911 */
     912function load_script_textdomain( $handle, $domain, $path = null ) {
     913    global $wp_scripts;
     914
     915    $path   = untrailingslashit( $path );
     916    $locale = is_admin() ? get_locale() : get_user_locale();
     917
     918    // If a path was given and the handle file exists simply return it.
     919    $file_base       = $domain === 'default' ? $locale : $domain . '-' . $locale;
     920    $handle_filename = $file_base . '-' . $handle . '.json';
     921    if ( $path && file_exists( $path . '/' . $handle_filename ) ) {
     922        return file_get_contents( $path . '/' . $handle_filename );
     923    }
     924
     925    $obj = $wp_scripts->registered[ $handle ];
     926
     927    /** This filter is documented in wp-includes/class.wp-scripts.php */
     928    $src = esc_url( apply_filters( 'script_loader_src', $obj->src, $handle ) );
     929
     930    $relative       = false;
     931    $languages_path = WP_LANG_DIR;
     932
     933    $src_url     = wp_parse_url( $src );
     934    $content_url = wp_parse_url( content_url() );
     935    $site_url    = wp_parse_url( site_url() );
     936
     937    // If the host is the same or it's a relative URL.
     938    if (
     939        strpos( $src_url['path'], $content_url['path'] ) === 0 &&
     940        ( ! isset( $src_url['host'] ) || $src_url['host'] !== $content_url['host'] )
     941    ) {
     942        // Make the src relative the specific plugin or theme.
     943        $relative = trim( substr( $src, strlen( $content_url['path'] ) ), '/' );
     944        $relative = explode( '/', $relative );
     945
     946        $languages_path = WP_LANG_DIR . '/' . $relative[0];
     947
     948        $relative = array_slice( $relative, 2 );
     949        $relative = implode( '/', $relative );
     950    } elseif ( ! isset( $src_url['host'] ) || $src_url['host'] !== $site_url['host'] ) {
     951        if ( ! isset( $site_url['path'] ) ) {
     952            $relative = trim( $src_url['path'], '/' );
     953        } elseif ( ( strpos( $src_url['path'], $site_url['path'] ) === 0 ) ) {
     954            // Make the src relative to the WP root.
     955            $relative = substr( $src, strlen( $site_url['path'] ) );
     956            $relative = trim( $relative, '/' );
     957        }
     958    }
     959
     960    // If the source is not from WP.
     961    if ( false === $relative ) {
     962        return false;
     963    }
     964
     965    // Translations are always based on the unminified filename.
     966    if ( substr( $relative, -7 ) === '.min.js' ) {
     967        $relative = substr( $relative, 0, -7 ) . '.js';
     968    }
     969
     970    $md5_filename = $file_base . '-' . md5( $relative ) . '.json';
     971    if ( $path && file_exists( $path . '/' . $md5_filename ) ) {
     972        return file_get_contents( $path . '/' . $md5_filename );
     973    }
     974    if ( file_exists( $languages_path . '/' . $md5_filename ) ) {
     975        return file_get_contents( $languages_path . '/' . $md5_filename );
     976    }
     977
     978    return false;
    894979}
    895980
  • trunk/tests/phpunit/tests/dependencies/scripts.php

    r44166 r44169  
    770770        $expected .= "<script type='text/javascript' src='/wp-includes/js/script3.js?ver={$ver}'></script>\n";
    771771        $expected .= "<script type='text/javascript' src='/wp-includes/js/script4.js?ver={$ver}'></script>\n";
     772
     773        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
     774    }
     775
     776    /**
     777     * @ticket 45103
     778     */
     779    public function test_wp_set_script_translations() {
     780        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     781        wp_enqueue_script( 'test-example', '/wp-includes/js/script.js', array(), null );
     782        wp_set_script_translations( 'test-example', 'default', DIR_TESTDATA . '/languages' );
     783
     784        $expected  = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
     785        $expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
     786                'wp.i18n.setLocaleData( translations.locale_data, "default" );' .
     787            '})(' . file_get_contents( DIR_TESTDATA . '/languages/en_US-813e104eb47e13dd4cc5af844c618754.json' ) . ");\n</script>\n";
     788        $expected .= "<script type='text/javascript' src='/wp-includes/js/script.js'></script>\n";
     789
     790        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
     791    }
     792
     793    /**
     794     * @ticket 45103
     795     */
     796    public function test_wp_set_script_translations_for_plugin() {
     797        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     798        wp_enqueue_script( 'plugin-example', '/wp-content/plugins/my-plugin/js/script.js', array(), null );
     799        wp_set_script_translations( 'plugin-example', 'internationalized-plugin', DIR_TESTDATA . '/languages/plugins' );
     800
     801        $expected  = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
     802        $expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
     803                     'wp.i18n.setLocaleData( translations.locale_data, "internationalized-plugin" );' .
     804                     '})(' . file_get_contents( DIR_TESTDATA . '/languages/plugins/internationalized-plugin-en_US-2f86cb96a0233e7cb3b6f03ad573be0b.json' ) . ");\n</script>\n";
     805        $expected .= "<script type='text/javascript' src='/wp-content/plugins/my-plugin/js/script.js'></script>\n";
     806
     807        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
     808    }
     809
     810    /**
     811     * @ticket 45103
     812     */
     813    public function test_wp_set_script_translations_for_theme() {
     814        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     815        wp_enqueue_script( 'theme-example', '/wp-content/themes/my-theme/js/script.js', array(), null );
     816        wp_set_script_translations( 'theme-example', 'internationalized-theme', DIR_TESTDATA . '/languages/themes' );
     817
     818        $expected  = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
     819        $expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
     820                     'wp.i18n.setLocaleData( translations.locale_data, "internationalized-theme" );' .
     821                     '})(' . file_get_contents( DIR_TESTDATA . '/languages/themes/internationalized-theme-en_US-2f86cb96a0233e7cb3b6f03ad573be0b.json' ) . ");\n</script>\n";
     822        $expected .= "<script type='text/javascript' src='/wp-content/themes/my-theme/js/script.js'></script>\n";
     823
     824        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
     825    }
     826
     827    /**
     828     * @ticket 45103
     829     */
     830    public function test_wp_set_script_translations_with_handle_file() {
     831        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     832        wp_enqueue_script( 'script-handle', '/wp-admin/js/script.js', array(), null );
     833        wp_set_script_translations( 'script-handle', 'admin', DIR_TESTDATA . '/languages/' );
     834
     835        $expected  = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
     836        $expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
     837                     'wp.i18n.setLocaleData( translations.locale_data, "admin" );' .
     838                     '})(' . file_get_contents( DIR_TESTDATA . '/languages/admin-en_US-script-handle.json' ) . ");\n</script>\n";
     839        $expected .= "<script type='text/javascript' src='/wp-admin/js/script.js'></script>\n";
     840
     841        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
     842    }
     843
     844    /**
     845     * @ticket 45103
     846     */
     847    public function test_wp_set_script_translations_i18n_dependency() {
     848        global $wp_scripts;
     849
     850        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     851        wp_enqueue_script( 'test-example', '/wp-includes/js/script.js', array(), null );
     852        wp_set_script_translations( 'test-example', 'default', DIR_TESTDATA . '/languages/' );
     853
     854        $script = $wp_scripts->registered['test-example'];
     855
     856        $this->assertContains( 'wp-i18n', $script->deps );
     857    }
     858
     859    /**
     860     * @ticket 45103
     861     */
     862    public function test_wp_set_script_translations_when_translation_file_does_not_exist() {
     863        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     864        wp_enqueue_script( 'test-example', '/wp-admin/js/script.js', array(), null );
     865        wp_set_script_translations( 'test-example', 'admin', DIR_TESTDATA . '/languages/' );
     866
     867        $expected  = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
     868        $expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
     869                     'translations.locale_data.messages[""].domain = "admin";' .
     870                     'wp.i18n.setLocaleData( translations.locale_data.messages, "admin" );' .
     871                     "})({ \"locale_data\": { \"messages\": { \"\": {} } } });\n</script>\n";
     872        $expected .= "<script type='text/javascript' src='/wp-admin/js/script.js'></script>\n";
     873
     874        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
     875    }
     876
     877    /**
     878     * @ticket 45103
     879     */
     880    public function test_wp_set_script_translations_after_register() {
     881        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     882        wp_register_script( 'test-example', '/wp-includes/js/script.js', array(), null );
     883        wp_set_script_translations( 'test-example', 'default', DIR_TESTDATA . '/languages' );
     884
     885        wp_enqueue_script( 'test-example' );
     886
     887        $expected  = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
     888        $expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
     889                     'wp.i18n.setLocaleData( translations.locale_data, "default" );' .
     890                     '})(' . file_get_contents( DIR_TESTDATA . '/languages/en_US-813e104eb47e13dd4cc5af844c618754.json' ) . ");\n</script>\n";
     891        $expected .= "<script type='text/javascript' src='/wp-includes/js/script.js'></script>\n";
     892
     893        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
     894    }
     895
     896    /**
     897     * @ticket 45103
     898     */
     899    public function test_wp_set_script_translations_dependency() {
     900        wp_register_script( 'wp-i18n', '/wp-includes/js/dist/wp-i18n.js', array(), null );
     901        wp_register_script( 'test-dependency', '/wp-includes/js/script.js', array(), null );
     902        wp_set_script_translations( 'test-dependency', 'default', DIR_TESTDATA . '/languages' );
     903
     904        wp_enqueue_script( 'test-example', '/wp-includes/js/script2.js', array( 'test-dependency' ), null );
     905
     906        $expected  = "<script type='text/javascript' src='/wp-includes/js/dist/wp-i18n.js'></script>";
     907        $expected .= "\n<script type='text/javascript'>\n(function( translations ){" .
     908                     'wp.i18n.setLocaleData( translations.locale_data, "default" );' .
     909                     '})(' . file_get_contents( DIR_TESTDATA . '/languages/en_US-813e104eb47e13dd4cc5af844c618754.json' ) . ");\n</script>\n";
     910        $expected .= "<script type='text/javascript' src='/wp-includes/js/script.js'></script>\n";
     911        $expected .= "<script type='text/javascript' src='/wp-includes/js/script2.js'></script>\n";
    772912
    773913        $this->assertEquals( $expected, get_echo( 'wp_print_scripts' ) );
Note: See TracChangeset for help on using the changeset viewer.