Grunt & WordPress development IV: Another task for internationalisation

grunt

This is part 4 in a series looking at using Grunt in WordPress plug-in/theme development.

  1. Grunt & WordPress development
  2. Grunt & WordPress development II: Installing Grunt
  3. Grunt & WordPress development III: Tasks for internationalisation
  4. Grunt & WordPress development IV: Another task for internationalisation

WordPress has recently (since 3.5) seen a shift towards a more JavaScript codebase. This shift is still minor (it currently accounts for less than 15%, according to its GitHub repository, in 3.9). But the introduction of Backbone.js and the re-factoring of particular the editor in the WordPress admin (media manager, shortcodes “objects” etc.) are testament to Matt Mullenweg’s comment:

I forgot to mention our biggest architectural change, which is already ongoing: an ever-increasing percentage of our codebase is shifting to Javascript instead of PHP. At some point I expect it to be the vast majority.

Matt Mullenweg, https://news.ycombinator.com/item?id=4744542

However, a JavaScript codebase presents difficulties for WordPress in terms of internationalisation. I ran into this while working on Event Organiser Pro’s 1.7 release. This new release saw the booking form customiser rewritten to use Backbone. With so much JavaScript replacing PHP, there became a massive need to allow strings to be translatable (in a sane way).

Received wisdom is that you should use wp_localize_script() to allow strings in javascript files to be translated. Pippin covers the method excellently here but essentially when you enqueue your script you use wp_localize_script() to make a variable available which contains all you translated strings.

function my_load_scripts() {
    wp_enqueue_script( 'my-script', plugin_dir_url( __FILE__ ) . 'my-script.js');
    wp_localize_script('my-script', 'mynamespace', array(
         'helloworld' => __('Hello World', 'mydomain' )
    ));
}
add_action('wp_enqueue_scripts', 'my_load_scripts');

Then in your JavaScript file, instead of the string “Hello World” you would use mynamespace.helloworld. Although this method is common, there are a couple of things wrong with it when you pit it against some sort of gettext function:

It makes code harder to read. For me when reading code it’s much easier to see the actual text rather than a variable. (More so when reading other people’s code as this helps you link the sourcecode with what you actually see)

<script>
  alert( mynamespace.welcome_msg ); //ok
  alert( mynamespace.gettext( "Welcome to..." ) ); //better
</script>

At the very best its slightly more cryptic and uglier.

It makes it harder to maintain code. – When editing a JavaScript file, a gettext function allows me to edit the string there and then. If I use wp_localize_script(), I need to track down the .php file responsible for that and change it there – and then not forget that that string might have been used elsewhere.

It makes the translator’s job harder. – Hands up anyone who is very poor at providing translators with comments or providing a context when appropriate. Me at least. Regardless, .po files provide a line number so that, if necessary, translators can look up that line to get some sort of context for the string they are translating. It’s not very helpful when that line number points them to a large array in some obscure php file, rather than where the text is being used. Nor is it immediately obvious which JavaScript file(s) are using the string.

You may think that my reasons here are weak and pinnikity… and you might right… But I prefer using gettext-esque function for translating strings.

The problem(s)… (and how Grunt helps solve them)

There a couple of problems with using trying to use a gettext function in a JavaScript file, but they are all easily solved:

  1. There is no native gettext function
  2. How do you get the translations from .po to your JavaScript file
  3. How to get translatable strings from your JavaScript file to your .pot

There is no native gettext function in JavaScript

A very simple solution is to roll your own. Below are four functions which handle translatable strings, plurals and contexts. They all expect the translated strings to be found in mynamespace.locale.

mynamespace.gettext = function( msgid ){
    if( this.locale[msgid] !== undefined ){
        return this.locale[msgid];
    }
    return msgid;
};

mynamespace.ngettext = function( msgid1, msgid2, n ){
    var key = ( n > 1 ? msgid1 + '_plural' : msgid1 );
    if( this.locale[key] !== undefined ){
        return this.locale[key];
    }
    return ( n > 1 ? msgid2 : msgid1 );
};

mynamespace.pgettext = function( ctxt, msgid ){
    if( this.locale[msgid+'_'+ctxt] !== undefined ){
        return this.locale[msgid+'_'+ctxt];
    }
    return msgid;
};

