Contenu principal

Des appels ajax dans la bonne langue avec WPML

Il y a un peu plus d’un mois, je mettais un article intitulé How to set the WPML language locale of an AJAX call [en] dans ma liste « à lire plus tard ». Cette semaine j’ai dû travailler avec l’extension WPML (certains m’ont d’ailleurs vu pester sur Twitter ^^’), et même si je n’ai pas encore rencontré le soucis (car pas encore besoin d’appels ajax sur le site de mon client), je me suis décidé à lire cet article…

Le soucis ?

Oui, le soucis c’est que lorsque l’on fait un appel ajax sur un site WordPress (vers admin-ajax.php comme il se doit bien sûr) nous sommes alors dans la zone d’administration du site, l’info de la langue se perd donc en route, et c’est donc la langue de l’administration qui est utilisée.
Par exemple : j’utilise sur mon site 3 langues, français (par défaut), anglais et allemand. La langue dans l’administration sera français (à moins d’ajouter une extension spécifique). Sur le site lui-même, en frontend, je vais du côté anglais, un appel ajax quelconque est passé, les infos retournées seront en français.
J’ai remarqué qu’il y a pourtant un système de cookie dans WPML qui permet de garder cette langue en mémoire, mais ça n’a pas l’air d’avoir le moindre effet, je n’ai donc pas creusé plus loin.

Comment résoudre ?

L’article cité propose une solution. Pour être honnête, je ne comprends pas du tout l’idée du rédacteur. Tout ce que j’ai compris c’est que selon la valeur de $_POST['lang'] il va changer la valeur de la langue dans l’administration. Le « problème » c’est que tout ceci est complètement inutile, du moins avec la version actuelle de WPML (3.1.5). En effet, dès lors que $_POST['lang'] ou $_GET['lang'] est envoyé avec l’appel ajax, il n’y a plus rien à faire, WPML se charge de basculer la langue DU FRONTEND dans la bonne langue. Le soucis n’est donc pas de savoir comment traiter cette info $_POST['lang'], mais comment l’envoyer !

OK, quelles solutions s’offrent à nous ?

Le plus simple est d’ajouter directement la langue en paramètre dans nos appels ajax. OK, dans le meilleur des cas j’ai fait moi-même le thème, j’ai donc un total contrôle sur le code, je peux donc ajouter ce paramètre. Mais qu’en est-il des extensions ? Je ne peux pas tout modifier, et puis tout va sauter à la moindre mise à jour.
Le mieux serait donc d’ajouter ce paramètre à TOUS les appels ajax. Jouable ? Oui, à une seule condition : que ces appels soient faits avec jQuery, ce qui a de grandes chances d’être vrai dans la grande majorité des cas àmha.
Voici l’idée, nous allons utiliser jQuery.ajaxPrefilter(). À quoi ça sert ? Cette fonction jQuery permet de modifier les paramètres de tous les appels ajax à la volée, avant qu’ils ne soient utilisés par jQuery.ajax() (à moins que le paramètre global soit passé volontairement à false dans les paramètres de l’appel ajax, ce qui à priori a peu de chances d’arriver).

C’est parti

D’abord il va nous falloir 2 ou 3 lignes de php : nous aurons besoin de l’url vers admin-ajax.php et la valeur de la langue. À vous de voir où vous souhaitez mettre tout le code que nous allons voir, personnellement je vous conseille de créer un plugin à ce but, sinon ce sera dans le fichier functions.php du thème.

01020304050607080910111213141516

add_action( 'init', 'my_script_init' );

function my_script_init() {
	global $sitepress;

	if ( !empty($sitepress) ) {
		wp_enqueue_script( 'jquery' );
		wp_enqueue_script( 'my-script', 'http://example.com/url/de/mon/script.js', array('jquery'), null, true );

		$vars = array(
			'ajaxurl'  => admin_url( 'admin-ajax.php' ),
			'wpmlLang' => $sitepress->get_current_language(),
		);
		wp_localize_script( 'my-script', 'fooL10n', $vars );
	}
}

OK, on a fait quoi là ?
D’abord on vérifie que WPML est bien activé, via la variable globale $sitepress.
On va avoir besoin d’un fichier JavaScript qui va contenir notre code, donc on a utilisé la fonction wp_enqueue_script() pour le mettre dans la « file d’impression ». Selon si vous utilisez un plugin ou votre thème pour ce code, vous aurez besoin de l’une des 3 fonctions suivantes pour l’url du fichier : get_template_directory_uri(), get_stylesheet_directory_uri(), plugin_dir_url(). Ensuite on utilise wp_localize_script() pour imprimer nos variables dans la page.
À noter que WPML imprime déjà une variable JS contenant la langue. Le truc c’est que notre code JavaScript doit être lancé avant les appels ajax, donc le plus tôt possible après avoir mis jQuery dans la page. Donc on va au plus simple : on utilise notre propre variable, comme ça on ne s’embête pas avec des problèmes de timing.

Ensuite on passe au JavaScript, le code qui est sensé se trouver dans le fichier http://example.com/url/de/mon/script.js.
Tout d’abord je vais définir 2 fonctions qui vont me faciliter la vie, et que vous pourrez peut-être réutiliser ailleurs.

0102030405060708091011121314151617181920212223242526272829

