In our first WordPress i18n article, we covered how to localize content with the Polylang plugin. In our second installment in this series, we looked at basic gettext with WordPress and how to start theme localization.
In this part, we’ll round out our understanding of WordPress theme localization with a bunch of recipes for accomplishing common tasks. By the end of this article, we’ll have something to show our clients, Najla and Taha. In part 2, we started a custom localized theme for our clients’ North African handmade crafts business, Handmade’s Tale. By the end of this part, we’ll actually have a deliverable to show Najla and Taha: a working localized theme. Here are the recipes we’ll cover:
- Basic i18n for theme UI elements (we started this in part 2)
- Singular and plural variants of translated strings
- Date & time formatting
- Number formatting
- Providing context for translators when translated strings are duplicated in different parts of our interface
- Getting the current locale for use in our theme
- Determining the current locale’s layout direction
- Writing locale-specific CSS
- Conditional, per-locale loading of assets
- Creating a custom language switcher
Note » I’m assuming you have basic knowledge of WordPress theme coding, including file structure, PHP, basic WordPress functions, HTML, and CSS.
As you can see, we will create a little grab bag of useful recipes that can assist us as we write our custom, localized WordPress themes. Let’s get started!
When we’re done, our site will look like this.
WordPress and Plugins
Let’s get started. Here’s what we had installed in part 2.
- WordPress (5.0.3)
- Polylang (2.5.2) ~ handles content & URL localization
- Loco Translate (2.2.0) ~ facilitates the translation of theme and plugin strings
- Contact Form 7 (5.1.1) ~ makes it easy to build contact forms
- CF7 Smart Grid Design Extension (2.8.0) ~ gives us more flexibility over CF7 forms from within the WordPress admin console (however, we’re really just installing it here since it’s a requirement of the Contact Form 7 Polylang extension)
- Contact Form 7 Polylang extension (2.3.1) ~ allows us to use Polylang to localize CF7 forms
Note » Since part 2, WordPress and some plugins have been updated. The updates are minor and point releases and shouldn’t have any effect on our work in this series to date. However, do note the new version numbers if you’re building along with us.
The Polylang plugin is handling a lot of the i18n work for us and, therefore, it deserves special mention. We’ll be relying on the localization features that Polylang gives us as we build our theme. If you’re not familiar with the basics of Polylang, you may want to check out part 1 of this series, where we cover the use of Polylang in detail.
We’ll have a basic WordPress theme directory structure. We covered some of these files in part 2, and we’ll cover more in this article.
| ├── themes/
| | ├── handmadestale/
| | | ├── classes/
| | | | ├── HMT_Language_Switcher_Widget.php
| | | | └── HMT_Walker_Nav_Menu.php
| | | ├── img/
| | | | └── logo.png
| | | ├── footer.php
| | | ├── functions.php
| | | ├── header.php
| | | ├── index.php
| | | ├── language-switcher.php
| | | ├── loop.php
| | | ├── page.php
| | | ├── single.php
| | | ├── style.css
| | | └── stylesheet-links.php
| | └── ...
| └── ...
Note » Some file names have changed slightly since part 2.
Note » All the code for this article can be found in its companion Github repo.
Basic UI i18n (Recap)
In the last part, we covered basic WordPress theme localization in detail. Let’s recap this briefly…
Note » If you’re coding along and haven’t completed all the code from part 2, you may want to clone and install the Github repo from part 2 and use it as your starting project to build on here. If you do so, make sure to go to Appearance > Menus in your admin panel and ensure that your menus are in the locations set in the theme. You may have to create new menus to see the locations.
We currently have our theme scaffolded with an
This is all bread-and-better stuff for WordPress theme development. However, notice the use of the
__() function above.
__() takes a translatable string and a text domain and returns their translation in the current locale, if it’s available. Let’s build on top of this and add a
loop.php file, since we’re currently not showing any of our actual posts on the Newsfeed page
Note » We called our posts page Newsfeed in a previous part.
We’ll add this line to our
<?php get_template_part('loop'); ?>
Then we’ll add the loop partial itself.
This is a standard WordPress loop, of course. With Polylang installed, the loop will automatically load the correct translations for our posts ie. the ones for the current locale.
Also, notice the use of
esc_html_e() above. This function works exactly like
__(), except it escapes its text for HTML use and echoes the translated string instead of returning it.
Note » WordPress provides several
_e()variants of its i18n functions, and they can be convenient when we want to echo out a translated string right away, which is most of the time. We cover many of WordPress’ i18n functions in part 2.
You may have noticed that our copyright text in
footer.php hasn’t been internationalized.
Copyright <?php bloginfo('name'); ?>.
Being the judicious WordPress developers we are, we should make this string translatable. And since we’re pulling in our site name dynamically through WordPress’
bloginfo(), we need a way to interpolate strings into our translations at runtime. This is commonly achieved with PHP’s built-in
printf() functions. Let’s update our
footer.php to use
We use our good old
__() function to register and return the translatable string
"Copyright 2019 %s". Nothing new there. We then pass the translated string that
__() gives us to
printf() as a format string.
printf() understands interpolation placeholders, like
%s, and will replace them with the succeeding parameters it receives. In this case
%s will be replaced with the localized name of our website, which we fetch using
All we have to do at this point is provide a translation for the string
"Copyright 2019 %s" for each locale we support. We need to take care to place the
%s interpolation placeholder appropriately in our translations.
Let’s say we wanted to show the number of published posts on our website. We could simply include something like the following in one of our template files.
<?php echo hmt_published_post_count(); ?> posts.
However, this won’t work when we just have one post, nor is it translatable. Let’s fix both of these things.
Note » Check out the simple code for
hmt_published_post_count()in this article’s companion Github repo. In case you couldn’t guess, by the way, it returns an
This mouthful of code uses WordPress’
_n() function to select between two translatable strings, one for singular and one for plural.
_n() selects between the two forms based on its third integer parameter. The function’s fourth parameter is the usual text domain which ensures the the translations are loaded from the correct PO file
Note » See part 2 of this series for more details on text domains and PO files.
With that code in place, we just need to provide the singular and plural forms for our string when we translate it.
Dealing with More than Two Plural Forms
_n() function seems to only be aware of two forms, singular and plural, some locales have more. Arabic, for example, can have 6 plural forms. Fortunately, the underlying gettext system that
_n() uses has a way to define several plural forms. And
_n() does actually support more than two forms, even though it appears not to.
For example, after defining the above string with
_n() and re-syncing our our template POT file, we may find that it contains the following string definition.
msgid_plural and the array-like
mgstr[i] syntax here. This is designating this string as a plural in the template file.
When our Arabic PO file is generated based on the POT template, it may have a header that looks like this.
This header defines each of Arabic’s 6 plural forms, and a corresponding count integer for each. This kind of header is supplied for us automatically by Loco Translate plugin when we use it for gettext translations.
Note » If all this POT, PO, gettext nonsense looks confusing to you, check out part 2 of this series where we cover this stuff in detail.
In fact, if we’re using Loco Translate we can ignore all these syntactic details. Simply refreshing and saving our template and Arabic files yields an easy-to-use interface for supplying the 6 Arabic plural forms.
All we need to do is fill in each of the 6 forms and save the file. WordPress’
_n() function will pick up the details from the PO file and present the appropriate form from the several we’ve provided.
Date & Time Formatting
Displaying the Localized Post Publish Date & Time
Let’s add the publish date to our posts in our
Luckily, using the built-in WordPress functions
the_time() are enough to display the localized publish date and time for a post within The Loop. We can configure the per-locale format in the Polylang settings under Languages > String translations in the WordPress admin.
Note » Did you notice our use of
<time>tag above? When we pass a format parameter to
the_time()it overrides any formats we’ve set previously in the WordPress admin. And, when used with a format parameter,
the_time()can be used to output both the date and the time.
Displaying an Arbitrary Localized Date & Time
What if we wanted to display any date and make sure that we can localize it? Well, WordPress provides a handy
date_i18n() function that can help with that.
<?php echo date_i18n('l, j F Y g:i A', strtotime('2019/01/15')); ?>
strtotime()returns a Unix timestamp from a date string.
The first parameter we supply to
date_i18n() is a date format string. The second parameter is a Unix timestamp.
date_i18n() will return the date and/or time (depending on our format) for the given locale if the locale specifies it. We can also use the locale we configured in the Polylang settings above if we want to.
<?php echo date_i18n(get_option('date_format'), 1549368000); ?>
get_option('date_format') will pull our localized date format from our settings, so we can use it to keep our date formats DRY (Don’t Repeat Yourself).
Note » Check out the documentation for date_i18n() on the WordPress Codex.
WordPress provides a function for conveniently formatting numbers based on the current locale.
<?php echo number_format_i18n(2388); ?>
number_format() takes a numeric parameter and a second, optional precision parameter.
<?php echo number_format_i18n('1243.1', 2); ?>
The preceding line will output
1,243.10 depending on the current locale, respecting the fact that we want
2 decimal places in the formatted output.
Note » From my tests,
number_format()didn’t output Indian numerals when the current locale was Arabic. Arabic formally uses Indian— ١ ٢ ٣ —numerals. However,
number_format()was outputting Arabic— 1 2 3 —numerals when the current locale was Arabic. Confused yet? Yeah, me too.
Note » Check out the official Codex documentation on number_format().
Providing Context for Translators
There may be cases where we use the same word or phrase in two different places in our UI, and the word has a different meaning in each place. For example, we may use the word “More” for an infinite scroll button. We may also use “More” for a go-to-details button. In a given locale we may want to use different phrases for those two pieces of text. However, if we simply wrap the word in
__(), our gettext system will try to avoid duplication and offer only one instance of the Word to translators.
To help solve this problem gettext gives us an additional context parameter that we can use when registering our translatable strings. WordPress taps into context through its
x functions. Let’s take a look.
_x()is analogous to
_ex()is analogous to
Notice the second parameter in the calls to
_ex() above. This is the context parameter, and it allows the gettext system to provide the two strings separately for translation.
If we sync up our POT and PO files in our Loco Translate plugin, we can see context in action.
We now have two instances of the word “Menu”, each with a descriptive context, and each can be translated separately.
Note » Many WordPress i18n functions have context equivalents. For example, the plural i18n function,
_n()has a context equivalent,
_nx(). You can find all the WordPress i18n functions listed on the WordPress Codex.
Getting the Current Locale
When building internationalized WordPress sites we sometimes need to fork our code based on the current locale or language. WordPress has us covered there. The
get_locale() function will return the current locale code.
<?php echo get_locale(); ?>
The above will display
fr-CA when the current locale is French-Canadian.
Note » Check out the get_locale() function in the WordPress Codex.
Getting the Current Language Code with Polylang
The Polylang plugin used for content translation offers some handy functions. One of these is
pll_current_language(). This function returns just the language code of the current locale.
<?php echo pll_current_language(); ?>
The above will display
fr when the current locale is French-Canadian.
Determining the Current Locale’s Layout Direction
Some locales are written left-to-right and others right-to-left. This often has an impact on our layouts. Arabic and Hebrew web pages, for example, may have a sidebar on the left instead of a Latin-centric right sidebar. To check the direction of the current layout, we can use WordPress’
<?php echo is_rtl() ? 'right-to-left' : 'left-to-right'; ?>
The function returns a boolean and can come in handy when we’re customizing our layouts.
Note » Check out the is_rtl() function in the WordPress Codex.
Writing Locale-Specific CSS
When we used
language_attributes() in our
header.php above, WordPress was kind enough to output handy language attributes into our
<html <?php language_attributes(); ?>>
The above will render to
<html dir="rtl" lang="ar"> when the current locale is Arabic, for example. We can use this to target specific locales and layout directions in our CSS.
In the above example, we display a font for use with our default locale (English in our case), and another font for Arabic. We rely on the HTML
language attribute and CSS attribute selectors for our override. This technique pairs well with conditional, per-locale asset loading, which we’ll go over in a moment.
We can similarly target a specific layout direction.
Here we’re using the standard
end value for the
text-align CSS property. However, since at the time of writing some browsers don’t support
end, we use the older
left values as a fallback.
Conditional, Per-locale Loading of Assets
Let’s add this line in our
header.php, right before our call to
<?php get_template_part('stylesheet-links'); ?>
This is loading in a file that has some conditional logic pertaining to locales.
After loading in our little CSS framework, Pure.css, we check to see if the Polylang
pll_current_language() is available and sqwauk if it’s not. This lets developers know about our dependencies right away.
We then use the
pll_current_language() function to determine the current language and load the appropriate Google Font for it.
Creating a Custom Language Switcher
Sometimes the language switchers that plugins like Polylang provide aren’t enough, and we need something a little more customized to our needs. This isn’t too difficult to accomplish with WordPress’ widget system and the Polylang plugin.
We can create a class to render our Widget.
The meat of our logic is in the overridden
widget() method. Here we use Polylang’s
pll_the_languages() function with a
raw flag to get an array of language names and URLs corresponding to all the translations available for the current page.
Note » See the Polylang function reference for more on
pll_the_languages(), which can actually render a language switcher itself.
We pass the returned languages to a
language-switcher partial for rendering.
We then just need to do the usual registration of our widget in
Finally, we can bring our widget into our theme templates.
That’s all there is to it.
With WordPress, Polylang, and a little custom work, we’ve been able to put together a nice little website for our clients and learn quite a bit of WordPress i18n and gettext along the way. If you want to see this journey from its beginning, check out part 1 and part 2 of this series.
If you’re writing your own custom themes for WordPress, consider using Phrase to translate them. Phrase works with POT, PO, and MO files out of the box, and provides a pro feature set for i18n developers and translators. Phrase can sync to your Github repo to detect when locale files change. It also provides tools for searching for translation strings and proofreading your translations. Phrase even includes collaboration tools so that you can save time as you work with your translators. Check out Phrase’s full feature set, and try it for free for 14 days. You can sign up for a full subscription or cancel at any time.
WordPress allows us to get websites up very quickly and cost-effectively while empowering our clients to administer them. With the knowledge we’ve developed in this series we can internationalize our sites and provide them in different languages and locales while producing custom, branded front-ends. I hope you’ve enjoyed this series, and keep on coding 👩🏽💻👨🏼💻