WordPress – Poedit: Translation Secrets

- by Anastis Sourgoutsidis | Posted under: Tutorials

Please note: The following tutorial is mostly about creating language files from scratch. If your theme provides an up-to-date language file, either .po or .pot you can create translation files without much of the following information.

There are a lot of excellent tutorials on the web regarding how to prepare your WordPress theme for translation, as well as how to translate it using Poedit. I am not going to repeat what already has been written, so here is a small list of tutorials on the subject (in case you are too lazy to search for them):

These should be enough to get things started and do things properly, right? Probably, you already thought that instead of typing a text-domain over and over, you will use a variable or a constant, so that you can easily copy-paste from theme to theme, right?

No! You don’t know enough yet. You definitely need to read the following two articles by Otto:

Alright, I’m ready!

No you are not. Now comes my time to speak… write… whatever…

At this point, I’m assuming that you have read and understood what has been written on the articles/tutorials I gave you, and that you are familiar with all WordPress’ localization functions, as well as the other related functions mentioned in the same page.

The reason I’m writing this post, is the failure of every other post I’ve read to properly inform me on how to “configure” Poedit for use with WordPress. I quoted the word configure, because it’s not really Poedit’s configuration, rather than what options Poedit will pass to gettext, but whatever, I’ll be writing Poedit for easiness. I also found troubles when actually translating a website from Greek to English.

Let’s start by a simple rule:

Use English as your base language during development, and only use English with the localization functions.

And here is why: It’s very common that you need a website in [insert random language] (let’s call it Greek) and English. The client (or you) needs the website up and running ASAP of course, so he/she wants the Greek version delivered tomorrow and the English version next week. The person that will be managing the site prefers the back-end to be in English. The client also wants to see progress, so you give him access to the development server. You install WordPress, install a theme, install all necessary plugins, and start copying the content that he gave you.

Of course, clients being clients, start whining about that section of the theme that says “Latest videos” instead of “Πρόσφατα βίντεο”, so you think you’ll just edit the theme’s files, and change that _e(‘Latest videos’, ‘theme’); call to _e(‘Πρόσφατα βίντεο’, ‘theme’);

You end up translating the whole theme to Greek that way, and the client is happy.

Then, you need to work on the English version. And this is when the shit hits the fan.

You have an English WordPress installation, with a hard-coded Greek theme. “No problem” you say, “I’ll use that premium plugin that allows me to translate from within WordPress”. Sure, that’s what I did. Problem is, both gettext and most plugins assume that the files and gettext calls are written in English. So, somehow I ended up with this…

Since I set on the plugin that I want Greek as the primary language, I was able to translate Greek to English, but how could I translate English to English?

I really don’t want to remember that project. From that time on, everything is built is English and then translated to any other language, even if the “other” language will be the only one.

Anyway, this post is really about Poedit and gettext secrets, so… voila!

Plural forms

Some of the tutorials mention that you need to add a plural form. Indeed, each language has it’s own peculiarities on plurals, and gettext needs to know how to handle them. English and Greek use the same plural form:

nplurals=2; plural=(n != 1)

You will understand why plural forms are needed, later in the tutorial.

For those who want a quick explanation, nplurals=2 says that there are two forms, a singular and a plural. plural=(n!=1) is a C-syntax expression, where n is a variable with the number passed (.e.g. from the _n() function) and plural stores the evaluation result of the expression (must be nplurals>plural>=0) i.e. if n=1 then plural=1!=1 so plural=0 (false), and if n=10 then plural=10!=1 so plural=1 (true).

You don’t need to understand what this does (unless you want to), as it requires you to really understand how gettext works. A list of plural forms for other languages that can be used with Poedit can be found here, and if you absolutely need to know what, why and how to design your plural form, you can read this.

In order to add a plural form, open your .po file into Poedit, and from the Catalog menu select Properties…

Then, under the Translation Properties tab, next to the Plural Forms label, enter your plural form.

Alternatively, you can open your .po file using a text editor. You will see on top, quite a few lines that are enclosed with double quotes. Search for the one that starts with “Plural-Forms: and append your form before the \n” (backslash-n-double-quote). If you can’t find it, just add it in a new line:

"Plural-Forms: nplurals=2; plural=(n != 1);\n"

To put it into context, it should look something like this:

"X-Poedit-Basepath: .\n"
"X-Textdomain-Support: yes\n"
"X-Generator: Poedit 1.5.4\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPath-1: ..\n"

Which brings me to the next set of options…

Sources Paths

There are usually two scenarios:

  1. Your theme’s language files are in a sub-directory.
  2. Your theme’s language files are not in a sub-directory.

