Skip to the content.

WP-I18N

i18n (Internationalization) in WordPress refers to the process of making themes, plugins, and core functionality translation-ready so they can support multiple languages. WordPress provides built-in functions and tools for localizing (l10n) content dynamically based on a user’s language settings.

Internationalization (i18n) Guidelines

Translation Functions

There are functions available for perparing code for translation. These must be used in order to take advantage of the translation features of WordPress. There are basic functions for most simple text strings, more advanced for instances that involve variables, numeration, or html. We should not concatenate translated strings to construct phrases because sentence structure changes in different languages. In these cases we can use methods like printf to construct the phrases. Here are some quick examples:

Method Description Docs
__( 'Text', 'text-domain' ) Basic i18n function that retrieves the translation of ‘Text’ in the specified text-domain. __()
_e( 'Text', 'text-domain' ) Equivalent of echo __() _e()
_n( '%s person', '%s people', $count, 'text-domain' ) Translates and retrieves the singular or plural form based on the supplied number. _n()
printf( __( 'Search results for: %s', 'text-domain' ), $search_query ) Add variables as placeholders to the strings.  
printf( __( 'Your city is %1$s, and your zip code is %2$s.', 'text-domain' ), $city, $zipcode ) You can use printf for multiple variables too.  

Best practices:

Translation Files (l10n)

Translation files are traditionally stored in a languages directory at the root of the project. Find one in plugins and modules that are set up with translations.

All the strings that use the i18n methods will be added to a project .pot file. They get translated and each language has it’s own .po file. This is used to generate an .mo file for each language which is used by php, and a .json file used by the js. As we add any new strings to our projects or change existing strings, these should be picked up by the .pot file and make their way into each translation as we get new translated strings. When changing a string the associated translations will need to be updated too.

Translation Programming Setup

As we’ve set a text-domain, it must be loaded so WordPress knows where the (.mo) translation files can be found. load_plugin_textdomain sets this in PHP and load_script_textdomain for Javascript. The plugins are set up with this step already, see bluehost here.

The translations in PHP “just work.” There are no extra steps to get the translation when the proper translation methods are used and the text domain is set and used.

Translations for scripts require one more step however. The script handle and the text-domain need to be set via wp_set_script_translations. The first parameter is the script handle, the second is the text-domain and the third is the directory that houses the .json translation files (languages). Learn more about this set script translations step in the docs or see how we’re doing it in the bluehost plugin. This step needs to be done for each text-domain, so when a module loads a script file, it should probably also set the script translation. This allows the @wordpress/i18n library to match the original strings passed into the translation methods to be matched to the appropriatly translated string.

Script translations may also require matching the files with hashed filenames. See the load_script_translation_file filter in the help-center module.

i18n CLI Commands

There are WP-CLI commands that will help us manage these translation files and strings. See the WP-CLI i18n docs.

In the plugins (and some modules) we have a composer command that runs all the required i18n commands together. composer run i18n will first update the .pot, then update .po with any new strings, generate the .mo and .json files. These file changes need to be committed as they are released with the plugin. They should be updated at least for every release version bump. The files are checked in each release to make sure any strings are updated (and ideally, the translations updated with them) and the command is run automatically in the set-version-bump script.

Engineers need to make sure to use the translation functions for any text strings and if strings change in a PR, also update the translations for that string accordingly. As we add/edit strings, request new translations, we can even use AI or google translate to get something in place for the time being. We have a global team of engineers, and multiple folks who speak these languages, so let’s not be afraid to ask for human help, beyond AI and Google translate, a human will detect local nuances and meanings to give the translations the correct voice and tone.

Plugin Translations

All plugins are set up for translations. They have a composer command to update translation files and this is run every release to keep them updated. We’ll need to keep an eye on any changed strings to ensure they have a translation. Ideally we have a workflow that blocks a release or PR without sufficient translations.

Module Translations

All modules with user facing text should have strings set up for translations. By the nature of the modular system behind our plugin, the plugin cannot be fully translated unless all modules are translated. The modules with a build step should also load the text-domains and set the script translations wherever the modules assets are managed.

Currently, some modules that contain javascript components that are directly consumed by the plugin. Here the component has been set up to receive text via props so the plugin can manage the text translations (as is the case for hostgator). The module has default text in place (as the staging module does). In these cases, however, the module should manage the text translations. A module specific text-domain should be set up and the defaults need to be set up as translatable.

See details about our Crowdin workflow for translations.

Tools