Use the coupon code WORDPRESS and save 30% OFF! Buy Now

WordPress – Poedit: Translation Secrets

Last Updated On
WordPress – Poedit: Translation Secrets WordPress template

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.

27 responses to “WordPress – Poedit: Translation Secrets”

  1. summ3r says:

    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. Alberto says:

    This is a great article, thanks a lot! I was going crazy trying to set up the _n function.

  3. RavanH says:

    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? :)

  4. RavanH says:

    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…

    • Anastis Sourgoutsidis says:

      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.

  5. Luka Peharda says:

    Wow, great article. And I thought that I knew how to translate WordPress themes/plugins :-)

  6. Acoho says:

    _nx_noop(‘All’, ‘All’, ‘comments’), // singular not used . Translation?

    • Anastis Sourgoutsidis says:

      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.

  7. Xavier Faraudo i Gener says:

    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.

    • Anastis Sourgoutsidis says:

      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:
      [php]<h1><?php /* translators: This isn’t foo, is a bar! */ _ex( ‘foo’, ‘bar’, ‘foobar-domain’ ); ?></h1>[/php]

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

    • Anastis Sourgoutsidis says:

      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.

      • Oliver says:

        Hi there!

        First of all, TOTALLY AWESOME article! Many thanks!

        As for the multiple translation domains extractions, you can use an online tool: http://www.icanlocalize.com/tools/php_scanner

        However, as far as I know, this only works with basic localization functions __() and _e().

        Regards,

        Oliver

        • Anastis Sourgoutsidis says:

          Hi there,
          thanks for the link.
          I tried submitting a single php file with calls to __(), _e(), _x() and _ex() and it totally ignored the last two. I didn’t bother checking any of the other localization functions, as it already proved to me that I’ll never use it.

  9. Many thanks bro, before i was translate to my lang, i don’t know about this.

  10. Tony says:

    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

  11. Radoslaw says:

    A freeware tool for translating po/pot files: PO Fast Translate -> http://communicator.pl/other_programs.html

  12. dekaelm says:

    i must translate the script : “Διαβάστε περισσότερα” when i click English to “read more”. Same to the “αναζήτηση” to “Search”.
    Can you help me please?
    Thanks for your time
    dekaelm

    • Anastis Sourgoutsidis says:

      Well, this depends on how you theme is built and how it’s translated (e.g. through plugins, .po/.mo files, etc).
      Whoever set the site up for you, will be able to help.

  13. Pedro Góes says:

    Very good and instructive text, thank you!

  14. Bnimbhal says:

    This is a great article, thanks a lot! I was going crazy trying to set up the _n function.

  15. Caue Rego says:

    Do you happen to know what could make `_n()` properly choose the plural but not translating while `__(_n())` will work?

    Also, as for the `_nx()` you probably know this by now, but just for the sake of it, [the documentation](https://codex.wordpress.org/Function_Reference/_nx) is pretty clear to me and confirms what you said there.

    • Anastis Sourgoutsidis says:

      tl;dr
      Assuming everything is set-up properly (e.g. plural form expression), I’d imagine you have multiple instances of the same text in your files, but only some of them are translated, or the domain does not match.

      Long answer:

      Consider these calls:
      [php]
      <?php
      __( ‘One’, ‘domain’ );
      _e( ‘One’, ‘domain’ );
      _x( ‘One’, ‘context’, ‘domain’ );
      __( ‘Many’, ‘domain’ );
      _e( ‘Many’, ‘domain’ );
      _x( ‘Many’, ‘context’, ‘domain’ );
      _n( ‘One’, ‘Many’, 2, ‘domain’ );
      ?>
      [/php]

      They produce the following output inside the .po(t) file (generated by the latest version of Poedit at the time of writing).

      [code]
      #: functions.php:2 functions.php:3 functions.php:8
      msgid "One"
      msgid_plural "Many"
      msgstr[0] ""
      msgstr[1] ""

      #: functions.php:4
      msgctxt "context"
      msgid "One"
      msgstr ""

      #: functions.php:5 functions.php:6
      msgid "Many"
      msgstr ""

      #: functions.php:7
      msgctxt "context"
      msgid "Many"
      msgstr ""
      [/code]

      If you follow the msgid and msgid_plural strings, you’ll make some observations. For example, you have 2 occurrences of “One” but 3 occurrences of “Many”, although in the PHP code above, each word appears exactly four times.
      Obviously, the calls with a context create a new entry in the .pot file to differentiate from the calls without a context.
      What is really interesting however, is that the first block that contains the _n() strings is grouped together with the first __() and _e() calls of lines 2-3, but there is separate block generated for the respective calls of “Many” in lines 5-6.
      So, if there is any chance you (or the translator) skipped the translation of msgid_plural “Many” what exists in the .po file is probably this:
      [code]
      #: functions.php:2 functions.php:3 functions.php:8
      msgid "One"
      msgid_plural "Many"
      msgstr[0] "Translated One"
      msgstr[1] ""

      #: functions.php:5 functions.php:6
      msgid "Many"
      msgstr "Translated Many"


      [/code]
      Now, if you called:
      [php]_n( ‘One’, ‘Many’, 1, ‘domain’ );[/php]
      would return Translated One

      But if you called:
      [php]_n( ‘One’, ‘Many’, 2, ‘domain’ );[/php]
      would return Many

      Assuming “Many” has been translated in the third block:
      [php]
      // Returns ‘Many’
      _n( ‘One’, ‘Many’, 2, ‘domain’ );

      // _n() returns ‘Many’.
      // __() gets the value ‘Many’ as its first parameter, finds the third block (which is a match), and returns ‘Translated Many’
      __( _n( ‘One’, ‘Many’, 2, ‘domain’ ), ‘domain’ );
      [/php]
      would seem to return the proper Translated Many, but as far as the whole gettext/WordPress thing is concerned, it’s a completely different string.

      Hope this helps :)

  16. Carlos Mendoza says:

    Thanks a lot! You just helped me with the _nx function.
    Thanks for the article!

  17. Adam Bergman says:

    Great walkthrough! The list of keywords saved my day!

  18. Sioban says:

    Thank you so much for your wonderful article ^-^ !

  19. Thanks a lot! You just helped me with the _nx function.
    Thanks for the article!

Leave a Reply

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

Get access to all WordPress themes & plugins

24/7 Support Included. Join 115,000+ satisfied customers.

Pricing & Sign Up

30-day money-back guarantee. Not satisfied? Your money back, no questions asked.

Back to top