If your theme’s language files are indeed in a direct sub-directory within the theme’s folder (e.g. your theme’s folder is /wp-content/themes/your-theme/ and your language files are in /wp-content/themes/your-theme/lang/ ), then set your Base path to . (dot) and add two more paths, a . (dot) and a .. (two dots) as such:

Or if you edit your .po file directly make sure the following lines are as such:

"X-Poedit-Basepath: .\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPath-1: ..\n"

You did know that the dot means current directory and the double dot means parent directory, didn’t you? Just so you know, Poedit works recursively, that when you set a directory as a path, it will also search all its sub-directories.

Setting the base path to a dot, means that we instruct Poedit to set the base path to wherever the .po file lives. Then, the other paths (search paths) tells where Poedit should look for gettext calls, relative to the base path. In effect, we are saying: I’m here; get whatever text you find here and in my parent directory, and all our subdirectories.

If you theme’s language file is not in a sub-directory, don’t add the .. (double dot) on the paths, or if you are editing the .po manually, discard the “X-Poedit-SearchPath-1: ..\n”

Now, let’s go to the good part.

Keywords lists, WordPress functions, whaaaaaa?

All the tutorials that I gave you, along with those that I didn’t give you, mention that you should add the functions __() and _e() in the Sources Keywords tab. Some also include functions such as _x() and _n(). Even less mention that when adding _x() and _n() there is a need of some cryptic numbers after them. And only one of them explains what those numbers are.

Take a deep breath…

Poedit (gettext actually) needs to know what PHP functions it must look for and get information from. In reality, gettext knows by itself what to look for in a .php file, but since WordPress uses its own wrapper functions to make our (the programmers’) lives easier, this pre-programmed knowledge is no longer applicable. Hence, we need to let Poedit know what functions to look for and how to handle them.

The complete functions list that it needs to know about are these (as of WordPress v.3.4.2):

__()
_e()
__ngettext()
_n()
__ngettext_noop()
_n_noop()
_x()
_nx()
_nx_noop()
_ex()
esc_attr__()
esc_attr_e()
esc_attr_x()
esc_html__()
esc_html_e()
esc_html_x()
_c()
_nc()

Looking at the Codex documentation will reveal all of these functions, except from _c() and _nc() which are deprecated but I include for completeness. I don’t know why every online resource I found failed to document all these. Unless you are absolutely certain that you only used __() and _e() throughout your code, you should include the full list above.

So, go on, add them in your .po file’s Sources keywords tab.

But wait! The screenshot has different things from what you just gave me. What about those cryptic numbers?

You’re right… here you go.

__
_e
__ngettext:1,2
_n:1,2
__ngettext_noop:1,2
_n_noop:1,2
_c
_nc:4c,1,2
_x:1,2c
_nx:4c,1,2
_nx_noop:4c,1,2
_ex:1,2c
esc_attr__
esc_attr_e
esc_attr_x:1,2c
esc_html__
esc_html_e
esc_html_x:1,2c

Let’s explain a bit, shall we?

_e doesn’t need any numbers, as it only accepts two default parameters: the text to be translated and the domain. Any function that only accepts a text and a domain, doesn’t need the colon and the numbers.

_n needs the :1,2 as 1 says the first parameter is the first plural (singular) and 2 is the second plural.

_x needs the :1,2c as 1 says the first parameter is the first plural (it doesn’t care really if it’s plural or not, it cares it has to translate it) and 2c says the second parameter is a comment, that is, the disambiguation text (context). For all of you that don’t see your disambiguation text (context) in Poedit, this is what is needed. This is the exact reason I started researching and writing this tutorial in the first place, so, I dedicate it to all of you :)

_nx() is really a combination of _n() and _x(), so :4c,1,2 says that the first and second arguments of the function are the plural forms, and the fourth is the disambiguation text.

I couldn’t find any resources so that I can understand the rules governing statements like :4c,1,2 and didn’t had time to experiment, to see if placing the 4c last would make any difference, so, if you did experiment if you did find some documentation about it, let me know in the comments.

Anyway, since adding them one by one from within Poedit is a bit pain in the a*s, you can edit the .po file directly and paste the following:

"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
"_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_ex:1,2c;"
"esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"

Or to better look it within context, with the rest of the declarations I gave you above:

"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
"_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_ex:1,2c;"
"esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
"X-Poedit-Basepath: .\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPath-1: ..\n"

That’s all folks

I hope you enjoyed my post as much as I enjoyed researching for it. If you are more knowledgeable on the subject matter than me, or you spot an error, or you have a suggestion, or a question, please leave a comment.

