Woo: You Too Have Woes

Posted by in i18n, Themes

How do I put this?

Dear WooThemes: to me, you have become one of the references on how to run a successful business, based solely on WordPress. Not only do you offer fantastic, pixel-perfect themes, but also excellent plugins, most visible of which are WooCommerce and Sensei. I’ve used your themes and plugins a few times in the past, and only have good things to say about the structure, performance and robustness of the code. What’s more, and keeping in mind that code quality alone does not a perfect WordPress business make, you truly make a difference in what you contribute to core, sponsor almost every WordCamp in sight, and, most important of all, provide world class customer support.

Which is precisely why some theme-related i18n decisions (or maybe a lack of them) baffle me to no end. Allow me to explain: I was looking for a solid framework upon which I could quickly build a website for a family member (read: unpaid), in a language that’s not English. Hold on, this is not where my troubles started; I downloaded my purchased copy of your Canvas theme (version 5.5.7) to my local installation, fiddled with the CSS and Javascript until the site was just the way I liked it, fixed a few things and the site was ready. I didn’t change, or even look at, a single line of PHP. All that went well and quickly.

The only thing left to do was to translate the theme’s strings to Portuguese. This isn’t a daunting task anymore: by having translated so much of WordPress itself, and also many plugins and themes, my PoEdit installation has accumulated a very large translation memory, which normally takes care of a significant amount of the work.

And so, trouble began…

.pot is not only legal, but actually mandatory

The first surprise is that there is no .pot file — Seriously? What’s with all you people? Is it the file extension that scares you off? Let me, if I may, give you a very quick reminder of language file formats: we have

  • .POT file — This file contains the original strings (in English) in your theme (or plugin). POT stands for Portable Object Template; notice that “Template” there? More on that later on…
  • .PO file(s) — One per language, they contain the translation of said originals. Did you notice the missing “T”? Good one, it’s because this file isn’t a template anymore, it contains the actual, translated objects now. So, if I want to translate your (sadly inexistent) theme’s .POT file, I’d create a pt_PT.po file and translate that (still no “T”, get it?).
  • .MO file(s) — Again, one per language. These are machine-readable, binary files that the gettext functions actually use (they don’t care about .POT or .PO files), and are a “compiled” version of the .PO file, discussed before. Again, because (surprisingly) it bears repeating, this is the only file WordPress will read when looking for translations, nothing else.

So the first fix you’d have to implement is to actually supply a proper .POT file with every single theme, named after the theme’s slug, like so: canvas.pot, in the lang folder. You can do this by running makepot.php before every code commit to that theme’s repository, see here how Mike did it. I’m not even going to address the odd presence of a single, lonely en_GB.po file in the /lang folder, for fear of finding out exactly which reasoning led to that…

I did run makepot.php on Canvas, which brings us to the second surprise:

 The textdomain parameter is not an arbitrary decision

Why, oh why, do all your themes load the same ‘woothemes’ textdomain? I understand that there is nothing formally wrong with this, but you could run the risk of seeing mixups in loaded translations. Here’s what the Theme Developers Handbook has to say about it:

You need to use a text domain to denote all text belonging to that theme. The text domain is a unique identifier, which makes sure WordPress can distinguish between all loaded translations. This increases portability and plays better with already existing WordPress tools. The text domain must match the slug of the theme. If your theme’s name My Theme is defined in the style.css or it is contained in a folder called my-theme the domain name should be my-theme. The text domain name must use dashes and not underscores.

So, the second fix would be to correct the textdomain on every gettext call (those __()‘s and _e()‘s and what have you). You know, not the_content( __( 'Continue Reading →', 'woothemes' ) ); but rather the_content( __( 'Continue Reading →', 'canvas' ) );. Everywhere. Tough, I know.

I thought that would be pretty much all, I mean I only needed to check that the call to  load_theme_textdomain was present and needed fixing, and… oh, look, not only is it there, it’s being called twice. What?

Load ALL the textdomains!

function woo_load_textdomain () {
load_theme_textdomain( 'woothemes' ); // But why?
load_theme_textdomain( 'woothemes', get_template_directory() . '/lang' );
if ( function_exists( 'load_child_theme_textdomain' ) ) // It mostly exists, you know?
load_child_theme_textdomain( 'woothemes' ); // You are a grown-up, not a child
} // End woo_load_textdomain()

You do realize that the first call to the function will look for an .MO file in the theme’s directory, don’t you? Why would it find anything there, if you have kindly supplied a /lang folder? The call looks unnecessary to me… By the way, the same applies to load_child_theme_textdomain… It will look in the child theme’s directory, not its /lang folder. I further suspect that load_child_theme_textdomain is a call that should be made by the child theme and not its parent.

Ergo, the third fix would be to simplify the loading of translations with the correct textdomain, like so:

function woo_load_textdomain () {
load_theme_textdomain( 'canvas', get_template_directory() . '/lang' );
} // End woo_load_textdomain()

Two fewer disk reads, times millions (millions, I tell you!) of visits to the website, it should help with the overall performance.

Are we there yet? Not quite.

Sharing is caring

After all this, I ventured a quick glance at your documentation, to see if there was anything I was missing, if, for some reason, there was a compelling argument for having implemented i18n this way. What I found was, to say the least, surprising.

Oh. My. God.

Oh. My. God.

I’m not usually given to internet memes, but this is the one instance where I feel an exception can be made:


Where do I even begin? Let me try:

You’re encouraging every user to start a brand new translation from scratch. This means that, instead of having centralized translation, everyone is told to do it themselves! I get that you could use Codestyling Localization to adapt an existing translation to your own needs, but telling me, the user, the not-translator, “No. Go away. Do it yourself” isn’t exactly gaining you points, you know? Here’s your fourth fix: create or use an environment where strings can be translated publicly and collaboratively. Install GlotPress, use WP Translations, anything… just not this. I’ll even let you in on a little polyglots’ secret: .POT files to us are like honey to bears; the moment we see one that’s not complete, you can be sure that someone is going to rush in and translate it. Maybe not all of it, maybe not to all languages, but it’ll mark it, if available publicly, as active, alive; someone else will invariably pick up the torch.

And then there’s this nugget: let’s assume that the above point isn’t valid and I decide to use Codestyling Localization, after all, as recommended. Did you ever actually try to do it? Did you see how many strings the plugin offers for translation? That’s right, zero. Nix. Nada. Zip.



So, the fifth and final fix would be to stop recommending Codestyling Localization as the tool for translating the theme, and only mention it as a tool for adapting existing translations (heck, even create new ones, assuming the previous fixes have been properly implemented, but only then)

I still need you around

In closing I’d like to just say this:

  • The above considerations probably apply to most, if not all of your themes. I only own a few and can confirm that they all suffer the exact same issues.
  • I haven’t gone deeper into considerations of translations of individual strings (a few odd cases flashed by, but this post is long enough as it is), or even language packs. I strongly recommend you read Otto’s take on it all. He said it much better than I ever could.
  • The purpose of this post is most definitely not to abuse, insult or berate; were I to have a lesser opinion of WooThemes, I wouldn’t have bothered writing it at all. It is precisely because I think highly of you that I’ve written it: because I know you will listen.

Finally, should you want to discuss this further, and ways to make it a process as flawless as your entire operation, I am at your disposal.

Thank you for listening