mynamespace.npgettext = function( ctxt, msgid1, msgid2, n ){
    var key = ( n > 1 ? msgid1 + '_' + ctxt + '_plural' : + '_' + ctxt + '_' + msgid1 );
    if( this.locale[key] !== undefined ){
        return this.locale[key];
    }
    return ( n > 1 ? msgid2 : msgid1 );
};

You may have noticed that only one plural form is supported (so a string is either plural or singular), but some languages use more (and some less). There are ways around this, but the limitation is also a result of the Grunt task that’ll we’ll use later. Plural strings and strings with a context expect _plural and _{context} modifiers – I personally think this is less than ideal, but again is forced upon me by my choice of Grunt task. (This is just a start, and I’d like to see these limitations lifted).

Getting translations from .po to your JavaScript file

This is a two-step process:

  1. Use a Grunt task to generate a .json file for each .mo file
  2. Depending on the user’s choice of locale, load that .json file and use wp_localize_script() to make it available in you JavaScipt file.

I went with grunt-i18next-conv to generate the .json files. I found that converting .po to .json included untranslated strings, so I recommend you opt for converting .mo files to .json. (If you need a Grunt task for generating your .mo task, I recommend po2mo task I covered in my last article). It’s this task, by way of the format of the .json file it produces, that imposes some of the limitations already mentioned.

Next you, when enqueuing your JavaScript file, you use wp_localize_script() to ‘attach’ the relevant .json file to it. In the following I expect that the .json files are of the form /languages/mytextdomain-{locale}.json

 $locale = array();
 $file = plugin_dir_path( __FILE__ ) . 'languages/mytextdomain-'.get_locale().'.json';
 if( file_exists( file ) ){
      $locale = json_decode( file_get_contents( $file ), true );    
 }

 wp_localize_script('my-script', 'mynamespace', array(
      'locale' => $locale
 ));

Getting the translatable strings from your JavaScript file to .pot

If you’re using grunt-pot this is easy. Simply include the functions above in the ‘keywords’ option:

  keywords: [ 
        ...
        'namespace.gettext:1',
        'namespace.ngettext:1,2',
        'namespace.pgettext:1c,2',
        'namespace.npgettext:1c,2,3',
        ...
       ]

and ensure the files to search include your JavaScript file.

Limitations

As discussed above there are currently two limitations:

  1. Poor support for plurals other than ‘single form plurals’
  2. Awkward ‘.json’ structure (not a massive issue…)

For the time being, however, and for use in Event Organiser’s booking form customiser, this method was ideal.

Get post content by ID

This post was originally published in November 2012, but was (accidentally) discarded during a migration. I used the wayback machine to retrieve the content and re-post it again.

As you may know the WordPress functions the_content() and get_the_content(), unlike their get_the_title() counterpart, cannot be used outside the loop. Naively ‘in the loop’ means just making sure the $post variable is global and points to the desired post.

So when someone asked why their code wasn’t working when they had used get_the_title() without passing it a post ID or declaring $post as global, the answer was simple.

What they said next threw me.

I find it extremely strange that the_content() works and that my $post variable is indeed an object full of data just like it should.

That is odd. But it turns out that get_the_content() doesn’t really use the $post global. Inspecting the source we see that it actually uses the $pages global. This stores the post’s content (pages as a post can carry across multiple pages). This is actually set up in: setup_postdata().

So while get_the_title() and co use the $post global, the_content() and get_the_content() rely on setup_postdata(). The upshot is that if using these outside the actual ‘Loop’ be sure to do both:

$posts = get_posts(array(...));
if( $posts ){
   foreach( $posts as $post ){
      global $post;
      setup_postdata();

      //Use the template tags

   }
}
wp_reset_postdata();

Note, while setup_postdata() doesn’t set up the $post global, wp_reset_postdata() does reset it.

Then, while reading this post by Tom McFarlin I thought it might sometimes be useful to be able to retrieve post content by passing the post ID, the same way you can with get_the title()

Get The Content by ID

/**
 * Display the post content. Optinally allows post ID to be passed
 * @uses the_content()
 *
 * @param int $id Optional. Post ID.
 * @param string $more_link_text Optional. Content for when there is more text.
 * @param bool $stripteaser Optional. Strip teaser content before the more text. Default is false.
 */
function sh_the_content_by_id( $post_id=0, $more_link_text = null, $stripteaser = false ){
    global $post;
    $post = &get_post($post_id);
    setup_postdata( $post, $more_link_text, $stripteaser );
    the_content();
    wp_reset_postdata( $post );
}

Example usage:

 sh_the_content_by_id(2283);