Yet another programmer blogging about code

Localising a Gravity Forms add-on

The Gravity Forms Add-on Framework makes the job of creating an add-on really easy. But there’s a couple of problems with letting it load our text domain for us:

  • it only loads our text domain when we initialise the add-on; if that fails, our error messages aren’t localised
  • it loads our text domain after we’ve already set the add-on titles (e.g. used in menus)

To get around those problems, we need to manage loading the text domain ourselves. First, we need to know the order of some important events. There’s lots of things that happen when loading a Gravity Forms add-on, but here’s the events important to us:

  1. the plugins_loaded action triggers Gravity Forms to initialise
  2. Gravity Forms triggers the gform_loaded action, allowing us to register our add-on
  3. Gravity Forms creates an instance of our add-on class
  4. our add-on sets the short and long titles
  5. the init action triggers Gravity Forms to call init() on our add-on
  6. Gravity Forms loads the text domain
  7. Gravity Forms calls init_admin() and sets up admin menus

If our plugin decides not to register our add-on, our text domain isn’t loaded. That might happen because there is a missing prerequisite, for example we need a more recent version of PHP or Gravity Forms, or a specific PHP extension. Our admin notices still need to be translated, however, so we need to load the text domain ourselves.

We could do that in the plugins_loaded action, before Gravity Forms initialises and triggers gform_loaded. But there’s a problem with that: other plugins can change the active locale with a filter hook, so we want to load our text domain after they’ve done that. The earliest we can load our text domain then becomes the init action.

So: we want to load our text domain on the init action, before our add-on is initialised by Gravity Forms. We can do that by setting the priority of our init hook so that it fires before Gravity Forms’ init hook (which is the default, 10).

But notice something else: our add-on will have already set the short and long titles by then, effectively on the plugins_loaded action. That means that our titles won’t be localised. To get around that, we need to set the titles again, after we’ve loaded our text domain but before Gravity Forms calls init() on our add-on.

We also want to stop Gravity Forms loading our text domain a second time. It’s not necessary, and slows down the script execution, so we might as well dispense with it.

Here’s what we need to do, in order:

  1. set the add-on’s short and long titles, knowing that they won’t be localised yet
  2. hook the init action to load our text domain
  3. hook the init action to set the titles again, localised this time, before Gravity Forms calls the add-on init()
  4. stop Gravity Forms loading our text domain

So here’s what we need in our plugin’s main class:

public function addHooks() {
    // use priority 8 to get in before our add-on uses translated text
    add_action('init', array($this, 'loadTextDomain'), 8);

public function loadTextDomain() {
    load_plugin_textdomain('example-plugin', false, plugin_basename(dirname(__FILE__) . '/languages'));

And here’s what we need in our add-on class:

public function __construct() {
    // NB: no localisation yet
    $this->_title       = 'Our Amazing Example';
    $this->_short_title = 'Amazing Example';
    // priority 9 to get in before init_admin()
    add_action('init', array($this, 'lateLocalise'), 9);

* late localisation of strings, after load_plugin_textdomain() has been called
public function lateLocalise() {
    $this->_title       = esc_html_x('Our Amazing Example', 'add-on full title',  'example-plugin');
    $this->_short_title = esc_html_x('Amazing Example',     'add-on short title', 'example-plugin');

* null the add-on framework load of text domain, because we already did it, thanks.
public function load_text_domain() { }

And job is done with the correct locale whether Gravity Forms loads our add-on or not, and without loading our text domain twice!