Internationalization

What is internationalization? What is internationalization?

Internationalization is the process of developing your theme, so it can easily be translated into other languages. Internationalization is often abbreviated as i18n (because there are 18 letters between the letters i and n).

Top ↑

Why is internationalization important? Why is internationalization important?

WordPress is used all over the world, in countries where English is not the main language. The strings in the WordPress plugins need to be coded in a special way so that can be easily translated into other languages. As a developer, you may not be able to provide localizations for all your users; however, a translator can successfully localize the theme without needing to modify the source code itself.

Top ↑

How to internationalize your theme? How to internationalize your theme?

For the text in the theme to be able to be translated easily the text should not be hardcoded in the theme but be passed as an argument through one of the localization functions in WordPress.

The following example could not be translated unless the translator modified the source code which is not very efficient.

<h1>Settings Page</h1>

By passing the string through a localization function it can it can be easily parsed to be translated.

<h1><?php __( 'Settings Page' ); ?></h1>

WordPress uses gettext libraries to be able to add the translations in PHP. In WordPress you should use the WordPress localization functions instead of the native PHP gettext-compliant translation functions.

Text Domain Text Domain

The text domain is the second argument that is used in the internationalization functions. The text domain is a unique identifier, allowing WordPress to distinguish between all of the loaded translations. The text domain is only needed to be defined for themes and plugins.

Themes that are hosted on WordPress.org the text domain must match the slug of your theme URL (wordpress.org/themes/<slug>). This is needed so that the translations from translate.wordpress.org work correctly.

The text domain name must use dashes and not underscores and be lowercase. For example, if the theme’s name My Theme is defined in the style.css or it is contained in a folder called my-theme the text domain should be my-theme.

The text domain is used in three different places:

  1. In the style.css theme header
  2. As an argument in the localization functions
  3. As an argument when loading the translations using load_theme_textdomain() or  load_child_theme_textdomain()

style.css theme header style.css theme header

The text domain is added to the style.css header so that the theme meta-data like the description can be translated even when the theme is not enabled. The text domain should be same as the one used when loading the text domain.

Example:

/*
* Theme Name: My Theme
* Author: Theme Author
* Text Domain: my-theme
*/
Domain Path

The domain path is needed when the translations are saved in a directory other than languages . This is so that WordPress knows where to find the translation when the theme is not activated. For example, if .mo files are located in the languages folder then Domain Path will be /languages and must be written with the first slash. Defaults to the languages folder in the theme.

Example:

/*
* Theme Name: My Theme
* Author: Theme Author
* Text Domain: my-theme
* Domain Path: /languages
*/

Top ↑

Add text domain to strings Add text domain to strings

The text domain should be added as an argument to all of the localization functions for the translations to work correctly.

Examples:

  • __( 'Post' )

    should become

    __( 'Post', 'my-theme' )
  • _e( 'Post' )

    should become

    _e( 'Post', 'my-theme' )
  • _n( 'One post', '%s posts', $count )

    should become

    _n( 'One post', '%s posts', $count, 'my-theme' )

Warning: The text domain should be passed as a string to the localization functions instead of a variable. It allows parsing tools to differentiate between text domains. Example of what not to do:

__( 'Translate me.' , $text_domain );

Top ↑

Loading Translations Loading Translations

The translations in WordPress are saved in .po and .mo files which need to be loaded. They can be loaded by using the functionsload_theme_textdomain() or load_child_theme_textdomain(). This loads {locale}.mo from your theme’s base directory or {text-domain}-{locale}.mo from the WordPress theme language folder in /wp-content/languages/themes/.

Note: As of version 4.6 WordPress automatically checks the language directory in wp-content for translations from translate.wordpress.org. This means that plugins that are translated via translate.wordpress.org do not require load_plugin_textdomain() anymore.
If you don’t want to add a load_plugin_textdomain() call to your plugin you should set the Requires at least: field in your readme.txt to 4.6.

To find out more about the different language and country codes, see the list of languages.

