Translating WordPress Plugins and Themes: Don’t Get Clever

When you use the WordPress translation functions to make your plugin or theme translatable, you pass in a text domain as a second parameter, like so:

<?php _e( 'Some Text String', 'my-plugin-name' ); ?>

This text domain is just a unique string (usually your plugin's WordPress.org repository slug). Well, many plugin developers see code like this:

<?php _e( 'Another Text String', 'my-plugin-name' ); ?>
<?php _e( 'Yet Another Text String', 'my-plugin-name' ); ?>
<?php _e( 'Gosh, So Many Text Strings!', 'my-plugin-name' ); ?>

And they think to themselves "hm, I sure am typing the 'my-plugin-name' string a lot. I'll apply the DRY (Don't Repeat Yourself) principle and throw that string into a variable or a constant!"

Stop! You're being too clever! That won't work!*

See, PHP isn't the only thing that needs to parse out your translatable strings. GNU gettext also needs to parse out the strings in order to provide your blank translation file to your translators. GNU gettext is not a PHP parser. It can't read variables or constants. It only reads strings. So your text domain strings needs to stay hardcoded as actual quoted strings. 'my-plugin-name'.

Happy coding!

* Well, it won't break your plugin, but it could make it harder to be used with automated translation tools. And trust me, you don't want to be manually managing your translation files... we have a better solution coming.

How to write a WordPress plugin that I’ll use

I tend to be very fastidious about the WordPress plugins that I’ll install. I’ll often write my own simple version of a plugin rather than install one from someone else that does a bunch of stuff I don’t need. Here is my philosophy behind writing WordPress plugins, best witnessed through the plugins I’ve written lately, like Markdown on Save, Login Logo, Monitor Pages, and WP Help.

Fewer features as a feature

There are diminishing returns as you add features. That is, the more you add, the more likely you’re adding something that X % of your plugin’s users won’t ever use. Stick to the basics. I’ll often release a “0.1″ version of my plugin with really obvious features missing. When I get a flurry of “You should add Y!” messages, that validates my assumption that Y is necessary. Start with the smallest version that gets the core job done. Iterate as needed.

Code the hell out of it

The best part of starting small is that you can code the hell out of the plugin. Do it right. Make each line of code beautiful. Make sure you’re using WordPress APIs properly, and while you’re at it, add i18n support (WP Help 0.2 shipped with support for Bulgarian, German, Spanish, Mexican Spanish, Macedonian, Dutch, Brazilian Portuguese, and Russian!)

Reduce UI

If you can do without UI, don’t make it. Make every bit of UI prove its necessity. As an example, look at my Login Logo plugin. It has zero UI. It looks for the presence of a file named login-logo.png in the wp-content directory. The rest is "magic." It measures the image, generates appropriate CSS, and gives you an instantly and easily customized login screen. The plugin is invisible. It's completely out of sight, and out of mind. Finally, UI screens are generally where plugin authors make security mistakes. By skipping them, you make it much more likely that your plugin is secure.

Code it for the future

Don't use deprecated APIs. Plan features in future-forward ways. Implement it in such a way that a site that is using the plugin doesn't break if the plugin suddenly goes away. One example of this is my Markdown on Save plugin, which offers per-post Markdown formatting. First, I decided that for performance reasons, I wanted to parse Markdown then the post was updated, not on display. The obvious place to store the generated HTML was in the post_content_filtered column that WordPress provides (but does not use). But then I considered what would happen if someone deactivated the plugin or deleted the plugin. The code that accessed post_content_filtered would not work. Their blog would spit out raw Markdown. And any exports they made would export raw Markdown. What if they were exporting to WordPress.com which doesn't support Markdown? So I decided to store the Markdown in post_content_filtered, and store the generated HTML in post_content. When you edit a Markdown-formatted post, it swaps in the Markdown, so you can edit that. But if you deactivated the plugin, it would fall back to the HTML. So you can feel free to use this plugin and know that if one day you wake up and you hate Markdown, all you have to do is deactivate the plugin and all of your posts are back to HTML.

Secure it

Writing secure WordPress plugins isn't hard. It just takes awareness. Take the time to do your research and code a plugin that will be an asset to its users, not a liability.

Excluding your plugin or theme from update checks

There has been a vigorous discussion going on regarding what data WordPress installs send to WordPress.org when doing update checks. Because WordPress (the software) doesn’t know whether a theme or plugin is listed in the WordPress.org repositories, it has to check them all, and let the repository sort it out. Some have expressed concern that private plugins developed for a single client could contain sensitive information in their headers, like contact information for the developer, etc.

If you, as a plugin or theme developer, would like to exclude one of your plugins or themes from the update array, the following code will do the trick.

For plugins:

function cws_hidden_plugin_12345( $r, $url ) {
	if ( 0 !== strpos( $url, 'http://api.wordpress.org/plugins/update-check' ) )
		return $r; // Not a plugin update request. Bail immediately.
 
	$plugins = unserialize( $r['body']['plugins'] );
	unset( $plugins->plugins[ plugin_basename( __FILE__ ) ] );
	unset( $plugins->active[ array_search( plugin_basename( __FILE__ ), $plugins->active ) ] );
	$r['body']['plugins'] = serialize( $plugins );
	return $r;
}

add_filter( 'http_request_args', 'cws_hidden_plugin_12345', 5, 2 );

Just change the cws_hidden_plugin_12345 string (two instances) to a namespaced function name for your plugin, and you're good to go.

For themes:

function cws_hidden_theme_12345( $r, $url ) {
	if ( 0 !== strpos( $url, 'http://api.wordpress.org/themes/update-check' ) )
		return $r; // Not a theme update request. Bail immediately.

	$themes = unserialize( $r['body']['themes'] );
	unset( $themes[ get_option( 'template' ) ] );
	unset( $themes[ get_option( 'stylesheet' ) ] );
	$r['body']['themes'] = serialize( $themes );
	return $r;
}

add_filter( 'http_request_args', 'cws_hidden_theme_12345', 5, 2 );

Put this in your theme's functions.php Again, just change the cws_hidden_theme_12345 string (two instances) to a namespaced function name for your theme. Do note that this will only hide the ACTIVE theme. This code doesn't get run if the theme isn't active. If you want to hide non-active themes, you'll have to put this code in a plugin and instead of using get_option( 'template' ) or get_option( 'stylesheet' ) you'd hardcode the values for the inactive theme you want to hide.

Just a note: I don't want the comments to turn into another battlefield for the update check debate. I'm putting my code where my mouth is and showing you how you can keep private plugins/themes from contacting WordPress.org if you so wish. Comments about this code are welcomed!

WordPress 2.5.1: Shortcodes updates

WordPress 2.5.1 is out, with a ton of little bug fixes and one important security fix. Go upgrade your 2.5 blogs now.

One area that has changed is shortcodes, those little square bracket shortcuts like [gallery]. In WP 2.5, those were processed before paragraphs were auto-created and quotes were curled. This lead to funky (and invalid) XHTML formatting for block-level shortcodes like the [gallery] one. In 2.5.1, shortcodes are parsed "at 11" -- that is, after paragraphs are wrapped and quotes are curled. Additionally, a shortcode with a buffer line between surrounding content will not be paragraph wrapped.

Example:

[gallery]

That shortcode's output would be untouched by WordPress -- whatever the shortcode outputs is going to the browser. Note that if you're a plugin developer and you want the output of your shortcode to be p-wrapped or have its quotes curled, you'll have to call those functions yourself. At some point in the future we may add more options for shortcode calling, but for now, you're on your own for formatting.

Be sure to check out the excellent documentation for this feature!