Posts tagged ‘api’

Olanguagesne of the new features alongside the auto-update feature in WordPress 3.7 is support for “language packs”. More info about these will be coming out eventually, along with new tools for plugin and theme authors to use to manage this system (or to not have to micro-manage it, rather). A lot of this feature is yet to be implemented on WordPress.org, but the core support for it is in WordPress 3.7.

In order to use it most effectively, there’s a few ground rules that you, as a plugin or theme author, need to follow. Fortunately, they’re pretty simple.

Text-domains = the plugin/theme slug

Firstly, for language packs to work, your text-domain must be identical to the plugin or theme’s slug.

What’s a “slug”? Good question. If you examine the URL of your plugin or theme on WordPress.org, you’ll find that it looks like this:

http://wordpress.org/plugins/some-text-here

or

http://wordpress.org/themes/some-text-here

That “some-text-here” part is the slug. It cannot be changed by the plugin or theme author once the entry is created for it in the WordPress.org directory. It is a unique item to plugins/themes, and that’s how WordPress.org will be managing and naming the language files.

Therefore, your “text-domain” must be the same as that slug. In all your translation function calls, the text-domain must be there, it must be a plain string, and it must be identical to the slug of your plugin or theme on WordPress.org.

Headers

For translation to be most effective for your plugin/theme, you need to include a header in it that you may not be including:

Text Domain: put-the-slug-here

This “Text Domain” header is read and used to load your language pack files even when your plugin is not activated. This allows the headers of the plugin (like the description and such) to be translated properly when the plugin is displayed on the Plugins/Themes screen. So your international users will be able to read that text too, before ever using the code.

If you want to include your own translation files instead of using the language pack system, then this still works. The core code will look for the relevant *.mo translation files in the plugin’s directory. If you use a subdirectory, like “/languages”, then you can use a header like the following:

Domain Path: /languages

Note that the Domain Path for plugins defaults to the plugin’s own root directory, but the Domain Path for themes defaults to “/languages” to begin with. If the default works for you, then you do not need to have this header at all.

Also note that if a language file is not found for a particular configuration, then WordPress 3.7 will fall back to using the language pack system to attempt to find it. So if you only include, say, 3 languages, and there are language packs for 4 more, then those 4 more will still work.

Speaking of configuration,

Function calls: load_plugin_textdomain or load_theme_textdomain

Here is how to properly call them, with the Headers you’ll need included for good measure:

If you want to allow for translation MO files in the plugin’s own directory:

Text Domain: plugin-slug
load_plugin_textdomain( 'plugin-slug', false, dirname( plugin_basename( __FILE__ ) ) );

If you want to allow for translation MO files in the plugin’s languages subdirectory:

Text Domain: plugin-slug
Domain Path: /languages
load_plugin_textdomain( 'plugin-slug', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );

If you want to use language packs exclusively (note: WP will still check the base /wp-content/plugins directory for language files, just in case):

Text Domain: plugin-slug
load_plugin_textdomain( 'plugin-slug' );

If you want to allow for translation MO files in the theme’s languages subdirectory:

Text Domain: theme-slug
load_theme_textdomain( 'theme-slug', get_template_directory() . '/languages' );

If you want to allow for translation MO files in the theme’s “lang” directory:

Text Domain: theme-slug
Domain Path: /lang
load_theme_textdomain( 'theme-slug', get_template_directory() . '/lang' );

If you want to use language packs exclusively (note: WP will still check the theme’s own directory for language files, just in case):

Text Domain: theme-slug
load_theme_textdomain( 'theme-slug' );

Important:

  • Any calls to load_plugin_textdomain should be in a function attached to the “plugins_loaded” action hook.
  • Any calls to load_theme_textdomain should be in a function attached to the “after_setup_theme” action hook.

How it will work

Eventually, WordPress.org will have a way to allow plugin/theme authors to upload translation files. Or, it will have a way to allow users to submit their translations to them via translate.wordpress.org… Regardless, the relevant MO files will be made on some basis, and the files will be made available to WordPress users through the normal plugin/theme update process. The auto-update system will automatically download these MO files into the /wp-content/languages directory. There will be plugins and themes subdirectories under that to hold these files.

The files will be named “slug-locale.mo”, where slug is the plugin or theme’s slug, and the locale is the relevant locale information about the language (like “en_US” for example). When load_plugin/theme_textdomain is called, WordPress will look in the specified place for the relevant MO file, and if it does not find it, then it falls back to looking in the /wp-content/languages folder for it, on that named basis. If it finds it, it loads it up and uses it.

This gives the plugin or theme authors the ability to continue to manage their translations themselves, as they’ve always done, or use the new language pack system and let WordPress.org manage it for you. The language pack system has a number of advantages:

  • Users only download the languages they actually need, instead of all of them. Your plugin is smaller, the download is faster.
  • New translations can be approved and pushed as updates independently of the plugin or theme. No more need to bump the version just to get new translations to users.
  • Translations can be handled much easier, or ignored by the author entirely. Communities can (eventually) do their own translations through translate.wordpress.org.

