Localizing WordPress Themes and Plugins

It would be easy to assume that everyone on the internet speaks English. The majority of software is produced in English simply for the reason that doing so gives it a larger potential audience (or, maybe more appropriately, gives it access into the American market). However, you shouldn’t assume this means everyone reads and writes English at the same level, or that non-native speakers wouldn’t want software in their own language. There is a world of speakers outside of English and now is the time to tap into them.

Maltese Falcon

This guide is another in the Inside WordPress series and will show how to prepare WordPress themes and plugins for localization. Not only will this open up your theme or plugin to more people, but it will contribute to the international reach of WordPress, and you’ll receive the very satisfying feeling of seeing your own work in another language. A little extra effort at the start will soon pay off.

Some important points:

  • You do not need to speak multiple languages yourself – localizing a theme or plugin just requires modifications to your code, not actual translation of words
  • The localization process is relatively simple – there is no reason not to localize everything you create!

The guide will be written from a beginners perspective, but will take you through some fairly advanced concepts. As a theme or plugin creator it is assumed that you will be familiar with both HTML and PHP, although detailed explanations will be given at each stage.

If you are a translator you can read the sister article, Translating WordPress, which explains how to take an already prepared theme or plugin and translate it into another language.

Multilingual WordPress

48 thoughts on “Localizing WordPress Themes and Plugins”

  1. This is a great tutorial! After reading it I’m considering to internationalize my blog theme.

    However, the instructions about load_plugin_textdomain are incompatible with Gengo, a Compatibility Page:

    Just like the code that adds widget-capabilities to plugins, calls to load_plugin_textdomain cannot be made immediately. Plugins must call load_plugin_textdomain inside a function that runs on the ‘init’ hook, or at the earliest, the ‘plugins_loaded’ hook. Plugins that do not do this are coded incorrectly, according to advice from WordPress core developers.

  2. Hi Leonardo, I’ve updated the guide to reflect this. While it may cause incompatibility with Gengo, the method is only a suggestion by the WordPress developers and not a requirement (according to the Codex). Still, it is better to show the ideal method!

  3. I managed to create a pot file (this step is missing in the tutorial), translated it and compiled the po file into a mo file. "F jS, Y" is translated as "jS \\d\\e F \\d\\e Y" and I get times like "5th de April de 2007". PHP or WordPress aren’t translating "5th" to "5º" and "April" to "abril". I did set my browser and wp-config.php to pt_BR. What else should I do to get "5º de abril de 2007"?

  4. Leonardo,

    Producing POT files is covered in Translating WordPress Themes & Plugins, this article is just concerned with how to put the appropriate PHP code into a theme or plugin.

    How are you displaying the date in your code? For WordPress to replace the months you must be showing the date through a WordPress function (and not just a PHP function). I’m not sure that PHP or WordPress would convert ‘5th’ to ‘5º’

    %d comment should be correct!

  5. Hello, John!

    Too bad I found out that article only after commenting here (and googling for the solution). It explains the procedure very well, thanks!

    About the time format, I’m using (get_)the_time. If there’s better fitting solution, please let me know! Ideally, I’d use a function which already knows which time formats are used in any locale.

    On %d and %, I don’t know why, but only % worked for me. From my background as a free software translator, I’m used to %d, %s and even {} but never knew about a %.

  6. If you are using that function then WordPress should convert months. Is the locale actually being loaded? Does the locale translate the month strings?

  7. In fact, WordPress does translate month names correctly in my blog, but not in my local test site (so that’s my fault). But the "th" isn’t properly translated, and neither does WordPress seem know the proper date format for each locale. Is there anything I can do about the two last issues?

  8. Hello, that’s me again. I’m having a hard time with the printf, because many template tags display the result directly, instead of returning to printf. In example:

    '.comment_author_link().''); ?>

    In this line, we get comment_author_link then ' Says:'

  9. I’ll place some extra spaces to preserve the code:
    < ?php printf (__ ('%s Says:', 'aqLite'), ''.comment_author_link().'te>'); ? >
    < ?php comment_author_link() <ci te></ci te> Says: ? >

  10. Leonard,

    You were correct about the __ngettext example! The reason is that the % is being passed into a special WordPress function, not printf. The WordPress function requires % and not %d.

    I don’t believe WordPress translates the ordinal suffix at all, and even PHP itself says that the ‘S’ date modifier is for English suffixes only. It looks like you’ll have to invent your own method to work around this!

    Most WordPress functions have two versions – one that echos and one that returns. If you using the data inside printf then you need the return version. In the case of comment_author_link you will need to use get_comment_author_link.

  11. About the suffix, I really can live without it. In Brazil we use it only for the first day of the month, even if (as far as I know) it’s not a formal rule.

    Thank you for your better-than-codex guidance! I’ll try it as soon as I get some more time for the WordPress theme, and then I’ll give you some feedback.

  12. Hello again, Jonh!

    I’m making progress on the theme i18n, and it should be released soon. The almost universal get_ tips fixed most of my issues!

    Unfortunately, there’ no get_comments_rss_link; and get_comment_type doesn’t let us specify the strings for comment, pingback and trackback as comment_type does. I just discovered this functions (in WordPress source code) are quite simple, so I created similar ones to match my needs.

    I also made sprintf and __ngettext work together. I’ll send you example code, as I believe the article could show it. I’ll use the contact form, because the comment form restricts code.

  13. Thank you so much for sharing this. I would be grateful if you can cover also the bi-di languages (Arabic, Urdu, Farsi, Hebrew, etc) and how to flip and reverse the whole layout to be right to left. That would be great and I think it will add a lot to your efforts to reach broader audience.

    Thanks again

  14. John,

    Thanks for the article–it got me started nicely on internationalising my plugins–but I very quickly hit a brick wall. I can’t get xgettext to work on my Windows development machine. It doesn’t seem to recognise PHP files at all. Someone told me the win versions were rather out of date. Do you know of any ways round this road-block? Thanks for your help.

  15. A great tutorial indeed, thank you so much!. I’m using it for internationalization of the excellent TMA theme (The Morning After). Sadly, the tutorial doesn’t seem to explain the ngettext thingie right. In TMA a similar function (comments_popup_link) is often called, but when I change the relevant passage the way you suggest, it gets messed up. Precisely, I change


    When there is one comment or two – all gets translated just fine. But poedit doesn’t see the other possibilities (3 comments, 4 comments, and so on) and where there are three comments or more – no text is visible at all. Any ideas?

  16. BTW, the xhtml code function doesn’t seem to work on your site, contrary to what a tooltip above the comment area says.

  17. Bah! I sorted it out finally. For some strange reason PoEdit couldn’t handle my language settings properly and when I __ngettex the %, it returned errors and added some strange referral to #, php-format file, even though there’s no such file in the theme I’m translating (and returned an error when saving).

    However, I finally decided to do it the hard way, opened the .po file in Notepad ++, then added the following “by hand”

    #: home.php:42
    #: single.php:44
    msgid “% comment”
    msgid_plural “%d comments”
    msgstr[0] “0 komentarz”
    msgstr[1] “% komentarze”
    msgstr[2] “% komentarzy”

    Then I reloaded the file in PoEdit (but without reloading the files from the folder) and saved – without any problems this time. Just don’t trust PoEdit and you’ll be fine 🙂

  18. Great article, was very helpful for localizing a wordpress theme! Unfortunately, there are some typos in the code examples (spaces before brackets), that took some extra time to find and correct to get the code working. You might want to correct those. Peace.

  19. Hello there,
    I need your help on this topic.
    I’ve already translated the whole theme (Arras 1.4) and MOST of the content, when i load the page on the second language, is translated.
    I say most because some words like: “Featured Stories”, “Latest Headlines” and the “Home” menu link remain untouched, which is kinda confusing because i translated them as well, just like all other strings.

    Can anyone help me?
    thx in advance,

  20. I understand how to mark my theme and what the role of the pot, po and mo files is – in theory. In reality, the article says to run “a localization tool” to generate them. Great. Care to tell us what “a tool” is?

    I also read the WordPress Codex pages and they refer to a GNU gettext tool. Had a look at their site http://www.gnu.org/software/gettext/ and it’s clear they went to great lengths to make it as confusing as possible for normal people to get this to work.

    I’m not a GNU/Linux/SVN/computer science expert. I’m just a blogger with basic programming skills, want to localize my blog and move on with my live. Can someone please explain in “idiot English” what tools I need and how to run them? Thanks, I’d really appreciate it!

  21. Hi John, nice article, just the Czech language example is not quite right, it should be:
    0 oken
    1 okno
    2 okna

    in fact, it is:
    0, 5, 6, 7, 8, 9, 10, 11… oken
    1 okno
    2, 3, 4 okna

    btw this is only one of many types of declension (there are 4 types for neutral, 4 types for feminine and 6 types for masculine nouns, each declension has 7 cases for singular and 7 cases for plural from) – FUN! – no wonder most Czech people are unable to use correct Czech.