New! JavaScript i18n support in WordPress 5.0

For years, internationalization (i18n) has been a thing that has been pretty well supported in WordPress when it comes to PHP development. For PHP, WordPress already provides all the tools necessary to make it as easy as possible to localize WordPress core, themes and plugins to any language. Today we are bringing the same capabilities to JavaScript development for WordPress.

How does it work?

When registering your scripts you can add wp-i18n as a dependency to allow you to add translatable strings as you would in PHP:

These functions mirror their PHP counterparts and can be used in exactly the same manner.

The final step is to tell WordPress your script contains translations and of which domain, this is to allow WordPress to selectively load only the necessary translations to ensure everything is as fast as can be:

wp_set_script_translations( 'my-handle', 'my-domain' );

Make sure to also specify the Text Domain for your translations in the header of your plugin file. Otherwise the translations will not be picked up by translate.wordpress.org.

Advanced usage

Right now it’s already possible to ship your own translations using the load_textdomain function and passing your own MO file. This is also possible using wp_set_script_translations which accepts an optional third path argument that allows you to tell WordPress to first look elsewhere for translations:

If passed WordPress will first check if a file in the format of ${domain}-${locale}-${handle}.json exists in the given path and use it as the source of translations if so. Alternatively it will also first check the given path for the md5 filename before defaulting to the WordPress languages directory.

If you want to ship your own translation files these should be in the JED 1.x ( .json ) format. GlotPress is able to create these along with other tools such as po2json. Ideally these files should only contain translations that occur within their respective JS files. With po2json you can generate these files as follows:

Behind the screens

When you upload your plugin or theme to wordpress.org all JS files will automatically be parsed the same as is already being done for PHP files. Any detected translations will be added to translate.wordpress.org to allow the community to cooperate to ensure WordPress, plugins and themes are available in as many languages as possible.

In order to parse all JS files the i18n-command for wp-cli is used. This replaces makepot.php to not only allow picking up translations in JS files but also to audit strings, parse strings only of a specific text domain and even pick up a few strings that weren’t detected by makepot.php. This command is freely available and open-source as makepot.php was and it’s recommended that anyone using makepot.php transition over to this much improved replacement.

Based on these parsed translations Language Packs are generated. Traditionally these used to only contain PO and MO files, one pair for each locale. In order to selectively load only the necessary translations regardless of whether it’s used or not a few more files are being added, one JSON file for every JS file that contains translations per locale.

When parsing JS files for translations we don’t know which handle is used to register that file so we’ve had to use an alternate mechanism to find the translations belonging to each file. To do this we’re using the md5 of the relative path of each file. This is appended to the usual name of ${domain}-${locale} in the form of ${domain}-${locale}-${md5}.json.

When you set script translations for a handle WordPress will automatically figure out the relative md5 hash of your source file, check to see if a translations file exists and if so ensure that it’s loaded into wp.i18n before your script runs.

Plugin and theme support

Translation and Language packs support for plugins and themes that are hosted on the repo is expected in the upcoming weeks. The patches are ready and waiting for commit. Plugin and theme authors are encouraged to start using wp-i18n in their JavaScript projects.

It’s true that you won’t be able to rely on wp_set_script_translations for older versions of WP unless you backport it. However, some plugins (including Yoast SEO) already ship JavaScript translations for quite some time. In the end it’s a matter of having the JSON translations available when you initialize wp-i18n.

We will probably move over to the new core implementation though once 5.0 lands. I think we’ll go with the backport over our current implementation, because it’s future compatible and gives the benefit of JS language packs.

When you choose to backport, please check if the functions you’re backporting weren’t set already in order to not collide with other plugins who might want to do the same thing. Might seem superfluous, but I’ve seen that go wrong more than once.

This looks like a great engineering effort — congrats to those involved!

@omarreiss Can you help explain when one would choose to use these new JS-native l18n functions in themes and plugins going forward (after 5.0 and into the future), and when one should instead use the `wp_localize_script()` workflow we’ve had in core for a while?

Are there inherent advantages of each? Will the older approach eventually be deprecated?

