WordPress – Poedit: Translation Secrets
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):
- Translating your theme
- Translating WordPress plugins & themes
- Translation with POEdit – Internationalize / Localize WP Themes Guide – Part 1 – Part 2 – Part 3
- How to translate a WordPress theme
- Create .pot or .po file using Poedit
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!
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…
There are usually two scenarios:
- Your theme’s language files are in a sub-directory.
- 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.