| name | orbit-compat-polylang |
| description | Polylang compatibility audit — pll_register_string, pll_current_language, custom-post-type translation, language switcher hooks, REST + WP-CLI integration. Polylang has free + Pro versions; covers both. Use when the user says "Polylang compat", "Polylang translate", "WPML alternative", or coexisting with Polylang. |
🪐 orbit-compat-polylang — Polylang compatibility
Polylang is the open-source WPML alternative (~700K sites). Different API than WPML — both must be supported separately, since users pick one based on price/preference.
What this skill checks
1. Register translatable strings
add_action( 'init', function() {
if ( function_exists( 'pll_register_string' ) ) {
pll_register_string( 'welcome_text', 'Welcome to my plugin', 'My Plugin', false );
}
} );
$translated = function_exists( 'pll__' )
? pll__( 'Welcome to my plugin' )
: 'Welcome to my plugin';
2. Current language
$lang = function_exists( 'pll_current_language' ) ? pll_current_language() : 'en';
3. Translate post ID
$translated_id = function_exists( 'pll_get_post' )
? pll_get_post( $post_id, 'fr' )
: $post_id;
4. Custom post type registration
Whitepaper intent: Polylang reads CPT settings — your plugin should register CPTs with 'show_in_rest' => true AND let Polylang's settings UI mark them translatable.
register_post_type( 'my_plugin_post', [
'public' => true,
'show_in_rest' => true,
// Polylang admin → Languages → Settings → Custom post types and Taxonomies
// user enables "translate" for this CPT
] );
5. Detect active
if ( function_exists( 'pll_register_string' ) || class_exists( 'Polylang' ) ) {
}
6. URL handling (3 URL modes like WPML)
$url_fr = function_exists( 'pll_home_url' ) ? pll_home_url( 'fr' ) : home_url();
7. Pro features (paid)
Polylang Pro adds:
- Strings translation export/import
- Slug translation
- Duplicate post in another language
If your plugin generates URLs from slugs, it must handle Pro's slug translation.
Output
# Polylang Compat — my-plugin
✓ Detects Polylang via class_exists check
✓ pll_register_string called for admin notices
⚠ Hard-coded strings in includes/templates/welcome.php — not registered
✓ pll_current_language used for current-language detection
❌ URL builder doesn't use pll_home_url — generates EN URLs in FR context
Pair with
/orbit-compat-wpml — alt translation plugin (different API)
/orbit-i18n — base i18n
7. Language-aware custom endpoints (REST, rewrite, AJAX)
The bug that ships when this check is missing: plugin exposes a custom URL like /{slug}.md or /wp-json/myplugin/v1/foo. User has Polylang active with EN/ES translations linked. Request comes in for the ES translation — endpoint returns the EN content because the handler never consulted Polylang.
Required checks
For every custom REST route, rewrite rule, or AJAX handler that returns post content:
7.1 Resolve current language
$lang = function_exists( 'pll_current_language' )
? pll_current_language()
: ( isset( $_GET['lang'] ) ? sanitize_key( wp_unslash( $_GET['lang'] ) ) : 'en' );
Sources of truth, in order:
pll_current_language() (Polylang's resolved language)
?lang=xx query parameter
Accept-Language request header (parse via WP::parse_request() or manual)
7.2 Translate the target post to the requested language
$translated_id = function_exists( 'pll_get_post' )
? pll_get_post( $post_id, $lang )
: $post_id;
if ( ! $translated_id ) {
}
7.3 Set response headers correctly
header( 'Content-Type: text/markdown; charset=utf-8' );
header( 'Content-Language: ' . $lang );
header( 'Vary: Accept-Language' );
Detection grep
grep -rn 'register_rest_route' --include='*.php' . | grep -v 'tests/'
grep -rn 'add_rewrite_rule\|template_redirect' --include='*.php' . | grep -v 'tests/'
grep -rn "add_action.*wp_ajax" --include='*.php' . | grep -v 'tests/'
For each result, inspect the handler body. If the handler returns content tied to a $post_id or a slug, search the handler body for pll_current_language\|pll_get_post\|wpml_current_language\|wpml_object_id\|Accept-Language\|?lang=. If none present → multilingual gap. High severity when Polylang/WPML is in the plugin's compatibility matrix.
Caching gotcha
If the endpoint is cacheable (sets Cache-Control: public, max-age=...), the cache key MUST include language — either via distinct URL per language (/es/{slug}.md) or via Vary: Accept-Language (which most CDNs ignore by default; Cloudflare APO ignores Vary entirely). Document the chosen strategy in the endpoint header.
Sources & Evergreen References
Canonical docs
Last reviewed
- 2026-04-29 (original) · 2026-06-02 (added §7 language-aware custom endpoints)