If you talk to a man in a language he understands, that goes to his head. If you talk to him in his language, that goes to his heart.
A couple of weeks ago we talked about internationalizing your WordPress website so that you can offer your content in multiple languages. Keep in mind that your website is the first thing most prospects will know about you, so the most intimate you can talk to them, the better. And, as Mandela said, native languages play a key role!
If you’re a WordPress plugin or theme developer, you should consider to translate and internationalize not only your website, but also your plugins and themes. This way, your users will be able to use them in their own language, and your plugin or theme will seamlessly integrate in a Dashboard that’s already in a foreign language. Besides, making your work accessible to everyone by translating it will increase your opportunities, because you’ll be able to attract customers that wouldn’t even consider you if you didn’t offer your plugin or theme in their language.
Unfortunately, this is easier said than done, isn’t it? If you don’t know how to do any of this or if you think it’s too complicated, don’t worry! We have your back 🤗 In this post I’ll share with you the top-6 secrets for translating plugins and themes I wish I had known when I started.
Secret #1. Write Your Code (and Strings) in English
If you’re an English-speaker, then you can skip this step. But if your native language isn’t English and you feel more comfortable writing in other languages than English, then, please, don’t. If you’re creating a new theme or plugin, you should avoid using your own language:
Most developers are used to English. Actually, language constructs and keywords are already in English (think about the keywords
while), so it just makes sense to stick to this language. If you do so, you’ll make the life of other people easier:
- Remember we’re talking about internationalizing your plugin or theme—that’s our goal today—and you won’t be the one translating it to most of the languages. It’s easier to find translators from English to another language than any other combination, so don’t limit your options by using different “source” languages.
- Internationalization functions (we’ll talk about them in just a few minutes) assume you’ll write your strings in English.
The previous example is written in Catalan—a romance language similar to Spanish, French, and Italian. If you know any of these, you might be able to understand most of the code. But if you don’t, you probably don’t have a clue about what that code is supposed to do. But if it were written in English…
then it’d be so much easier for everybody! 😎
Secret #2. Know Your Tools—Internationalization Functions in WordPress
- Developers wrap translatable strings in special
- Special tools parse the source code files and extract the translatable strings into
.pot(Portable Objects Template) files.
- In the WordPress world,
.potfiles are often fed to GlotPress, which is a collaboration tool for translators.
- Translators translate and the result is a
.pofile (i.e., a
.potfile, but with translations inside).
.pofiles are compiled to binary
.mofiles, which give faster access to the strings at run-time.
If you need to remember one thing: translatable strings are parsed from special function calls in the source-code, they are not obtained at run-time.
So, what does it all mean? How does
gettext look like? Well, it’s as easy as follows—if you want to make the following snippet translatable:
You need to write it as follows:
echoing the string directly, we first wrap it within the
__(), one of the multiple functions included in the library. This function simply returns the translated equivalent of input string it’s given. If there isn’t a translation available, the original (English) version is returned. By simply applying this step in all your plugin/theme strings, it’ll be ready for translation.
Once all the strings are wrapped, you need to extract them from your source code and generate a
.pot file. In order to do this, you can use a WordPress tool called
makepot.php (you can download it from this Subversion repository). Then, from a terminal, you simply need to execute the following statement:
Finally, you simply need to tell WordPress that your plugin is actually translatable, so that it uses the proper translations when required (and available). Just edit your plugin‘s main file (usually,
plugin-name/plugin-name.php) so that it looks like this:
Line 15 is the
Text Domain, whose value is the second parameter you add in all
gettext functions, which usually corresponds to the name of the plugin. Line 16 is the
Domain Path—that is, the folder in which WordPress will find your
.pot file. Finally, in line 27 we define a simple function named
plugin_name_i18n that’s responsible of actually loading the translations. If any of these three elements was missing, internationalization wouldn’t work.
Secret #3. Write Translatable Strings
Now that you know how to make a string translatable, it’s time to talk about how to guarantee that said string can be properly translated. If you’re an English speaker and you don’t know any other language, pay special attention, because we’ll discuss important stuff!
More than once, I’ve come around snippets like these:
I know it’s a silly example but, believe me, this happens too often. The problem with the previous snippet is that the translator is supposed to translate fragments of the final string (“The”, “user”, “post”, and “has been deleted”) as if they were independent building blocks, and it’s the developer who then concatenates all blocks and builds the actual final string. Sure, this works in English—the resulting sentences make perfect sense:
- The user has been deleted.
- The post has been deleted.
But this doesn’t work in all languages. For instance, in Spanish nouns have gender—they’re either masculine or feminine. Articles and adjectives are also declined, and they must agree with the noun they complement. So, the previous two sentences in Spanish should look like this:
- El usuario ha sido borrado. (masculine)
- La entrada ha sido borrada. (feminine)
The problem? A translator can only translate the word “The” as either “El” or “La”, but not both at the same time. And the same applies to the last fragment—it’s either “ha sido borrado” or “ha sido borrada“. As a result, following the previous approach, the translated strings will probably look like this:
- El usuario ha sido borrado. 👍
- El entrada ha sido borrado. 😨😱
Another thing that developers usually get wrong is plurals. There’s plenty of ways to internationalize a plural string badly, but I’ll give you just a couple of examples. There’s plenty of developers that use an
else clause for printing the singular and plural versions of their string:
Others try to be more “concise” and use the same root string, dynamically appending a final “s” when required:
Again, this obviously works in English, but when we try to translate those into other languages… well, things go very, very wrong.
How can you fix this and other issues with your translations? Just follow these three simple rules:
1. Write Full Sentences
Never, ever build a sentence by concatenating fragments. Instead, write the full sentence as many times as needed, make each sentence translatable, and let the translator do their work. Sure, this means slightly more work (there’s more sentences to code and translate), but the final result looks far better. If we followed this simple rule, the previous example we discussed would look like this:
and now their translations in Spanish could look like they’re supposed to:
- El usuario ha sido borrado. 👏🎉
- La entrada ha sido borrada. 👏🎉
2. Use Placeholders in Your Sentences
Sometimes, your strings need to contain a variable piece of text. For instance, a welcome text might look like this: “Hello, David” or “Hello, Ruth”. Obviously, you can’t generate all the possible sentences for all the names… so you might think that, in this particular case, concatenating the greeting and the name is the right way to go:
¡Wrong! You can’t do it! Just remember rule #1—write full sentences. In these cases, use
printf‘s placeholders to indicate where the variable text should be located:
Now, a Spanish translator can provide the following translation:
- Hola, %s
printf gets the translated string with a placeholder in the right location, ready to be replaced with the actual value.
3. Use Plural Functions
English has one singular form, which you use when there’s only “one” of “something” (“one post”, “one user”, “one page”), and one plural form you use in all other cases (“zero posts”, “three users”, “500 pages”). And, truth is, most languages I know also follow this rule. So, if we apply rules #1 and #2, we can easily create translatable plural strings as follows:
But, again, that’s not how you do it. There are other languages that have multiple plurals, and each version of the plural is used depending on the “amount of things” we’re counting. For instance, in Polish, plik (file) is used as follows:
- 1 plik
- 2, 3, 4 pliki
- 5-21 plików
- 22-24 pliki
- 25-31 plików
which means that you can’t create an
else block considering just one string for “1 element” and another for “n elements”—you need something more powerful. Luckily,
gettext also includes a function to overcome this issue::
Using a function named
_n(), you can specify the singular and the plural sentence (in English, there’s only two versions, remember?). When this “string” is translated to a different language, the translator will have to offer as many translations as their language requires. That is, Spanish translators only needs to produce two translated strings (“%d fichero” and “%d ficheros”), but a Polish translator needs to produce three (“%d plik”, “%d pliki”, and “%d plików”)
Finally, just one thing that’s worth discussing. If you look at the previous snippet, you’ll see that the variable
$count appears twice. The first time it appears, it’s a parameter of
_n() function, and it simply “tells”
gettext how many elements we have, so that
gettext can load the appropriate translation string. The second time it appears it’s a parameter of
printf, so that the placeholder
%d can be replaced with the actual value.
Secret #4. Use Contexts…
One problem with languages (and especially with English) is polysemous words—that is, a single word might have multiple definitions. For instance, “comment” can be a verb (“My mum always comments on what I’m wearing”) or a noun (“He made negative comments to the press”). But the fact that in English both words look exactly the same doesn’t imply that they also look exactly the same in other languages. How should a translator translate the word? Is it a verb or is it a noun? Clearly, the answer to these questions depends on the context we’re in. Sometimes it’s a noun, sometimes it’s a verb:
As you can see, you can add a third parameter to all
gettext functions, making the context explicit. Based on my own experience, there are three types of context that disambiguate most cases:
command. The users asks the computer to perform a certain action. For instance, “Delete post”, “Create user”, and so on.
user. Sometimes, it’s the other way around—the computer tells the user what they are supposed to do. For instance, “Type in a valid e-mail address” or “Please, try again later”.
text. If it’s neither of the previous two, then it’s just a text informing or notifying of something. For example, “There was an error during…”, “Payments are processed by a secure platform”, or “Akismet has protected your site from 6,861 spam comments already”.
I strongly suggest you use contexts throughout all your code. In my opinion, it’s a good practice to explicitly state the intention of a certain string using one of the aforementioned contexts, for they ensure that translators will perfectly understand what the message is and how to translate them. There might be some cases in which none of these three fully capture your intention or the appropriate meaning of a given word/sentence—if that’s the case, use a different context and make sure it serves your needs.
Secret #5. …and Help Translators (as much as you can)
As I just said, contexts help translators do their job. But sometimes, it’s not about the context—the string they’re supposed to translate is just cryptic:
the previous string makes absolutely no sense, so it’s hard to believe that any translator will get it right. Best case scenario? They won’t translate it and leave it as it is. In these cases, just add a small description to the string so that the translator can understand what’s going on. Descriptions are added by inserting a comment with the
translators: keyword on it:
Thanks to the previous comment, now we know that the string
g:i:sa is a PHP date formatter.
Secret #6. Follow the Rules
Finally, if you want to get involved with translations (either by translating your own plugin to a language you know or contributing to third-party plugins), just go to WordPress.org and join the language team you’re interested in. But before you translate anything, make sure you talk to someone in that community and know how they work. Most translation teams have their own guidelines on how to translate (formal vs informal, verb tenses, specific words, and so on), so you need to be familiar with them and adhere to them.
Remember that all translations are produced and maintained by the community. If we want a coherent and good-looking translation, we all need to follow the same rules—if we don’t, it’ll end up looking quite messy.
Are you interested in translating your plugin or theme? Have you already started? Do you want to contribute and don’t know how? Talk to us in the comments section and we’ll help you!
Featured image by Mark Rasmuson.