Codex tools: Log in / create account
Contents |
I18n is a abbreviation for internationalization, or the process of making an application ready for translation. In the WordPress case it means marking strings, which should be translated in a special way. It is called i18n, because there are 18 letters between the I and the n.
WordPress uses the gettext libraries and tools for i18n.
In order to make a string translatable in your application you have to just wrap the original string in a __ function call:
$hello = __("Hello, dear user!");
If your code should echo the string to the browser, use the _e function instead:
_e("Your Ad here")
After the strings are marked in the source files, a gettext utility called xgettext is used to extract the original strings and to build a template translation POT file. Here is an example POT file entry:
#: wp-admin/admin-header.php:49 msgid "Sign Out" msgstr ""
Every translator takes the WordPress POT file and translates the msgstr sections in their own language. The result is a PO file with in the same format as a POT, but with translations and some specific headers.
From a translated PO file a MO file is built. This is a binary file which contains all the original strings and their translations in a format suitable for fast translation extraction. The conversion is done using the msgfmt tool.
Sometimes in one application you need to use more than one large logical translatable module and a different MO file accordingly. A domain is a handle of each module, which has a different MO file. In WordPress translations of themes and plugins live in different domains.
If you want to get a broader and deeper view of gettext, we recommend you the gettext online manual.
The strings for translation are wrapped in a call to one of a set of special functions. The most commonly used one is __(). It just returns the translation of its argument:
echo "<h2>".__('Blog Options')."</h2>";
Another simple one is _e(), which outputs the translation of its argument. Instead of writing echo __('Using this option you will make a fortune!'); you can use the shorter _e('Using this option you will make a fortune!');
echo "We deleted $count spams."
How would you i18n this line? Let's give it a try together:
_e("We deleted $count spams.");
It won't work! Remember, the strings for translation are extracted from the sources, so the translators will see work on the phrase: We deleted $count spams.. However in the application _e will be called with an argument like We deleted 49494 spams. and gettext won't find a suitable translation of this one and will return its argument: We deleted 49494 spams.. Unfortunately, it isn't translated correctly.
The solution is to use the printf family of functions. Especially helpful are printf and sprintf. Here is what the right solution of the spams count problem will look like:
printf(__("We deleted %d spams."), $count);
Notice that here the string for translation is just the template We deleted %d spams., which is the same both in the source and at run-time.
If you have more than one placeholders, please allow argument swapping.
printf(__("Your city is %1$s, and your zip code is %2$s."));
Here the original author of the string implied an order of the city and zip code. However in some countries it would be more suitable to list the zip first and city second. If you had used %s you wouldn't have given the translator the opportunity to swap them.
Let's get back to the spams example: printf(__("We deleted %d spams."), $count);. What if we delete only one spam? The output will be: We deleted 1 spams., which is definitely not correct English, and probably any other language.
In WordPress you can use the __ngettext function.
printf(__ngettext("We deleted %d spam.", "We deleted %d spams.", $count), $count);
__ngettext accepts 3 arguments:
The return value of the functions is the correct translated form, corresponding to the given count.
Do you think translators will know how to translate a string like: __("%1$s%2$s")? Here comes to help the _c() function:
_c("%1$s%2$s|date time");
Now you can write a "personal" message to the translators, so that they know how to deal with the string. Of course if they accidentally translate the comment part it is stripped automatically.
Until we gather some WordPress-specific examples, use your time to read the short, but excellent article in the gettext manual. Summarized, it looks like this:
The text domain is a unique identifier, which makes sure WordPress can distinguish between all loaded translations. Using the basename of your plugin is always a good choice.
Example: if your plugin is a single file called shareadraft.php or it is contained in a folder called shareadraft the best domain name you can choose is shareadraft. In case of a theme — choose the directory name.
The domain name is also used to form the name of the MO file with your plugins' translations. You can load them by invoking:
load_plugin_textdomain( $domain, $path_from_abspath, $path_from_plugins_folder )
as early as the init action.
Example:
$plugin_dir = basename(dirname(__FILE__)); load_plugin_textdomain( 'myplugin', 'wp-content/plugins/' . $plugin_dir, $plugin_dir );
This call tries to load myplugin.mo from your plugin's base directory.
The second and third parameters are present because of a change that occurred in WordPress 2.6. For versions lower than 2.6, the second parameter should be the directory containing the .mo file, relative to ABSPATH.
For WordPress 2.6 and up, the third parameter is the directory containing the .mo file, relative to the plugins directory. (Thus, if you plugin doesn't need compatibility with older versions of WordPress, you can leave the second parameter blank.)
For themes the process is surprisingly similar:
load_theme_textdomain(domain-name);
Put this call in your functions.php and it will search your theme directory for locale.mo and load it.
All the rules from above apply here, but there is one more. The additional rule states that you must add your domain as an argument to every __, _e, _c and __ngettext call, otherwise your translations won't work.
Examples:
Adding the domain by hand is a burden and that's why you can do it automatically:
Otherwise:
php add-textdomain.php -i domain phpfile phpfile ...
After it's done, the domain will be added to all gettext calls in the files.
You remember the POT file is the one you need to hand to translators, so that they can do their work, don't you?
Once that is in place, there are a couple of ways to generate a POT file for your plugin:
php makepot.php wp-plugin your-plugin-directory
After it's finished you should see the POT file in the current directory.
It is a good idea to offer the POT file along with your plugin, so that translators won't have to ask you specifically about it.
Also, if you add a line like this to your plugin header, WordPress should internationalize your plugin meta-data when it displays your plugin in the admin screens:
Text Domain: your-text-domain