There hasn’t been any discussion about deprecating `wp_localize_script` and I can’t see it happening in the foreseeable future. This work in core was mostly done to support using the new `wp.i18n` package because without this work, it’s utility is diminished (and in fact necessary due to Gutenberg using `wp.i18n`).

So with that said, there’s no urgency to switch away from using `wp_localize_script` but definitely recommended to gain the advantages of the new api and in many ways enhances working with translated strings in javascript.

One big advantage of this new JS I18N API is that your translatable strings are now part of the code and not in some PHP file.

This means:

You can use proper plural forms using `_n()` and `sprintf()`, which is currently not possible with `wp_localize_script()`

Developers don’t have to go back and forth between PHP and JS to find out what kind of message `_myI18nData.someStuff.messageXY` is. Now it’s just `__()` everywhere.-

It’s much better for translators because they now have the necessary context regarding usage of a string. With `wp_localize_script()` one doesn’t really know where in the application a specific message would pop up.

Hey folks! I don’t know if I did something wrong, but these were a couple of things I had to figure out to make the JS translation work:

1. The plugin must have a Text Domain set, otherwise wp i18n make-pot command won’t search through javascript files;
2. When using wp_set_script_translations, remember that plugin_dir_path function already adds a trailings slash to it, so you can use it like this wp_set_script_translations( 'my-handle', 'my-domain', plugin_dir_path( __FILE__ ) . 'languages' ); (in opposite of concatenating it with '/languages'.
3. Using the last version of po2json, installed globally through npm, I’ve used po2json DOMAIN-LOCALE.po DOMAIN-LOCALE-HANDLER.json -p --format jed to generate the .json file correctly.

Thanks Felipe for your message, but unfortunately I saw this a bit late. I have been struggling with the command po2json translation.po translation.json -f jed1.x shown in the blog post is giving an incorrect format for the JED file.

Finally, I found out that the right one is simply po2json translation.po translation.json -f jed (as you mentioned).

@omarreiss, could you please update your text, it might save some time for other people 🙂

If the json file name is generated based on the relative file path, won’t this break the translations if someone changed the wp-content directory name for security purposes?

herregroen
1:56 pm on December 19, 2018

For plugins and themes the relative path is relative to the plugin or theme itself, so changing the wp-content directory poses no issue there. Since the JSON file name also includes the plugin/theme domain which is required to be unique this is sufficient to create unique file names.

Is this already working for / with the current version of WP (5.0.2)? I just updated one of my plugins in the repository and the JS strings are printed out without any error but without any translation, too!

Thanks to @herregroen for the chat on Slack! The new system works in general but there seams to be a bug by generating the .json files by wordpress.org! If I build them manualy and put them into the right directory everything works fine 🙂

One of the greatest things in WP is that you can write a plugin and change all the strings you want. Either there is a filter that will allow you to do it, or as last resort, you can use `gettext` filter…

But of course… There is no consistency with Gutenberg, so why should there be consistency when we think of translations…

So again, we came to point that it’s impossible to use dynamic texts in this case.

Hi Krzysiek, I understand your frustration and I’m sorry you got hit by some inflexibility you’ve encountered in the system. Please assume no bad intention on behalf of the people making WordPress and engage in constructive and civilized discussion.

Pre-filters script translations for the given file, script handle and text domain. This way you can short-circuit the script translation logic to return your own translations.

load_script_translation_file

Filters the file path for loading script translations for the given script handle and text domain..

load_script_translations

Filters script translations for the given file, script handle and text domain. This way you can override translations after they have been loaded from the translation file.

If I understand correctly, this should technically allow you to do the things you want to do.

There is still a PR open on Gutenberg to discuss also adding filters to the gettext functions. See https://github.com/WordPress/gutenberg/pull/12517. There are some performance drawbacks that are mentioned in there that make us hesitant to do that. Please feel free to add your perspective to the discussion there.

I don’t assume bad intentions. But it’s really hard not to assume a really bad job with this 5.0 branch…

It’s here for 2 months now and there are a lot of inconsistencies that are harming WP flexibility. Many features are completely ignoring previous WP standards. The documentation is very poor or non-existent… (why do you have to link some external sites of WP enthusiasts in here and not send me to proper page on Codex or WP Dev Ref?)