GET /wp-json/wp/v2/posts?lang=de : voilà la requête qu’on veut voir fonctionner, alors qu’aucune extension de traduction n’est installée sur ce projet. Le client a construit sa propre table de correspondance entre objets et langues (souvent via une taxonomie personnalisée ou une colonne de métadonnées), et il attend que l’API REST native de WordPress sache en tenir compte sans qu’on ait à réécrire les points de terminaison.
C’est un cas fréquent sur les projets headless : le contenu multilingue existe déjà côté base de données, mais l’API expose tout, toutes langues confondues, dans le même flux. La bonne nouvelle, c’est que WordPress prévoit exactement ce genre d’extension via ses filtres de requête REST, sans qu’il soit nécessaire de dupliquer les routes ou d’écrire un contrôleur maison.
Le point d’entrée : rest_<post_type>_query
Chaque type de contenu interrogeable via l’API REST déclenche un filtre nommé rest_{$post_type}_query juste avant que la requête WP_Query ne soit exécutée. Il reçoit deux arguments : le tableau d’arguments de requête déjà préparé par le contrôleur, et l’objet WP_REST_Request qui contient les paramètres bruts de l’URL, y compris ceux qu’on ajoute soi-même.
add_filter( 'rest_post_query', function ( $args, $request ) {
$lang = $request->get_param( 'lang' );
if ( $lang ) {
$args['meta_query'][] = [
'key' => '_site_lang',
'value' => sanitize_key( $lang ),
];
}
return $args;
}, 10, 2 );
Ce filtre suppose qu’une métadonnée _site_lang est déjà renseignée sur chaque article — c’est la partie « architecture maison » qui précède ce tutoriel. Si la langue est plutôt stockée via une taxonomie, on remplace le meta_query par un tax_query classique, la mécanique du filtre reste identique.

Déclarer le paramètre pour qu’il soit accepté
Sans déclaration explicite, WP_REST_Request::get_param() renverra bien la valeur si elle est présente dans l’URL — l’API REST ne rejette pas les paramètres inconnus par défaut. Mais pour profiter de la validation automatique (et éviter qu’un visiteur envoie ?lang=%3Cscript%3E sans contrôle), il vaut mieux enregistrer le paramètre via rest_{$post_type}_collection_params.
add_filter( 'rest_post_collection_params', function ( $params ) {
$params['lang'] = [
'description' => 'Filtrer par code de langue interne',
'type' => 'string',
'validate_callback' => function ( $value ) {
return is_string( $value ) && preg_match( '/^[a-z]{2}(-[a-z]{2})?$/i', $value );
},
];
return $params;
} );
Cette validation rejette proprement toute valeur qui ne ressemble pas à un code de langue (fr, en-us…), avec une réponse 400 explicite plutôt qu’un filtrage silencieux qui renverrait une liste vide sans prévenir personne.
Exposer la langue dans la réponse
Un client headless a aussi besoin de savoir, pour chaque objet reçu, dans quelle langue il est rédigé — sinon il doit deviner. On ajoute un champ personnalisé via register_rest_field() :
add_action( 'rest_api_init', function () {
register_rest_field( 'post', 'site_lang', [
'get_callback' => function ( $post ) {
return get_post_meta( $post['id'], '_site_lang', true ) ?: 'fr';
},
] );
} );
Ce champ apparaît alors dans chaque objet de la réponse JSON, à côté de title et content. Côté frontend (Next.js, Nuxt, ou un simple fetch), il devient trivial de vérifier la cohérence entre la langue demandée et la langue réellement servie.
Gérer les types de contenu personnalisés
Si le projet expose des types personnalisés (une bibliothèque de recettes, un catalogue d’événements), il faut répéter l’enregistrement du filtre pour chaque post_type concerné, ou généraliser avec une boucle sur get_post_types( [ 'show_in_rest' => true ] ) plutôt que de multiplier les add_filter à la main :
- Lister les types exposés à l’API avec
get_post_types()et le paramètreshow_in_rest - Enregistrer dynamiquement
rest_{$post_type}_querypour chacun - Centraliser la logique de résolution de langue dans une fonction unique, appelée par tous les filtres
Un piège fréquent : le cache d’objet
Si le projet utilise un cache de requêtes REST (une passerelle, un CDN, ou simplement rest_pre_serve_request détourné), il faut s’assurer que la clé de cache intègre le paramètre lang. Sinon, la première requête en français fige la réponse pour toutes les langues suivantes, jusqu’à expiration du cache — un bug particulièrement difficile à reproduire en local, où le cache est souvent désactivé.
Sur un projet headless, toute variable qui change la réponse doit systématiquement changer la clé de cache associée : la langue n’échappe pas à cette règle, même quand on l’ajoute après coup.
En résumé
Filtrer du contenu multilingue via l’API REST sans extension tierce tient en trois briques : un filtre rest_{$post_type}_query pour restreindre la requête, une déclaration de paramètre pour la valider, et un champ exposé pour que le client sache ce qu’il reçoit. Cette approche reste entièrement portable : elle survivrait même à un changement ultérieur de stratégie de traduction, puisqu’elle ne dépend que des mécanismes natifs de WordPress.