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.

35 thoughts on “Translating WordPress Plugins and Themes: Don’t Get Clever

  1. Actually, you can pass anything you want as the textdomain and gettext will parse it just fine.

    What won’t work is passing a variable as the text to be translated.

    1. My rebuttal is that this will work when doing once-off manual generation. But if you want to, say, automate the generation across, say, the entire WordPress.org plugin repository, you’re going to need to know the textdomain programmatically.

    2. I’m afraid I don’t understand the distinction between manual generation (i.e. using WP’s own i18-tools) and the automation you mention.

      The way I see it, you would have to make some additional assumptions, such that every plugin has the Textdomain: header set or that the textdomain is identical to the plugin slug.

      Please clarify.

  2. As scribu said, Mark, you are wrong. The domain argument isn’t parsed at all. It is used only when the string is translated to choose the correct .mo file and it’s perfectly OK to be a variable.

    One thing that won’t work with the domain-as-variable approach is the add-textdomain.php. It expects the domain you give to be a literal string and not a PHP expression.

  3. Ugh … I wish you would have posted this last week, but still great information to have. I just need to fix a newly released plugin now. Thanks!

  4. Ben manuel nesil (yani WP kendi I18-araçları kullanarak) ve söz otomasyon arasındaki ayrım anlamıyorum korkuyorum.

    Başlık kümesi veya textdomain eklenti slug aynı I see it: yol, her eklenti Textdomain olduğu gibi bazı ek varsayımları yapmak olurdu.

    Lütfen açıklığa kavuşturun

  5. Mark, I’d love to hear you opinion about “redundant translations”. Like for example I have a plugin and I’d like to use the string “These revisions are identical.” in it. The WordPress core also contains exactly this string, so should I use _e(‘These revisions are identical.’, ‘myplugin’) or _e(‘These revisions are identical.’) instead in my plugin code?

    Another funny case would be: _x(‘j F, Y @ G:i’, ‘revision date format’) …

    Thanks!

  6. What about using defines? Do the non-WP scripts look for ‘blah’ or will BLAH work?

    define(THEDOMAIN,’pluginname’);

    __(‘the string’,THEDOMAIN);

    It still allow you to easily change the text domain if you ever needed to, while cutting down significantly on app overhead associated with $thedomain = ‘pluginname’; __(‘the string’,$thedomain);

    The question is, will an automation shell script care that the value is THEDOMAIN but WordPress processes uses ‘pluginname’?

    If they need to be the same, as in define(pluginame,’pluginame’) then the define gains no advantage. If you ever needed to change the domain ID string for any reason, i.e. define(‘newpluginame’,’newpluginame’) you are stuck with just as much editing as using a string literal.

    Insight?

  7. I don’t do even that. I route all translations through my own function t. It’s t that applies the text domain :p

    $this->t( ‘translate this’ );

Comments are closed.