 Good morning everybody. Thank you. Thank you all for coming to my talk today. Today I'm going to be talking about how to write multi-lingual plugins and themes with WordPress. My name is John P. Block. I am a Lead Web Engineer at TenUp. In case you're wondering, TenUp is hiring. We are always hiring. Tell them I sent you. We're a distributed company. Everybody works from home. We get to work with a lot of large clients, and we try to make web publishing easy, maybe even fun. And I'm not sure if I mentioned this, but we're hiring. You can find me on Twitter at John P. Block, and that's where I'll post these slides after I upload them. And I think WordCamp Miami is going to be distributing those links as well. So before I get started, I want to talk about a couple of assumptions and a couple of definitions. The first assumption that I'm going into this talk with is that you can write PHP. You don't have to be advanced, but you need to know how to write a little bit of PHP. The second assumption is that you are writing plugins or themes for the WordPress.org repositories. If you're not doing that, if you're going to distribute your own plugins or themes, there's a little more homework you'll have to do than what's in this talk because you won't be using the tool chain that WordPress.org gives you. With the definitions in multilingual software, there are basically two words that you really need to understand. The first one is internationalization, and the second one is localization. Internationalization is the process of making your code translation ready. It's giving the potential to be translated to your code. It has nothing to do with actually putting it into another language. You will see internationalization abbreviated as I18N. That's because there are 18 letters between I and N. Pro tip, this works for institutionalization too. I know, right? Localization is the process of putting translated text into an internationalized plugin or theme. It's the realization of that potential to be translated, and localization is abbreviated as L10N because there are 10 letters between L and N. Today I'm really just talking about internationalization. You do not need to know another language to get something out of this talk, but it will certainly help with some of the stuff we're going to be talking about to kind of understand why it's important. So let's talk first about why you should care about this. Why bother internationalizing? Why should you care enough to do that with your plugins and themes? I want to start by making a very simple statement about this. Translating your plugins and themes is an act of empathy. I'm going to say that again because I think it's really important. Internationalization is an act of empathy. More than half of all new WordPress sites are not in English. The last stat I remember seeing on the total WordPress sites in the world, around 65%, only 65% of WordPress sites are in English. That's a staggering number. Having 35% of 25% of the web is not in English. To drive this point home a little bit, I want to go through a little mental exercise. So imagine you've got a website, you hear about this great plugin, all your friends love it. It fills this need that you really have and you're really excited about using it. You download it, put it on your website, activate it, go to the plugin page, and you see this, or this. And then you look in the source code and you see that it isn't even able to be translated. It's not internationalized at all. Even if you wanted to or could translate this, you can't. I know I wouldn't use this plugin. And I would come away from this experience frustrated and probably more than a little upset. And I'd tell people not to use this plugin. So internationalization is an act of empathy. But if that's not enough of a reason for you, you grow your user base just by, you potentially grow your user base just by internationalizing your code. If you get leads from your plugins, if you sell plugins, if you sell premium plugins, that's potentially more money. Or if you love the WordPress spirit, you have the potential to do more good in the world. WordPress is all about democratizing web publishing and internationalization is hand in hand with that. So those are some good reasons to internationalize your code. Now let's talk about how to actually do it. The first topic in internationalizing your code, or the first thing you need to understand is a text domain. A text domain is a namespace or a group for your translations. It needs to be unique across plugins or themes. So two plugins can't have the same text domain, and two themes can't have the same text domain. The best way to ensure that it's unique is just to use the directory name of your plugin, or the plugin or theme slug, because that's already unique to the file system. And on WordPress.org now, that's actually a requirement if you want to use language packs, which I'll talk about later. So the first thing you need to do is load your text domain when your plugin or theme loads. If you're writing a theme, you need to use load theme text domain. The first argument is the text domain, the second argument is the path to where your translations actually are located. You need to run this on the after theme setup hook, and path needs to be an absolute path. It can't be relative to anything. It has to be absolute from the root of the file system. If you're writing a plugin, you use a similar function, load plugin text domain. You have the text domain first, a deprecated argument that you can skip, and then the path very similar to load theme text domain. You need to run this on plugins loaded. You want it loaded as early as possible, and that's the best action to use for that. This is where it gets a little confusing. Path here is relative to the plugins directory, not absolute. So relative to wpcontent slash plugins. So if you have my plugin, it would be my plugin slash languages or wherever your translations are located. So after you've loaded your theme text domain or your plugin text domain, you need to also add a couple of new headers to your header comment. Those are respectively text domain and domain path. The text domain is your text domain, and the path is relative to your actual theme or plugin. I don't make these rules, but once you understand them, they're a lot easier to work with. Okay, and that's what you need to do to get your text domain loaded. That registers your group of translations with WordPress and gets you ready to go with translations. Now you need a couple of basic translation functions. So these three translation functions are sort of the bread and butter of translating. Double underscore, underscore E, and underscore N. So the first two, double underscore and underscore E, you give it the text you want to translate and your text domain. That tells WordPress, look in my group of translations for this text, and if we're using another language, give me that language. So it'll grab that translation of whatever text you're putting in and return it. If it can't find a translation, it'll just return the text you put in. Double underscore will just return the values. Underscore E will echo it directly. So those two are pretty straightforward. Underscore N is for use when you're dealing with some variable number of things. So it's a lot easier to understand with an example. So here you have some count of your drafts, for example. So with underscore N, the first argument is the sentence you would use if it's just one. The second argument is the sentence structure you would use if it's anything else, zero or more than one. So in English, I have one draft, I have two drafts, different sentence structures. Then you give it the actual number you're dealing with and your text domain. It will figure out which sentence to use, look up the right translation, and then use that and then return it. And then you are usually going to want to use formatted strings with PHP to inject that number directly into the string, rather than put it in as a variable in the string. And I'll have a lot more on that later. Another very important thing to keep in mind with underscore N is you need to resist the urge to do this and try to manage the numbers yourself. Some languages have something called a singular plural, where basically it would be like if in English, instead of saying, I have zero drafts, I have to say I have no draft. Underscore N understands how to deal with that situation, so you should use it whenever you have a situation like this. Using this kind of structure where you're trying to check the number yourself is in some languages, it's going to give you the wrong translation. So each of those three basic translation functions has an analog in a group of functions called disambiguation by context. Context is a very, very important thing in translation. The more context you can provide in a translation, or for a translator, the better the translation is going to be. So for example, comment in English can be a noun or a verb. So if you need to translate the word comment, a lot of languages are going to have wildly different words based on whether it's a noun or a verb. Disambiguation by context fills that need. These are the same function signatures as the previous functions, but with an extra argument for context right before the text domain. So for example, with comment, in context you would say basically the part of speech, this is a noun or this is a verb, or you could just say noun. These functions are meant for pretty short descriptions. If it's a part of speech or some very straightforward linguistic structure, use this, but if you need to provide a larger description of what you're trying to do, you can also add comments before the translation function. If you use the word translators with a colon, whatever comment comes after that will show up in the translation template for the translators to use. So in this case you have hello something, while some languages are going to treat that very differently if it's hello John versus hello Miami. So in this case you would want to add a comment saying hey this is a formal name that I'm going to be putting in here, that's the user's first name. So just in case that helps you translate. Moving on to some more advanced translation functions. Number format I18N is one of the most awkward functions to say out loud. PHP has a function called number format where you can pass in an integer or a float and it will format it in a very nice way. It puts the space separators, it'll put the decimal point in. Very helpful, but PHP is going to be configured to use the language your server is using, not the language your website is using. So PHP isn't good enough. WordPress has this function to use the language you're using and languages can say hey I have a different space separator or a different decimal indicator. So if you're in Europe, chances are you're going to want it to look like this. Number format I18N does that. The first argument is the number, it can be an integer or a float or even a string, it'll cast it. And the decimals argument tells WordPress how many decimal places to keep. Sort of hand in hand with that is date I18N. Similar situation, PHP doesn't care what your WordPress site is in. It's going to use the server language. So if you want to use, you know, February 20th, 2016, PHP is just going to put that in English no matter what your site's in. So you give it the date format, you give it an optional time stamp, it'll default to the current time and then you can tell it whether you want it to be in GMT or the configured time zone of your site. So you might pass in a format like this, that would be month, numerical date and four-digit year. In English, it would do that. If you're just using the date function from PHP, that's what it'll do. But using date I18N, that'll actually translate it for you. This doesn't quite work with everything, but for the most part, this will handle all your needs. Just to be safe, you should probably also pass your date formats through double underscore because other locales might want to change the date format. For example, in the U.S. we do month, date, year, and in Europe they do date, month, year. All right. I don't have time to go over all of the advanced functions because they're advanced and they take a lot more time to talk about. But if you want to, here's a little extra credit homework, go home and look these up. These slides will be available. You don't need to scribble these down real quick, but these are also some really helpful functions to know about. So extra credit. All right. So you've gone through your plug-in. You've wrapped all of your text in internationalization functions. Now you're ready for some final steps. The first thing you need to do once you've wrapped everything in a translation function is generate a template file. These are called POT files or POT files. These are basically a dictionary of all of the text you need translated in your plug-in or theme. So the official repositories have web tools to generate this. When you upload your plug-in, you can click a button and it'll create and then download the POT file for you. There are other tools to generate these for you if you don't want to use those or if you're not going to use the repository, but they're a little more complex to talk about in this talk. So I'll have a link to those tools at the end, but I'm not really going to go over how you would use them. Once you've generated your POT file, you need to publish it. If the translators don't know what to translate, you're not going to have any extra languages. The best practice is to just package it in your languages directory of your plug-in or theme. That way, if they're using your plug-in or theme by default, they will have the translation template. And finally, this step is actually optional now. Add translation files to your plug-in or theme. The translators will translate all of your text and they'll give you back a file. It's .mo, which is a machine object. It's basically an optimized file with a key value of text in and text out. They'll give this to you. You can distribute it with your plug-in or you can let WordPress.org distribute it using language packs. Language packs is a service that WordPress.org has in which if someone downloads your plug-in and installs it and they're not using US English as their language, it will check with WordPress.org for a translation file and automatically download it for you. So you don't actually have to package these, but one caveat is language packs only sends completed translations. So if a translation is not 100% complete, it's like 90% complete, it won't get downloaded through language packs. So in that case, you would probably want to package it with your plug-in or theme. All right, now for the gotchas. These are tough. The first one, relatively straightforward, line endings. Just use Unix line endings. Don't use carriage returns. They don't play well with most translation tools. So just avoid carriage returns. Empty strings? How are you going to translate that? Don't do it. It's computational overhead and it doesn't make any sense and it breaks some tools. Formatted strings versus interpolation. This is probably the biggest gotcha in internationalization. So we talked about this a little bit earlier, but I want to give you an example of how this will actually mess up your translation. So say you have this in your plug-in or theme. You live in whatever state they have in their profile. If you do this, if you interpolate that variable right there in the translation text itself, when your pot file gets generated, it's going to put it right into the translation key. Your plug-in does not run when you generate your pot file. It's just a standard dumb text parser that looks at the source code and grabs the strings. The same goes for text domains. Don't be clever. Don't use a constant or a variable for your text domains. Just literal text. That's the only way it'll work because if you use a variable, you will get that literal variable as your text domain and then WordPress won't know where to find your translations because the text domain going into the function at runtime is different. To further complicate this, in this sort of a scenario where you have this in the translation file, when this runs, say, state is Florida, it is no longer looking for this string. It's looking for you live in Florida or Texas or New York. It's not going to find a translation. So even if your translator somehow translated that, it's not going to work. So you don't want to use interpolation in your strings at all. What you want to use is formatted strings. The printf function and the sprintf function in PHP Core are what you want to use. It allows you to put a placeholder in your text and then when you run printf, you give it text and the variable and it will stick that variable into the placeholder's place. In this particular function call here, it's translating the text with the placeholder. Translators will know what to do with placeholders. They're familiar with that. And then once you have it translated, then it sticks the variable in. You should use this for things that shouldn't be translated or will be completely unguessable. If you can guess what it should be, that gets a lot more complex. So that's one gotcha. The next one is word order and position with formatted strings. Say you have two variables that you need to put in. Hi, John, you live in Texas. In some languages, the sentence order, the sentence structure and the order of those two placeholders could change. And suddenly you're saying hi, Texas, you live in John. It doesn't make any sense. So formatted strings have a way to get around that. You can tell it to keep the position of the variable the same by adding this $1 sign and $2 sign so that one always corresponds to the first variable and two always corresponds to the second variable, so that if those switch position, they still get the right value. You don't really need to do this if it's only one placeholder, but for more than one, you have to do this. So that's most of the gotchas, or at least the ones that I can actually keep myself from ranting too long about. Let's talk about some best practices. Well, use decent English style. Slaying abbreviations, especially puns, they do not translate. You will confuse your users and your translators might actually revolt. Keep text together. Obviously, you don't want to hit your translator with a whole chapter of a book, so split it up by paragraph, but going back to the concept of context, you need to provide as much context as possible, and the more text you can keep together, the more context your translators are going to have. They're going to see how the sentences in a paragraph relate to each other, and that will give them more information about the correct way to translate that paragraph. So break it paragraphs, but use formatted strings instead of concatenation. So for example, if you have an image that needs to go between two sentences, don't translate one sentence, then the image, and then translate the other sentence. Use formatted strings to just pop that image into the middle of those two sentences and add a comment for the translators to say, hey, this is an image. It's a wapu image. It's going to be awesome. Just leave it there. And then translate phrases, not words. If you really are using just the one word, like for a menu label, that's fine, but the more you can keep the end translated phrase together, the better. Try to avoid unnecessary HTML. People go back and forth on this one. It's not the end of the world if you have to put HTML into your translation strings, but you can't really assume that your translators will understand HTML or know what to do with it, so you might end up with tags in the wrong place or just completely stripped from the translation. Sometimes it's unavoidable, but generally I tend to... Whenever I run into the situation where I feel like I have to have HTML in my translations, I'm usually over-complicating it, and there's an easier way to get around it. Things like, you know, don't link... If you need to say, you know, click here to buy a subscription. Don't link the word... Yeah, don't put a hyperlink around the word here. Put it around the whole sentence, and then you can just put the HTML out there, translate the sentence, and you're done. But like I said, sometimes it's unavoidable. Make absolutely no assumptions about the translated text. You cannot rely on the text being the same length that it went in as, either character count or byte count. You can't rely on the word order being the same, so if you're translating some text and you have the word order somehow influencing what comes after the text, you can't assume that the word order will be the same or the sentence structure. Don't assume the direction is the same. Right to left languages are a thing. English is left to right. So what that means is if you have, you know, a button that says go to the next page and you have one of those little right-angled quotes to say, you know, next, if a right-to-left language translates that and you don't include that right-angled quote in there, it's going to show up and look like it's saying, you know, next page, but I'm pointing backwards. So include things like that. You can't assume that the direction is going to be the same and the translator for that language will know how to handle it. And on a similar note, punctuation. Don't, let me, you know, if there's a period at the end of your sentence, include that in the translation. Punctuation isn't universal to languages. All right, and finally, avoid translating URLs that don't have a translation. If you're linking to, like, Wikipedia, that's probably safe, but if you're linking to, you know, your personal blog and you only speak one language, use a formatted string to put that in because the translators are going to try to find a translated version of that page and it's not going to work. All right, so let's talk about a few tools. The first one up here, Pig Latin, is a, it's a plugin written by an automatician and I cannot for the life of me pronounce his name, but his handle in most places is in B and he loves bears. Pig Latin will put a filter on all of your translation, it puts a filter on all translation functions where any text that goes through will get programmatically turned into Pig Latin. This makes it incredibly easy to see what you have and have not internationalized. So the text just gets completely destroyed and if you can read it easily, you haven't translated it yet. It's also really helpful if you need to test things like how a translation might break your layout by having a longer string because it does significantly lengthen all of the strings that it puts out. So it's very helpful in a lot of different ways when you're translating your plugins and themes. The next one is the WordPress internationalization tools. These, if you want to generate your own pot file or there are a few other tools in there. If you want to do that, this link is where you will find those tools. You can check them out in Subversion or Git. It's a different URL for Git. So if you're going to roll your own distribution for your plugin, you'll definitely want to check that out. PoEdit is an open source desktop program for managing translations. Probably most of your translators will use to actually translate your text. It might be a good idea to download it, check it out, see what the interface is like and get a sense of what they're going to see. Another nice thing about PoEdit is it can also generate your pot file. So that's another way to generate it if you don't want to use the web tools or if you want to have that pot file generated before you upload it. GlotPress is an open source web application for managing translations. So it's a web application for PoEdit. It's what the rest of your translators will use. And WordPress has its own instance of GlotPress at translate.wordpress.org. Translate WordPress also runs language packs, so when you upload your plugin or theme to the .org repositories, what it's going to do is send over all your text to Translate WordPress, and then anybody who's approved as a translator for your plugin or theme will be able to go in and translate your text for you. And you can request, if you have existing translators or someone wants to volunteer, you can request access as the plugin or theme author for them. If you don't have access, you can still suggest edits as well through Translate WordPress. They just have to go through a gatekeeper to make sure you're not trying to inject cross-site scripting vulnerability. So that is all I've got. I'll take questions. If there are more questions than what I can take right now, I will be in the happiness bar after this to answer any extra questions, but what's happened? So the question is, can you use labels in formatted strings to use an associative array to label your variables? No. I could be wrong about that, but I'm almost positive you can't. There is another set of functions, vprint and vsprint, which will let you do the string with the placeholders and then just an array. But it's not a... I mean, you could use an associative array, I suppose, but I've never tried that, so I don't know if that would cause problems. So the question is, with the internationalization functions, your templates, it looks like they become very verbose and could clutter up your templates. What advice do I have for keeping your templates clean? It's part of the territory. There isn't... You could do something where you have just a dictionary file where you can put all of your text that needs to be translated, store it in an array, and then include that at the beginning and you can reference it throughout your templates. I don't know. That's probably the easiest way to reduce the clutter that this would add to your templates. In practice, I have not... I haven't really found this to add all that much clutter to my templates, but at the same time, I'm a plugin developer, so I'm used to fairly complex code. Yeah, if possible. But if that's not possible, you might try using a dictionary file to have all of your translations in one place and then just include that throughout your code. Any other questions? To change the translations of the plugins or themes or of the content. I have used... So the question is, is there a plugin we recommend for allowing the... or enabling the site administrator to directly edit the translations in the plugins and themes? Not to translate their content, but to translate these strings in your code. I have used a plugin... I used a plugin years ago that did that. I don't remember the name of the plugin. It was on the WordPress.org repository, and I didn't have any problems with it. Generally, I would say, if it's a publicly available plugin, ask them to use Translate WordPress. If it's a private plugin, give them the POT file and ask them to use PoEdit. Yeah. I generally tend to advise against allowing file system edits from a web script. So I would be kind of weary about a plugin that let them edit something on the server directly, especially something that's going to be going into your template and could introduce a security vulnerability. I'm out of time, so... Yeah, there are filters on it. All the translation functions have filters inside them. So you could filter it, but I got to wrap up. I will be at the Happiness Bar after this if anybody else has more questions, and thank you all so much for coming.