WordPress versions 4.9 and below allowed you to translate text in your JavaScript by passing JSON objects via wp_localize_script
. While this system works great it has some limitations.
With the release of WordPress 5.0, support was introduced to allow developers to translate their theme or plugin directly within JavaScript. Translating in JavaScript has some nice benefits:
- You add your text in the React components where it is used so you don’t have to map objects to strings.
- Translations need only be loaded when the site is in a language you have translations for instead of loading for every language.
- Translation calls are done during translation generation not during page load which improves performance.
The available tools work great if you are using plain JavaScript and no form of Continuous Delivery (CI/CD) in your workflow. If that is the case, go no further. The Block Editor handbook has what you need.
The TypeScript Problem
The available tools provided from WP-CLI for translating have the following assumptions:
- Each JS file is a single file per block and should therefore have a separate translation file per JS file.
- All source files have a
.js
or.jsx
extension. - A WordPress install must be available which adds complexity to Continuous Delivery.
As you know, if you are reading this, using a modern Webpack based application with TypeScript does not fit into those assumptions.
The maintainers of the WP-CLI command are not working on a solution because of an underlying dependency limitation.
There are some suggested workarounds to compile your application first then run the WP-CLI commands against it. This was not reasonable for us for the following reasons:
- Our Webpack compiler will obfuscate the finished JS to the point that the WP-CLI tools do not pick up the calls correctly.
- We prefer to keep our translation process away from our Webpack build process as the translation system is intended to be independent.
The Solution
This is what you came here for right…?
Let’s recap our goals:
- The system must pull translations out of TypeScript source files. Either
.tsx
or.ts
(although TSX would be more prominent). - The generation of the finished JSON files must be able to be run during Continuous Delivery.
- The generation of the finished JSON files must be able to run without a WordPress install available.
- The JSON files must be one file per language, not one file per source file.
- The JSON files must contain only translations located within TypeScript files and not PHP required translations.
Generating the .PO files
To enter the translations into the .po
files we use a tool called Poedit. Since we are already using Poedit as part of the translation process, it was a no brainer to also use Poedit to generate the .po
files.
Out of the box, Poedit does not support TypeScript extensions. TypeScript support may be added using the following:
- File -> Preferences
- Tab -> Extractors
+
Settings
Language: TypeScript
List of extensions: *.tsx;*.ts
Command: xgettext -L JavaScript --add-comments=translators: --force-po -o %o %C %K %F
An item in keyword list: -k%k
An item in input files list: %f
Source code charset: --from-code=%c
Now you may click the “Update from code” button like normal and your .po
files will fill with translations within TypeScript. Enter your translations, hit save, and you’re good to go.
Generating the JSON translation files
For this part we create a custom PHP script which takes the finished translation from the .po
files and turns them into something WordPress can understand.
First we need Gettext. Gettext is a library for managing translations. It is the same library used by WP-CLI as well as WordPress Core. We install it using Composer.
This will generate a composer.json which looks like this:
Next create a PHP script. This could be named anything you like, but we named ours command.php
This PHP script does the following tasks:
- Loops through every
.po
file found in thelanguages
directory. - Generates a matching
.json
file for each found.po
file. - Returns a code to let the runner know if any of the tasks failed.
The only change you’ll need to make is update the RELATIVE_PATH
to match where your finished JS (non min) file is located relative to your plugin or theme. This is important because it controls the hash included in the .json
file names. If the RELATIVE_PATH
does not match where the file is located when it is enqueued via wp_enqueue_script
or wp_register_script
, WordPress won’t load it.
For more information check out the source code of
load_script_textdomain
.
Running the PHP script is simple:
Integrating with your build system
The system wouldn’t be complete if we had to manually run the PHP script before each release. You can add the commands to whatever Continuous Delivery system you use. For these purposes we’re going to use GitHub Actions.
Add the following jobs to your existing action configuration and the JSON files will auto generate during each release.
Notice how we set a PHP_VERSION
environmental variable? That is an optional way to manage which PHP version will be install and used for the PHP commands.
Conclusion
That’s it! You’re now translating your TypeScript.