Translating a theme or plugin
A theme or plugin with "Read more" hard-coded suits English-language sites only. So that it can be translated into any language, strings in the code are wrapped in translation functions, and the translations themselves are kept in separate files.
Translation functions
<?php
// content.php
?>
<a href="<?php the_permalink(); ?>"><?php esc_html_e('Read more', 'mytheme'); ?></a>
<p><?php echo esc_html__('No posts found.', 'mytheme'); ?></p>| Function | What it does |
|---|---|
__('Text', 'domain') | Returns the translation |
_e('Text', 'domain') | Prints the translation |
esc_html__(), esc_html_e() | The same with escaping for HTML |
esc_attr__(), esc_attr_e() | The same for attributes |
By convention strings in the code are written in English: the English text serves both as the key and as the default value.
The text domain
The second argument is the text domain: a unique name by which WordPress finds the translation file. It matches the folder name of the theme or plugin and is given in the header:
/*
Theme Name: My Theme
Text Domain: mytheme
Domain Path: /languages
*/Loading the translation
// functions.php
add_action('after_setup_theme', function () {
load_theme_textdomain('mytheme', get_template_directory() . '/languages');
});A plugin uses load_plugin_textdomain() on the init hook.
Placeholders
A variable must not be put into a translation string. The value is inserted with sprintf():
printf(
/* translators: %s: the author's name */
esc_html__('Posted by %s', 'mytheme'),
esc_html(get_the_author()),
);The translator sees the translators comment — it explains what the placeholder stands for.
Plurals
$count = get_comments_number();
printf(
esc_html(_n('%s comment', '%s comments', $count, 'mytheme')),
number_format_i18n($count),
);_n() picks a form by the number. English has two forms, while some languages have three or more — all of them are set in the translation file.
Context
One English word may translate differently. A context helps tell the cases apart:
$verb = _x('Post', 'verb: publish a comment', 'mytheme');
$noun = _x('Post', 'noun: a blog article', 'mytheme');Translation files
| File | What it is |
|---|---|
mytheme.pot | The template: every string without a translation |
fr_FR.po | The French translation in text form |
fr_FR.mo | The same translation in binary form — WordPress reads it |
WP-CLI builds the template:
wp i18n make-pot . languages/mytheme.potThen the .pot file is opened in the Poedit program, the strings are translated and saved — producing the .po and .mo files. For a plugin the files carry the domain: reading-time-fr_FR.mo.
Strings in JavaScript
wp_set_script_translations('mytheme-app', 'mytheme', get_template_directory() . '/languages');After that the wp.i18n.__('Text', 'mytheme') function is available in the script.
Common mistake
Translation functions receive only strings written right in the code. A __($message, 'mytheme') or __('Hello ' . $name, 'mytheme') call does not get into the .pot template: the program that collects strings does not run the code and does not know the variable's value. Translate the whole phrase with a %s placeholder and insert the value afterwards.