Watch Out

  • Name your MO file as {locale}.mo (e.g. de_DE.po & de_DE.mo) if adding the translation to the theme folder.
  • Name your MO file as {text-domain}-{locale}.mo (e.g my-theme-de_DE.po & my-theme-de_DE.mo) if you are adding the translation to the WordPress theme language folder.

Example:

function my_theme_load_theme_textdomain() {
load_theme_textdomain( 'my-theme', get_template_directory() . '/languages' );
}
add_action( 'after_setup_theme', 'my_theme_load_theme_textdomain' );

This function should ideally be run within the themes’ function.php.

Language Packs

If you’re interested in language packs and how the import to translate.wordpress.org is working, please read the Meta Handbook page about Translations.

Top ↑

Localization functions Localization functions

Top ↑

Basic functions Basic functions

Top ↑

Translate & Escape functions Translate & Escape functions

Strings that require translation and is used in attributes of html tags must be escaped.

Top ↑

Date and number functions Date and number functions

Top ↑

Basic strings Basic strings

The most commonly used function is __(). It returns the translation of its argument:

__( 'Blog Options', 'my-theme' );

Another simple one is _e(), which outputs the translation of its argument. Instead of writing:

echo __( 'WordPress is the best!', 'my-theme' );

you can use the shorter:

_e( 'WordPress is the best!', 'my-theme' );

Top ↑

Variables Variables

If you are using variables in strings, similar to the example below, you need to use placeholders.

echo 'Your city is $city.'

Use the printf family of functions. Especially helpful are printf and sprintf. For example:

printf(
/* translators: %s: Name of a city */
__( 'Your city is %s.', 'my-theme' ),
$city
);

Notice that the string for translation is the template "Your city is %s.", which is the same in both the source and at run-time.