15 thoughts on “WordPress – Poedit: Translation Secrets

  1. another good, realiable, user friendly tool you can also use for translating po or pot files is POEditor http://poeditor.com/ ,
    the web based localization tool also allows import from xls, xlsx, strings, xml, resx and collaborative work through open projects for volunteers to join and/or assigning collaborators, along with other useful features that makes for a slick, fast translation experience.

  2. Excellent! That _x:1,2c rule was what I was looking for too :)

    But here’s another puzzle I have that you might know how to solve: IS it possible to adapt the keyword list function rules to only include strings from one particular domain?

    Case in point: I use lots of translation strings in a plugin in a similar context as used by WordPress itself so they are already in the main PO database and most likely already translated for most users. So if I use for example __('Category') instead of __('Category','my-plugin-domain') that string gets translated even if there is no translation for my plugin yet.

    As long as the context is similar, this is an advantage. But the downside is that these strings find their way into the plugins PO file too where they are dead weight among the other plugin specific strings, since translating them differently there will have no effect. And too the translator there is no visible difference between ‘valid’ plugin strings and strings that belong to the main WordPress domain…

    Any thoughts? :)

  3. OK, I think I found a way around the problem by using translate() — like translate(‘Category’) instead of __(‘Category’) — for strings that I want to keep out of the plugin translation strings database…

    • Actually, you shouldn’t base your theme/plugin translation on other domains, although it’s technically possible. Always include a domain (and only one domain across each project) in your gettext calls.
      The “foreign” translation files that you are trying to use, might change in the future, and your strings may be untranslatable. It just not too much of a hustle to translate duplicate things first, rather than having to change code or retranslate down the line.
      Hope this helps.

    • Not sure where you got this from, but this is a crappy use of the _nx_noop() function.

      This function is used for registering singular AND plural strings without translating them, however what you pasted is just one form, therefore the _nx_noop() function shouldn’t have been used in this instance. A simple _x(‘All’, ‘comments’) would have suffice.

      For more information on the _nx_noop() function, please read _nx_noop and translate_nooped_plural

      Hope this helps.

  4. Great, great article. Want to add still a couple of things (yes, i18n never ends, does it?)
    On the one hand, both __ngettext and __ngettext_noop are deprecated, as _c and _nc.
    On the other hand, there is the ability to add comments for translators (from the very code), which is a good practice if you want someone else to work in your files. These are a bit trickier, though. To add a comment for translators, you need to add a PHP comment block ( /* comment block */) in the line before the i18 function, and this comment block must start by the tag “translators”. Like this:
    /* translators: This isn’t foo, is a bar! */
    $foobar = _x( ‘foo’, ‘bar’, ‘foobar-domain’ );

    And *also*, you need a minor bit of tweaking with POEdit to make them show. Go to File->Preferences->Parsers tab, and select PHP, then Edit.
    Then, look for Parser command, which will be something like:

    xgettext –language=PHP –add-comments=TRANSLATORS –force-po -o %o %C %K %F

    You need to replace the uppercase TRANSLATORS with lowercase translators, like this:

    xgettext –language=PHP –add-comments=translators –force-po -o %o %C %K %F

    And that’s it, you’re good to go with developer comments for translators.

    • And a great comment too!

      I wasn’t aware that POEdit wouldn’t parse the translator comments, since I never used them myself, so great advice right there!
      I hope you are ok if I update the post with this extra bit of info.

      Perhaps you already know this, but the translators’ comment can also be on the same line as the gettext call, as long as it’s right before it.
      It can be useful for situations like:

      <h1><?php /* translators: This isn’t foo, is a bar! */ _ex( ‘foo’, ‘bar’, ‘foobar-domain’ ); ?></h1>
  5. Thanks for the article!

    Am wondering if it would be possible for POEdit (or any other program) to only read and include strings from a given domain.

    Why i’m asking this is, because i’m using WooCommerce and am overriding the templates in my theme directory. The woocommerce translations are just fine, so i’m using their domain name.

    Now my .po file is a mixture of two domains.

    Is there any way to avoid that and have POEdit read from specific domain?

    Solution i’m using now, is putting all my theme strings into a single file in a separate directory and point POEdit to that specific directory.

    Cheers!
    Richard

    • Hi Richard,
      I’ve been in your place before, and what I did find back then, was nothing. It seems it’s not possible, neither through Poedit, nor xgettext.

      However, I did abandon that “shortcut”, for the one simple reason I mention in a comment above.

      Changing the domain is just a mass find-replace operation in a good IDE, and Poedit’s Translation Memory should be able to help you re-translate easily. (Not sure about the Translation Memory, as I haven’t actually tried that).

      Hope this helps.

  6. Life saver, finally a real tutorial on how to do this, seems everyone is just copying each others tutorial. poedit does not set the right settings on default.. thank you

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>