Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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>
FunctionWhat 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

FileWhat it is
mytheme.potThe template: every string without a translation
fr_FR.poThe French translation in text form
fr_FR.moThe same translation in binary form — WordPress reads it

WP-CLI builds the template:

wp i18n make-pot . languages/mytheme.pot

Then 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.

Thanks for reading!

Donate to the site — we work hard for you.