Creating a WordPress Multilingual Site – Part 3: Best Recipes for Custom Theme Localization

We round out our i18n/l10n WordPress series with a slew of very common and useful recipes to use when localizing custom WordPress themes. We'll also finish the simple theme we've been working on while we're at it. Come along for the ride.

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)
  • Interpolation
  • 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!

Our Goal

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)
  • Plugins
    • 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.

Polylang

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.

Note » If you want to build along with us, or if you’ve completely unfamiliar with WordPress localization, we strongly recommend that you check out part 1 and part 2 of this series.

File Organization

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.

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 index.php, header.php, and footer.php.

index.php

header.php

footer.php

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 index.php file.

Then we’ll add the loop partial itself.

loop.php

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.

Photo by Color Crescent on Unsplash

Note » The Arabic UI strings shown above were translated using the Loco Translate and Polylang plugins. We go over that in part 1 and part 2.

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.

Interpolation

You may have noticed that our copyright text in footer.php hasn’t been internationalized.

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 sprintf() or printf() functions. Let’s update our footer.php to use printf().

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 get_bloginfo('name').

Note » Read more about sprintf() and printf() in the official PHP documentation.

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.

Plurals

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.

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

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

While WordPress’ _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.

Note the 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 loop.php file.

Luckily, using the built-in WordPress functions the_date() and 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 the_time() within the <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.

Check out the official documentation for the_date() and the_time(). Also see the Formatting Date and Time entry in the WordPress Codex for details on the available date formats.

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.

Note » 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.

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.

Number Formatting

WordPress provides a function for conveniently formatting numbers based on the current locale.

number_format() takes a numeric parameter and a second, optional precision parameter.

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.

Note » _x() is analogous to __() and _ex() is analogous to _e().

Notice the second parameter in the calls to _x() and _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.

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.

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’ is_rtl() function.

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

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 right and 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 wp_head():

This is loading in a file that has some conditional logic pertaining to locales.

stylesheet-links.php

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 functions.php.

Finally, we can bring our widget into our theme templates.

That’s all there is to it.

In Closing

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 PhraseApp to translate them. PhraseApp works with POT, PO, and MO files out of the box, and provides a pro feature set for i18n developers and translators. PhraseApp 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. PhraseApp even includes collaboration tools so that you can save time as you work with your translators. Check out PhraseApp’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 👩🏽‍💻👨🏼‍💻

Creating a WordPress Multilingual Site – Part 3: Best Recipes for Custom Theme Localization
Rate this post
Comments