According to the Polygots Make blog, WordPress is used all over the world and in many different languages. To put that in perspective, more than a third of existing WordPress installations are non-English and in his keynote at WordCamp Seattle, core developer Andrew Nacin mentioned that only 5-10% of the world speaks English.
At AudioTheme, we’ve been selling themes for a little more than a year and have already had sales in at least 45 countries, with only around 52% of total sales coming from customers in the United States.
It doesn’t take a math degree to know there’s a huge opportunity for growth.
WordPress itself has rich language capabilities and the core developers are continuously improving the experience for non-English users. With a major push in 4.0, and continuing through the end of the year, it’ll become easier than ever to use WordPress in other languages.
As a developer, it only makes sense to prepare your themes and plugins for use in other languages — a process called internationalization (i18n). In his primer on i18n and localization (l10n), Brian Krogsgard concluded by saying:
It’s time for us to stop ignoring other languages. Internationalization isn’t a feature. If you don’t have properly [internationalized] plugins and themes, you have bugs in your project. That’s all there is to it.
Needless to say, it can be a frustrating experience when WordPress plugins and themes are difficult to work with in other languages because they’re not properly internationalized, but too often, i18n really is an afterthought. Part of that comes down to confusion about what’s involved and it’s partly due to the tedium of setting up and maintaining the tools and environment.
Fortunately, it’s fairly easy to implement i18n and there are tools that can help automate some of the mundane aspects.
i18n Basics
For the most part, preparing a theme consists of a few basic steps:
- Internationalize strings with gettext function calls during development
- Add “Text Domain” and “Domain Path” headers
- Load the language file with a call to
load_theme_textdomain()
- Create an RTL style sheet
- Generate and include a POT file
There is a plethora of resources about getting your projects up to snuff, but i18n is also an ongoing process. Those last two items, in particular, may require continued maintenance.
I’m a big fan of automating workflows to save time; reduce the tedium of repeatable tasks; maintain consistency between team members, operating systems, and tools; and improve quality. At AudioTheme, we’ve worked hard to refine our processes, build tools, and make our products accessible to international audiences, so I’d like to introduce a couple of the tools and methods we’ve built and use to create POT files, generate RTL style sheets and enqueue them.
Generating POT Files
WordPress has a fairly robust suite of PHP tools for handling all sorts of internationalization tasks, namely generating POT files, but they were somewhat hidden before being incorporated into the core development branch in 3.6. Even so, they’re still awkward to use with plugins and themes and require external dependencies like gettext, which can be hard to install on some systems. Getting everyone on a team properly set up and using them consistently can be quite the hassle.
That’s where grunt-wp-i18n helps out. It’s essentially a Grunt plugin wrapper for the PHP-based tools, which is important because they handle some WordPress-specific things like package headers, translator comments and page template names.
We’ve been able to remove many of the dependencies required by the PHP tools, so if you’re already using Grunt, you’re all set — not even WordPress is needed. The Grunt plugin also provides much more control over the data that ends up in the POT file, allowing you to make it even easier for users to localize their themes using tools like Poedit.
Once a project is configured, the POT file can be regenerated every time a new version is released.
It’s already been adopted by plugins like Jetpack, bbPress, BuddyPress and WordPress SEO by Yoast, amongst others. Here’s a Gist demonstrating how to incorporate it into your project.
[gist id=”3868b1c90d7bf404016d” file=”Gruntfile.js” lines=”23-29″]
Wait, that’s it? Yep, the Grunt plugin automatically reads the text domain and other settings from the theme’s headers if they exist, otherwise your set up might require a few more settings. Run grunt makepot
and you’ll have a new POT file.
RTL Style Sheets
Right-to-left (RTL) language support is largely neglected in most themes. Underscores — a fantastic starter theme — advocates creating an rtl.css
style sheet with RTL-specific rules to override default left-to-right rules. While that’s better than nothing, the downsides are that it’s a manual process prone to errors and requires RTL sites to load two style sheets.
After adopting Grunt, WordPress core implemented a task to automate the generation of RTL style sheets using CSSJanus and they haven’t committed any RTL-related CSS patches since last year.
No RTL-specific patches to WordPress since last year thanks to cssjanus. @aaronjorbin #osb14
— Mel Choyce-Dwan (@melchoyce) June 26, 2014
Why not take advantage of the same tools?
CSSJanus converts LTR properties and values in a style sheet to RTL. The project page says it may not always be enough, but it’s a start. We can use the same grunt-cssjanus plugin created by Yoav Farhi that’s used in core.
As I mentioned, rtl.css
is loaded in addition to the main style sheet, so automatically generating it with CSSJanus would mean we’re duplicating every rule in style.css
and overriding them in rtl.css
. Needless to say, that’s even worse for performance.
Instead, we can make the CSSJanus Grunt task save the file as style-rtl.css
, which has the added benefit of grouping the files by name and further clarifying what the new file does.
[gist id=”3868b1c90d7bf404016d” file=”Gruntfile.js” lines=”9-21″]
There Can Be Only One
If you’re not familiar with how rtl.css
is enqueued, WordPress automatically loads various style sheets if they exist (rtl.css, ltr.css, {locale}.css, etc.), but style-rtl.css
isn’t one of those. So how do we enqueue it instead of the default style sheet without resorting to complicated hacks?
Again, we can turn to core for the solution: Use WP_Dependency::add_data()
by way of the wp_style_add_data()
function added in 3.6. It has a few use cases, but in our situation, we want to specify an RTL style sheet that should replace the default style sheet. After enqueueing the main style sheet as usual, make a call to it like this:
[gist id=”3868b1c90d7bf404016d” file=”functions.php” lines=”12-19″]
That lets WordPress know we want to replace style.css
with style-rtl.css
for RTL languages.
Underscores’ RTL style sheet contains a default CSS rule for setting the text direction on the body
element, so we’ll want to add that to style.css
like this:
[gist id=”3868b1c90d7bf404016d” file=”style.css” lines=”14-18″]
The @noflip
comment tells CSSJanus not to convert that rule and the .rtl
class ensures it’s only applied when an RTL language is loaded.
That’s really all there is to it. We now have:
- An automatically generated RTL style sheet
- That’s grouped with
style.css
for better organization and improved clarity - Only one style sheet being enqueued for RTL support
Wrapping Up
Compared to the time it takes to develop a theme, i18n is quick and easy, so don’t limit the theme’s potential to English speakers and don’t let Brian call your code buggy.
I focused on themes in this article, but a similar setup is possible for plugins and even the editor style sheet in themes. It just requires a little more configuration in the Grunt tasks.
There are plenty of other possibilities for automating tasks with Grunt, from injecting text domains during the build process to managing translations with a service like Transifex — let us know what cool things you’re doing with i18n and l10n in your WordPress projects.
[poststatus-author-bio]
Great article. I don’t understand why WordPress is not multilingual by default. Many CMS are already multilingual
Great tutorial Brady! I’ve been using your grunt-wp-i18n task for the last couple months and it is terrific. I think this is the push I needed to also automate RTL.
Thanks for including the Transifex link. Are you using their services to generate translations? I’d be curious how the cost structure works and how easy it is to upload the .pot file and get back the .po/.mo files for different locales.
Thanks Devin! I’ve only tinkered with Transifex, so don’t have much experience with it yet, but I’ve noticed lots of people using it for their projects and they have an API to help automate integration.
It’s free for open source projects and reasonably priced otherwise. If you’d like to dig in more, you might also be interested in what the folks over at WP-Translations are doing.
Just updated a project: https://github.com/devinsays/platform/commit/bb2671d7476ec20979d6be99fdf703fcdb802034. Dead simple! Thanks again for the tutorial!