// Parse a query string: 'a=bar&b=foo' => {a: "bar", b: "foo"}
if ( typeof window.parseQueryString !== 'function' ) {
	window.parseQueryString = function( queryString ) {
		var params = {}, queries, temp, i, l;
		if ( typeof(queryString) !== 'string' || queryString === '' ) {
			return {};
		}
		queries = queryString.split("&");
		l = queries.length;
		for ( i = 1; i <= l; i++ ) {
			temp = queries[i-1].split('=');
			params[temp[0]] = typeof(temp[1]) === 'string' ? temp[1] : null;
		}
		return params;
	};
}
// Make a query string: {a: "bar", b: "foo"} => 'a=bar&b=foo'
if ( typeof window.makeQueryString !== 'function' ) {
	window.makeQueryString = function( queryObject ) {
		var queryString = '';
		for ( var k in queryObject ) {
			queryString += k + '=' + queryObject[k] + '&';
		}
		if ( queryString.length > 0 ) {
			queryString = queryString.substring( 0, queryString.length - 1 );
		}
		return queryString;
	};
}

La première, parseQueryString(), permet, comme son nom l’indique, de parser une chaine de caractères et d’obtenir un objet de paramètres. La seconde, makeQueryString(), fait le travail inverse.

Maintenant on passe aux choses sérieuses, je lâche le kraken et je détaille ensuite.

31323334353637383940414243444546474849505152535455565758596061626364656667686970

if ( window.fooL10n ) {

	jQuery.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
		// Only for calls to admin-ajax.php
		if ( options.url.indexOf(fooL10n.ajaxurl) === 0 ) {
			var data = options.data ? parseQueryString(options.data) : {},
				url  = options.url.split('?');
			if ( url.length > 1 ) {
				url.shift();
				url = parseQueryString( url.join('&') );
			}
			else {
				url = {};
			}

			// Make sure we don't have the "icl_ajx_action" param.
			if ( url.icl_ajx_action !== undefined || data.icl_ajx_action !== undefined ) {
				return;
			}

			// Maybe there's a "lang" parameter in the url
			if ( typeof(url.lang) === 'string' ) {
				if ( url.lang !== '' ) {
					return;
				}
				if ( options.type.toLowerCase() !== 'get' ) {
					options.url = fooL10n.ajaxurl + '?' + makeQueryString(url);
					options.url = options.url.replace('?lang=', '').replace('&lang=', '');
				}
			}

			// The params are stored in option.data
			if ( !data.lang ) {
				data.lang    = fooL10n.wpmlLang;
				options.data = makeQueryString(data);
			}
		}
	});

}

D’abord avec if ( window.fooL10n ) je vérifie l’existence de notre variable fooL10n, qui théoriquement ne peut que exister vu notre code php. Cette vérification sera surtout utile si vous intégrez notre JavaScript dans votre propre fichier JS (celui de votre thème ou d’un plugin existant).
Ensuite on lance directement jQuery.ajaxPrefilter() : ici, pas question d’utiliser jQuery(document).ready(function($){ ..., il faut lancer ceci le plus tôt possible.
Maintenant, tout ce qui va nous intéresser sera contenu dans options.url pour l’url de la requête et options.data pour les paramètres de la requête. Toutes les 2 sont des chaînes de caractères.

Deuxième test, avec if ( options.url.indexOf(fooL10n.ajaxurl) === 0 ) on vérifie que l’url de la requête ajax pointe bien vers admin-ajax.php. Dans le cas contraire inutile d’ajouter notre paramètre de langue.

Maintenant on peut vraiment commencer le boulot.
Le premier travail consiste à vérifier que le paramètre icl_ajx_action n’est présent nulle part. Il s’agit d’un paramètre utilisé par WPML pour ces propres appels ajax. À priori, mais peut-être que je me trompe, si ce paramètre est présent, on n’a besoin de rien faire.
Ensuite, il nous faut vérifier la présence d’un paramètre « lang » : s’il y en a déjà un, c’est sûrement volontaire, on ne va pas l’écraser.

Pour vérifier la présence de ces 2 paramètres nous allons devoir fouiller dans 2 endroits : les paramètres de l’appel ajax bien sûr, mais aussi son url.
Petite étape, on doit préparer nos variables. C’est toute la première partie jusqu’à la ligne 44 (fermeture du else).
On obtient 2 variables :
data contient options.data mais sous forme d’objet,
url contient les paramètres contenus dans l’url options.url (s’ils existent) mais sous forme d’objet.

On vérifie alors la présence de icl_ajx_action dans les 2 endroits. Si oui, on stoppe.

On vérifie ensuite la présence d’un paramètre lang dans l’url. On vérifie également qu’il n’est pas vide. S’il est bien là, on stoppe. Si le paramètre est présent mais vide, on continue. Au passage, si le type de l’appel ajax n’est pas « GET », alors on supprime ce paramètre lang de l’url afin de ne pas perturber le traitement ultérieur. En effet, lang va être détecté par WPML aussi bien en $_GET qu’en $_POST, donc on pourrait se retrouver avec un $_GET['lang'] vide et un $_POST['lang'] avec la bonne valeur. Mes tests ont montré qu’il était inutile de supprimer ce $_GET['lang'] de l’url, mais voilà, je suis maniaque et je préfère prévenir que guérir.

Dernière étape, on vérifie la présence de lang dans l’objet data. S’il n’y est pas, on l’ajoute enfin dans data, que l’on change en chaîne de caractères, et que l’on range dans options.data.

Conclusion

Ça y est, on est enfin au bout ! Maintenant, tous vos appels ajax en frontend seront accompagnés d’un paramètre lang qui permettra à WPML de savoir où il en est \o/

See ya!