If you have more than one placeholder in a string, it is recommended that you use argument swapping. In this case, single quotes (') are mandatory : double quotes (") tell php to interpret the $s as the s variable, which is not what we want.

printf(
/* translators: 1: Name of a city 2: ZIP code */
__( 'Your city is %1$s, and your zip code is %2$s.', 'my-theme' ),
$city,
$zipcode
);

Here the zip code is displayed after the city name. In some languages, displaying the zip code and city in opposite order is more appropriate. Using %s prefix, as in the above example, allows for this. A translation can be written:

printf(
/* translators: 1:ZIP code 2:Name of a city */
__( 'Your zip code is %2$s, and your city is %1$s.', 'my-theme' ),
$city,
$zipcode
);

The following example tells you what not to do

Warning: This is incorrect.

// This is incorrect do not use.
_e( "Your city is $city.", 'my-theme' );

The strings for translation are extracted from the source without executing the PHP associated with it. For example: The variable $city may be Vancouver, so your string will read "Your city is Vancouver" when the template is run but gettext won’t know what is inside the PHP variable in advance.

As the value of the variable is unknown when your string is translated, it would require the translator to know every case for the variable $country. This is not ideal, and it is best to remove dynamic content and allow translators to focus on static content.

Top ↑

Plurals Plurals

Top ↑

Basic Pluralization Basic Pluralization

If you have a string that changes when the number of items changes. In English you have "One comment" and "Two comments". In other languages you can have multiple plural forms. To handle this in WordPress, you can use the _n() function.

printf(
_n(
'%s comment',
'%s comments',
get_comments_number(),
'my-theme'
),
number_format_i18n( get_comments_number() )
);

_n() accepts 4 arguments:

  • singular – the singular form of the string (note that it can be used for numbers other than one in some languages, so '%s item' should be used instead of 'One item')
  • plural – the plural form of the string
  • count – the number of objects, which will determine whether the singular or the plural form should be returned (there are languages, which have far more than 2 forms)
  • text domain – the theme’s text domain

The return value of the functions is the correct translated form, corresponding to the given count.

Top ↑

Pluralization done later Pluralization done later

You first set the plural strings with _n_noop() or _nx_noop().

$comments_plural = _n_noop(
'%s comment.',
'%s comments.'
);

At a later point in the code, you can use translate_nooped_plural() to load the strings.

printf(
translate_nooped_plural(
$comments_plural,
get_comments_number(),
'my-theme'
),
number_format_i18n( get_comments_number() )
);

Top ↑

Disambiguation by context Disambiguation by context

Sometimes a term is used in more than one context and must be translated separately in other languages, even though the same word is used for each context in English. For example, the word Post can be used both as a verb "Click here to post your comment" and as a noun "Edit this Post". In such cases the _x() or _ex() function should be used. It is similar to __() and _e(), but it has an additional argument — the context:

_x( 'Post', 'noun', 'my-theme' );
_x( 'post', 'verb', 'my-theme' );

Using this method in both cases, we get the string Comment for the original version. However, translators will see two Comment strings for translation, each in a different context.

Taking an example from the German version of WordPress as an illustration: Post is Beiträge. The corresponding verb form in German is beitragen.

Note that similar to __(), _x() has an echo version: _ex(). The previous example could be written as:

_ex( 'Post', 'noun', 'my-theme' );
_ex( 'post', 'verb', 'my-theme' );

Use the one you feel enhances legibility and ease of coding.

Top ↑

Descriptions Descriptions

You can add a clarifying comment in the source code, so translators know how to translate a string like __( 'g:i:s a' ) . It must start with the words translators: and be the last PHP comment before the gettext call. Here is an example:

/* translators: draft saved date format, see http://php.net/date */
$saved_date_format = __( 'g:i:s a' );

Top ↑

Newline characters Newline characters

Gettext doesn’t like \r (ASCII code: 13) in translatable strings, so avoid it and use \n instead.

Top ↑

Empty strings Empty strings

The empty string is reserved for internal Gettext usage, and you must not try to internationalize the empty string. It also doesn’t make any sense because translators won’t have context.

If you have a valid use-case to internationalize an empty string, add context to both help translators and be in peace with the Gettext system.

Top ↑

Handling JavaScript files Handling JavaScript files

Use wp_localize_script() to add translated strings or other server-side data to a previously enqueued script.

Top ↑

Escaping strings Escaping strings

It is good to escape all of your strings, preventing translators from running malicious code. There are a few escape functions that are integrated with internationalization functions.

<a title="<?php esc_attr_e( 'Skip to content', 'my-theme' ); ?>" class="screen-reader-text skip-link" href="#content" >
  <?php _e( 'Skip to content', 'my-theme' ); ?>
</a>
<label for="nav-menu">
  <?php esc_html_e( 'Select Menu:', 'my-theme' ); ?>
</label>

Top ↑

Best Practices for writing strings Best Practices for writing strings

Here are the best practices for writing strings

  • Use decent English style – minimize slang and abbreviations.
  • Use entire sentences – in most languages, word order is different than English.
  • Split at paragraphs – merge related sentences, but do not include a whole page of text in one string.
  • Do not leave leading or trailing whitespace in a translatable phrase.
  • Assume strings can double in length when translated.
  • Avoid unusual markup and unusual control characters – do not include tags that surround your text.
  • Do not put unnecessary HTML markup into the translated string.
  • Do not leave URLs for translation, unless they could have a version in another language.
  • Add the variables as placeholders to the string as in some languages the placeholders change position.
printf(
__( 'Search results for: %s', 'my-theme' ),
get_search_query()
);
  • Use format strings instead of string concatenation – translate phrases and not words –
    printf(
    __( 'Your city is %1$s, and your zip code is %2$s.', 'my-theme' ),
    $city,
    $zipcode
    );
    

    is always better than

    __( 'Your city is ', 'my-theme' ) . $city . __( ', and your zip code is ', 'my-theme' ) . $zipcode;
    
  • Try to use the same words and symbols to prevent translating multiple similar strings (e.g. don’t do the following)
    __( 'Posts:', 'my-theme' ); and __( 'Posts', 'my-theme' );

 

Top ↑

Resources Resources