Things like that. These all rely on plugins and themes doing translations a certain and specific way, along with properly internationalizing their code for translation.

Obviously, any code not doing this sort of thing won’t get these benefits. Well, we can’t fix everything at once. But hopefully, the most common and popular ones will do this (or already are), and they can be integrated into the system quickly and easily.

Some tools to help

If you’re a plugin or theme author, do yourself a favor and use your SVN client to get a copy of this repository:

http://develop.svn.wordpress.org/trunk/

This is the core develop repository for WordPress. It comes with the WordPress trunk code (in /src) but it also has some important tools you’ll need in the /tools/i18n directory. Note that to use these tools, you need the *entire* checkout, not just the tools. The tools make calls back into the WordPress core code to do some of the work, so the whole /trunk directory needs to be available there.

Also, those tools are managed by the core team. So keep them to date by doing an svn update every once in a while too.

Here’s one of those tools: makepot.php

And here’s how you run it:

> php makepot.php wp-plugin /path/to/my/plugin-dir plugin-slug.pot

This will scan your plugin’s directory and create a POT file for you to give to translators or include with your plugin. Theme authors, same deal, just replace “wp-plugin” with “wp-theme”.

Here’s another tool: add-textdomain.php

It will read in a file and add a proper text-domain to all translation function calls it finds. To use it, you can do this:

> php add-textdomain.php plugin-slug /path/to/a/file.php > newfile.php

The newfile.php will be identical, but all the translation calls will be fixed up and have the plugin-slug in there as intended.

The tool outputs the new file on standard output, which I redirected into “newfile.php” as you can see above. This is so that it is non-destructive by default. If you’re confident, and have backups of the files just in case, you can use it in-place like so:

> php add-textdomain -i plugin-slug /path/to/a/file.php

The original file will be replaced with the modified version. Use this at your own risk. I’m paranoid, I prefer to make a new file for manual comparison. πŸ˜‰

This tool will go through and add the text-domain to any calls where you might have left it off. I have done this many times. Force of habit, or I just forget to do it, etc.

More Info

And if you’re having a hard time with making your text translatable in the code, I have a couple other posts on that topic as well. See them too.

So go forth, plugin and theme authors. Start fixing up that code. Many of you may have nothing to fix. Some of you may just need a header change. But it’s worth giving it a once over anyway. It certainly would be very nice if, as the new features begin to be added to WordPress.org, then your code was all ready and set to take immediate advantage of it, wouldn’t it? πŸ™‚

Shortlink:

So I added the new Tweet button to Simple Twitter Connect yesterday, and was going through the documentation they provided for it. One thing I noticed was a mention of the “count API”.

Now, Twitter says that the count API is private, but come on.. Seriously? You’re sending count information to every Tweet button being displayed anywhere, how private can that really be?

Turns out it’s not private at all. It’s just hidden.

To get the tweet count for any URL, simply do a GET for this:

http://urls.api.twitter.com/1/urls/count.json?url=URL-TO-GET-COUNT-FOR

What you’ll get back will look like this:

twttr.receiveCount({"count":123,"url":"URL-TO-GET-COUNT-FOR"})

Some of you may recognize that as a JSON callback. Basically, they’re using part of their @anywhere system to receive the count and update the button accordingly. Not surprising, the whole tweet button is built on top of the anywhere code. But it’s kinda useless to us in this format, since we need to be able to define our callback appropriately instead of using theirs. No worries, just add a callback parameter yourself:

http://urls.api.twitter.com/1/urls/count.json?url=URL-TO-GET-COUNT-FOR&callback=bob

bob({"count":123,"url":"URL-TO-GET-COUNT-FOR"})

Obviously, they didn’t release this information because they’re concerned about scaling, and probably rightfully so. Still, if you want that count, you can get it. Just don’t abuse it.

Shortlink:

(Note to future readers: This is fixed in WordPress 3.3, so this article is now out-of-date. See http://core.trac.wordpress.org/changeset/18541Β for the patch.)

I was not aware that other people didn’t know about this until recently, but since it seems to be little known, I thought I’d write a post on the topic.

Chain LinksIn WordPress, you should never start the custom permalink string with any of these %postname%, %category%, %tag%, or %author%. (Unless you know what you’re doing, of course. πŸ™‚ )

Meaning that “%category%/%postname% ” is a bad custom permalink string. So is just “%postname%” for that matter.

Why? Well, it has to do with how the WordPress Rewrite system works.

Rewriting Explained

See, when you request a URL from a WordPress site, WordPress gets the URL and then has to parse it to determine what it is that you’re actually asking for.

It does this by using a series of rules that are built whenever you add new content to WordPress. Generally the list of rules is pretty small, but there are specific cases that can cause it to balloon way out of control.

Normal Rules

Let’s say you’re using a normal permalink string, like my preferred “%year%/%postname%”. The rules that are generated will look like this:

/robots.txt (for the privacy settings)
/feed/* (for normal feeds of any kind)
/comments/* (for comments feeds)
/search/* (pretty url for searches, not often used)
/category/%category% (category archives)
/tag/%tag% (tag archives)
/author/%author% (author archives)
/%year%/%month%/%day% (with each of those after year being optional)
/%year%/%postname% (this is the permalink string you define)
/%pagename% (any Page)

The way that system works is that it compares the URL it has to each of those in turn, from the top to the bottom. When one of them fits, then WordPress knows what to display and how to do it.

Note that the order I listed those in is is significant. Each one from the top down is less specific than the previous one.Β  For example, “robots.txt” matches only that, while “/feed/*” matches anything starting with /feed/. And so on down the list. The %postname%, %category%, %tag%, %author%, and %pagename% will match any string, but the other WordPress % ones will only match numeric fields. Like %year% is always a number.

Notice that the last one is %pagename%. This basically matches everything, because %pagename% can be anything at all. Even hierarchical pages like /plugins/whatever/something will cause this to match. It’s the fall-through position. And then, if that page doesn’t actually exist on your site, then this causes the query to trigger the 404 condition internally, which causes your theme’s 404.php to load up.

Pretty simple and straightforward, really.

Problem Rules

The problem comes in when you try to use a non-number for the beginning of your permalink string. Let’s examine those last two rules closer:

/%year%/%postname% (this is the permalink string you define)
/%pagename% (any Page)

What if you used “%category%/%postname%” for your custom permalink string? Now those last two rules are these:

/%category%/%postname% (this is the permalink string you define)
/%pagename% (any Page)

That violates our main rule, doesn’t it? That each one should be less specific than the one above it? Because %category% can match any string too, just like the %pagename% can… With this set of rules, there’s no way to view any of the Pages. Not good.

So, WordPress detects this condition and works around it. Internally, this sets a flag called “use_verbose_page_rules”, and that triggers the rewrite rebuild to make this set of rules instead:

/robots.txt (for the privacy settings)
/AAA
/BBB
/CCC (one of these for each of your Pages)
/feed/* (for normal feeds of any kind)
/comments/* (for comments feeds)
/search/* (pretty url for searches, not often used)
/category/%category% (category archives)
/tag/%tag% (tag archives)
/author/%author% (author archives)
/%year%/%month%/%day% (with each of those after year being optional)
/%category%/%postname% (this is the permalink string you define)

Now we have basically the same set of rules, except for those new ones at the top. Every Page now gets its own very specific rule, and this satisfies our main condition once again.

Pages

But what if you have a lot of Pages? I once read a post by a person who had over 50,000 Pages on his site. That is a special case obviously, but consider our lookup system. We’re going through these rules one at a time. With our first method, our rule list was only 10 rules, maximum. With this new method, you add a rule for every single Page you make. Going through 50,000 rules takes a lot longer than going through 10. And even just building that list of rules can take a long time.

Basically you’ve created a performance issue. Your Pages now won’t scale to unlimited numbers. Your site’s speed is linearly dependent on the number of Pages you have.

This is a bad thing.

Conclusion

Firstly, it’s really not any better for SEO to have the category in there, or to have just the postname there by itself. And anybody who tells you differently is wrong. If you disagree with me, then no, I’m not interested in arguing this point with you; you’re just wrong, period, end of discussion.

Secondly, shorter links are great and all, but hell, why not use a real shortlink? WordPress 3.0 now has a ShortLink API that defaults to using ?p=number links on your own domain. These will actually work for any WordPress site, even ye back unto WordPress 2.5. WordPress 3.0 just makes it nicer and easier to use these with the Shortlink API (as well as allowing plugins to make this automagically use services like wp.me or bit.ly). So use that instead.

The conclusion is, in general, just don’t do it. Leave a number, or something static, at the beginning of your permalink string and you’ll never have any sort of problems. But if you really MUST do this sort of thing, then keep your number of Pages low. Don’t try it when you have more than, say, 30-50 Pages.

Addendum

Okay, so I actually simplified things for this post. It’s actually worse than this, as verbose page rules can add much more than one rule per page, as this post demonstrates (he gets 11 per extra page!).

Shortlink:

Originally posted here: http://ottodestruct.com/blog/2009/wordpress-settings-api-tutorial/

When writing the Simple Facebook Connect plugin, I investigated how the Settings API worked. It’s relatively new to WordPress (introduced in version 2.7), and many things I read said that it was much easier to use.

It is much easier to use in that it makes things nice and secure almost automatically for you. No confusion about nonces or anything along those lines. However, it’s slightly more difficult to use in that there’s very little good documentation for it. Especially for the most common case: Making your own settings page.

So, here is my little documentation attempt.

Continue reading ‘WordPress Settings API Tutorial’ »

Shortlink: