Internationalizing a WordPress Plugin’s JavaScript with Singular, Plural, or Multiple Forms

WordPress provides some excellent tools for internationalizing themes and plugins, and there are plenty of resources to learn about it. Standard tools allow you to substitute for phrases, plural forms, and make your text available to JavaScript by creating a global object. A non-standard problem is to dynamically add text with JavaScript when there are multiple forms of a phrase WITH placeholders. As if having singular and plural forms wasn’t enough…

Getting the Phrases Right for Translation

Imagine a scenario where your program uses a conditional to figure out which phrase to use. For example, the phrase might be one of “Monthly Payment”, “Bi-Weekly Payment”, or “Weekly Payment”. It’s tempting to hardcode Payment and conditionally select for the other word. Assuming you know something about internationalization already, this is obviously a problem. We could internationalize the words separately, using __( ‘Monthly’, ‘pluginname’ ), __( ‘Bi-Weekly’, ‘pluginname’ ), __( ‘Weekly’, ‘pluginname’ ), and __( ‘Payment’ , ‘pluginname’ ), but this would be a terrible thing to do. We need to internationalize the complete phrases, since the translations will probably have different grammatical structures. So, we need to use __( ‘Monthly Payment’, ‘pluginname’ ), __( ‘Bi-Weekly Payment’ , ‘pluginname’ ), and __( ‘Weekly Payment’ , ‘pluginname’ ) in order to make sure our phrases are translated properly.

A Little About wp_localize_script()

In order to make our translations available to our JavaScript, we need to use the function wp_localize_script( $handle, $name, $data ). $handle is the name of the JavaScript file in which the translations will be used – if the file is named scriptname.js, then $handle will be scriptname, without the file extension. $name is the name that will be given to the JavaScript object that will hold the translations. You will refer to this name whenever you access translated text in your JavaScript. You should make it unique so that it doesn’t conflict with other JavaScript that might be loaded on the page. $data is a PHP associative array. The array keys are converted to property names that you can refer to in your JavaScript. The associated values are the translated strings that will be available in your JavaScript.

You can create the $data array:

$data = array(
	’monthly_payment’ => __( ‘Monthly Payment’, ‘pluginname’ ),
	’biweekly_payment’ => __( ‘Bi-Weekly Payment’, ‘pluginname’ ),
	’weekly_payment’ => __( ‘Weekly Payment’, ‘pluginname’ )
);

Then pass $data to the localization function:

wp_localize_script( ‘script name’, ‘unique_object_name’, $data );

You’ll need to make sure this is called after the main JavaScript file is enqueued with wp_enqueue_script. When the page is loaded, the localization function will create an object that your main script can access. It doesn’t care about readability:

<script type="text/javascript”>
	/* < ![CDATA[ */
	var unique_object_name = {"weekly_payment”:”Weekly Payment”,”biweekly_payment”:”Bi-Weekly Payment”,”monthly_payment”:”Monthly Payment“};
	/* ]]> */
</script>

Using Your Localized Phrases

In your JavaScript code, you can access the different phrases and you’ll probably choose which on to use depending on a conditional. The mostly likely case for this is that the phrase to be displayed depends on some user input. For example, if the user selected “Monthly”, then the “Monthly Payment” phrase needs to be displayed. You would probably set a JavaScript variable based on this selection, and then use it in a switch or if statement.

var phrase, period = document.getElementById( ‘period’ ).value;
switch ( period ) {
	case ‘weekly’:
		phrase = unique_object_name.weekly_payment;
		break;
	case ‘biweekly’:
		phrase = unique_object_nane.biweekly_payment;
		break;
	case ‘monthly’:
	default:
		phrase = unique_object_name.monthly_payment;
		break;
}

Here, I’ve assumed that the value of the selected input is untranslated text.

Adding Placeholders to the JavaScript Phrases

This is where things get a little more complex. If you have a singular and plural form of a phrase, you can use _n( $single, $plural, $number, $domain ) in combination with sprintf( $format, $args ). Generally, $args and $number are known at runtime for PHP, but not necessarily known if it depends on user input through JavaScript. $number needs to be known in order for the _n() function to choose the correct phrase. Since we’re assuming it’s not known, then we can’t use the _n() function.

_n() is a nice shortcut to conditionally choosing between a singular and plural form, but we can just as easily decide that in JavaScript when we actually know what form we need. We need to manually create multiple forms of a phrase and have some way of identifying placeholders in JavaScript. I use the form {placeholder_text}, since the curly braces make it easy for to pick out strings in JavaScript. As a simple example, I’ll use a number of years:

$data = array(
	’one_year’ => __( ‘One Year’, ‘pluginname’ ),
	’many_years’ => sprintf( __( ‘%s Years’, ‘pluginname’ ), {number} )
};

In the singular translation, the phrase is static – “One Year” – and there is need for a placeholder. In the plural version, a translator can construct the correct phrase using the %s placeholder for the number to be inserted. When sprintf fires, {number} will take the place of %s in the phrase. A nicely formatted version of the resulting object looks like this:

var unique_object_name = {
	“one_year”:”One Year”,
	”many_years”:”{number} Years”
};

We now have a singular form and a plural form available to JavaScript. Let’s assume that we need this because a user can enter a number into a form, and that JavaScript needs that number to choose the correct phrase to use.

var phrase, number = document.getElementById( ‘years’ ).value;
if ( number > 1 ) {
	phrase = unique_object_name.many_years;
	phrase = phrase.replace( “{number}”, number);
} else {
	phrase = unique_object_name.one_year;
}

Here I’ve used JavaScript’s .replace() method to find the placeholder and replace it with the number I want to use.

Creating Phrases with Multiple Placeholder

It’s fairly straightforward to extend this to phrases with multiple placeholders. An example would be translating a string that displays a number of years and a number of months, such as “Your amortization period is %1$s years and %2$s months.” The $s in the placeholders indicates that they will be replaced by a string instead of a digit, which is true – PHP will replace them with a placeholder string and JavaScript will replace the placeholder string with digits. Because of multiple singular and plural combinations, there are six phrases that need translation (assuming the year value must be greater than 0):

$data = array(
	’one_year’ => __( ‘Your amortization period is one year.’, ‘pluginname’ ),
	’many_years’ => sprintf( __( ‘Your amortization period is %1$s years’, ‘pluginname’ ), ‘{num_years}’ ),
	’one_year_one_month’ => __( ‘Your amortization period is one year and one month’, ‘pluginname’ ),
	’one_year_many_months’ => sprintf( __( ‘Your amortization period is one year and %s months’, ‘pluginname’ ), ‘{num_months}’ ),
	’many_years_one_month’ => sprintf( __( ‘Your amortization period is %s years and one month’, ‘pluginname’ ), ‘{num_years}’ ),
	’many_years_many_months’ => sprintf( __( ‘Your amortization period is %1$s years and %2$s months’, ‘pluginname’ ), ‘{num_years}’, ‘{num_months}’ ),
);

The JavaScript will then need to check for the number of years and months in order to pick the correct phrase to use, then use the .replace() method to substitute the values for the number of years and number of months. You can extend this concept to create substitutions of whatever type you need, assuming that they remain grammatically correct in other languages. Numbers generally remain the best candidate for JavaScript substitutions within translated phrases.