Polylang compatibility audit
Skill Lonsdale201/wp-agent-skills/polylang/polylang-compatibility-audit
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill polylang-compatibility-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Audit whether a WordPress plugin or classic theme is compatible with Polylang, Polylang Pro, and Polylang for WooCommerce. Use when asked whether code is Polylang-compatible, why a shortcode/option/page/product does not translate, or when code contains stored post/page/product/term IDs, get_permalink/home_url calls, pll_* calls, pll_register_string, PLL_Translate_Option, custom post types/taxonomies, custom tables, REST lang parameters, shortcode/block output, translated slugs, ACF/sync logic, WooCommerce products/orders/cart/Store API, emails, PDFs, cron jobs, exports, or webhooks.
SKILL.md
16.5 KB, ~3.6k tokens by cl100k_base, as published. Nobody here has run it
Polylang Compatibility Audit
Use this skill to decide whether an existing plugin or classic theme is safe on a Polylang multilingual site, and to produce concrete fixes when it is not.
Polylang compatibility is not the same as WPML compatibility. Polylang stores translations as separate posts/terms connected by language and translation taxonomies, exposes public pll_* APIs, translates registered strings/options, and relies on runtime language context. Do not invent a wpml-config.xml equivalent for Polylang.
Verdicts
Use one of these audit verdicts:
| Verdict | Meaning |
|---|---|
| Compatible | Static UI text, dynamic strings/options, stored object IDs, URLs, queries, REST/headless, WooCommerce paths, and async output all resolve in the intended language or are deliberately language-neutral snapshots. |
| Partially compatible | Core pages work, but one or more surfaces leak the default/source language, mixed-language IDs, untranslated options, wrong REST results, or wrong async output. |
| Not Polylang-compatible | The plugin/theme assumes one language globally, stores translated content as a single unregistered value, queries direct SQL without a language model, or writes Polylang private taxonomy relationships manually. |
| Not testable | The required Polylang stack, languages, translated content, Woo integration, or execution path is missing, so only static risks can be reported. |
Audit Workflow
1. Identify the stack
Check which Polylang layer is relevant before judging the code:
$has_polylang = function_exists( 'pll_current_language' ) || defined( 'POLYLANG_VERSION' );
$has_pro = defined( 'POLYLANG_PRO' ) && POLYLANG_PRO;
$has_pll_wc = defined( 'PLLWC_VERSION' ) || class_exists( 'PLLWC_Data_Store' );
Also record configured languages/default language, URL mode, translated post types/taxonomies, media translation state, WooCommerce HPOS/checkout-blocks/Store API/custom-order-type involvement, and whether another multilingual plugin is active.
pll_current_language() can return false in admin screens, all-language filters, CLI, cron, and other non-frontend contexts. Any code outside a normal frontend request must choose an explicit language from the object being rendered, the order/customer/request language, a saved setting, or pll_default_language().
2. Separate static text from dynamic strings
Static strings in PHP/JS templates stay normal WordPress i18n:
esc_html_e( 'Settings saved.', 'my-plugin' );
Audit for a valid text domain, loaded translations, .pot coverage, JS translations, and escaping. Do not replace all gettext calls with pll__().
Admin-entered strings are different. Any option, field label, legal text, email body, popup copy, shortcode heading, or builder setting that the merchant edits in WordPress must be registered and translated through Polylang:
add_action( 'admin_init', static function (): void {
if ( function_exists( 'pll_register_string' ) ) {
pll_register_string( 'myplugin_cta_label', get_option( 'myplugin_cta_label', '' ), 'My Plugin' );
}
} );
$label = get_option( 'myplugin_cta_label', '' );
echo esc_html( function_exists( 'pll__' ) ? pll__( $label ) : $label );
pll_register_string() is an admin-side registration API in Polylang core. Register strings where the admin runtime can see them; frontend-only registration is a common reason strings never appear in Languages > Translations.
3. Audit stored object IDs and URLs
Find every stored post, page, term, product, variation, attachment, menu, form, template, and category ID. For live frontend display, translate IDs before use:
$page_id = (int) get_option( 'myplugin_landing_page_id' );
if ( function_exists( 'pll_get_post' ) ) {
$translated_id = pll_get_post( $page_id, pll_current_language() ?: pll_default_language() );
$page_id = $translated_id ?: $page_id;
}
$url = get_permalink( $page_id );
Important Polylang details:
pll_get_post()andpll_get_term()return0when no translation is found; do not only check forfalse.- Do not concatenate language slugs into URLs. Use
pll_home_url( $lang ), translated permalinks, or the translated object ID. get_permalink( $source_id ),is_page( $source_id ), menu IDs, and template IDs are not automatically corrected when the ID came from your own option or custom table.- For historical snapshots such as invoices, order line names, audit logs, and sent emails, keeping the original language can be correct. Document the intent.
4. Check custom post types and taxonomies
If the plugin registers content that authors translate, verify it opts in:
add_filter( 'pll_get_post_types', static function ( array $types, bool $is_settings ): array {
$types['book'] = 'book';
return $types;
}, 10, 2 );
add_filter( 'pll_get_taxonomies', static function ( array $taxonomies, bool $is_settings ): array {
$taxonomies['genre'] = 'genre';
return $taxonomies;
}, 10, 2 );
Do not mark operational records as translatable just because they are posts. Logs, queue jobs, API tokens, payment records, and internal caches should usually stay language-neutral or store a language snapshot.
Audit timing: these filters must run early enough for Polylang's model and settings UI. Late filters added after Polylang has built its object-type list may not affect admin behavior, REST fields, or query filtering.
5. Check object creation, import, and sync
Programmatic creation must assign language and translation relationships with public APIs:
$en_id = pll_insert_post( array( 'post_type' => 'book', 'post_title' => 'Coffee Guide' ), 'en' );
$fr_id = pll_insert_post( array( 'post_type' => 'book', 'post_title' => 'Guide du cafe' ), 'fr' );
pll_save_post_translations( array(
'en' => $en_id,
'fr' => $fr_id,
) );
For existing posts/terms use pll_set_post_language(), pll_set_term_language(), pll_save_post_translations(), and pll_save_term_translations().
Never write directly to Polylang's private language or translation taxonomies. Direct wp_set_object_terms() calls against internal language taxonomies can corrupt language state, miss caches, and bypass future changes.
6. Audit queries and custom storage
Normal WP_Query, taxonomy queries, and archive requests are filtered by Polylang when a current language exists. Custom code still needs review:
WP_Query,get_posts(),get_terms(), and REST collections should pass the intendedlangwhen running outside normal frontend context.- Use
lang => ''only when the code intentionally needs all languages, such as admin reports, sync screens, migrations, and cross-language selectors. - Direct SQL against
wp_posts,wp_terms, lookup tables, or custom tables is not automatically language-filtered. - A custom table that references content needs a language model: stored translated object IDs, a
langcolumn, or a documented snapshot policy. - Cache keys must vary by language for rendered fragments, REST responses, transient HTML, and AJAX payloads.
7. Audit REST, AJAX, and headless flows
For REST/headless use the polylang-rest-headless skill for implementation details. At audit level, check:
- clients pass and preserve
lang; - custom REST controllers read
pll_current_language()after Polylang REST request handling; - custom collection routes are registered through
pll_filtered_rest_routesonly when they truly support language filtering; - custom controllers that Polylang cannot infer identify their object type with
pll_rest_request_object_type; - Pro
langandtranslationsfields are not stripped by custom response formatting; - AJAX endpoints, admin-ajax handlers, and Store API extensions do not rely on page URL context only;
- HTTP caches and CDN rules vary by language URL, language cookie,
langparameter, or domain mode as appropriate.
8. Audit Polylang Pro assumptions
Guard all Pro-only behavior:
if ( defined( 'POLYLANG_PRO' ) && POLYLANG_PRO ) {
// Translated slugs, shared slugs, Pro REST fields, sync modules, ACF integration.
}
Common Pro-sensitive areas:
- translated or shared slugs mean slugs are not a stable global identifier;
- sync modules may copy selected custom fields, terms, and metas between translations;
- ACF field groups, field labels, relationship fields, and option pages may need explicit translation or ID mapping;
- block and shortcode content may be parsed/synced by Pro modules, but plugin-defined nested data still needs audit.
If a plugin stores IDs inside serialized block attributes, ACF fields, page-builder JSON, or shortcode attributes, check whether Pro sync translates them. If not, add a plugin-level mapping routine.
9. Audit WooCommerce separately
For WooCommerce code, use the polylang-wc-compatibility skill. At audit level, verify product/variation language via PLLWC_Data_Store::load( 'product_language' ), Woo-aware order language, HPOS wc_get_orders() queries with lang, Store API calls preserving lang, cart item data translating embedded IDs, language-aware SKU/global unique ID checks, and explicit language sources for emails, invoices, shipping documents, and webhooks.
Do not treat Woo products and orders as ordinary posts once Polylang for WooCommerce is active. Polylang WC owns Woo-specific product, variation, order, REST, Store API, stock, and SKU behavior.
10. Audit async and non-page output
The highest-risk leaks are not normal pages; they are cron tasks, Action Scheduler jobs, webhooks, admin exports, PDFs, emails, invoice generation, feed builders, and CLI commands.
For each non-page flow, answer which language should be used, where it is stored or derived, whether strings use pll_translate_string( $string, $lang ), whether object IDs use an explicit $lang, whether missing-translation fallback is deliberate, and whether output is cached or persisted per language.
Dynamic String Checklist
For every admin-entered string, require a stable name/context, admin/settings-time pll_register_string(), output through pll__(), pll_esc_html__(), pll_esc_attr__(), or pll_translate_string( $string, $lang ), context escaping after translation, no registration for non-human values, PLL_Translate_Option for translated options/arrays, and raw option preservation during save/update.
PLL_Translate_Option registers option strings, translates option_{$name} reads, guards raw values during updates, and supports sanitize callbacks through pll_sanitize_string_translation. If a plugin instantiates it only in admin, frontend reads may remain untranslated; if it only runs on frontend, strings may not be registered for translators.
Stored ID Checklist
For each stored ID, classify it:
| Stored value | Audit rule |
|---|---|
| Page/post shown on current frontend page | Translate with pll_get_post( $id, $lang ) before permalink, title, content, or conditional checks. |
| Term/category used for query/filter UI | Translate with pll_get_term( $id, $lang ) or query by translated term. |
| Product/variation in Woo flow | Use Polylang WC product language store or Woo-aware hooks. |
| Attachment/media | Respect Polylang media translation settings; translate only when media translation is enabled and a translated attachment exists. |
| Order/invoice/email snapshot | Usually keep saved value, but translate live labels and template strings with explicit language. |
| Admin global selector | Either show all languages with labels or store one source-language ID and map per request. |
| Custom table foreign key | Add lang, store per-language IDs, or document language-neutral behavior. |
Common Findings
High severity:
- shortcode output uses one saved option string without
pll_register_string()andpll__(); - stored source-language page/product/term ID is used directly in frontend links, queries, or conditions;
- custom REST route returns mixed-language content and ignores
lang; - email/PDF/webhook output has no explicit language source;
- code writes directly to Polylang language/translation taxonomies;
- Woo product/order logic bypasses Polylang for WooCommerce data stores.
Medium severity:
- dynamic strings are registered only on frontend, so translators cannot edit them;
PLL_Translate_Optionis loaded only in one runtime and misses admin registration or frontend translation;- translated CPT/taxonomy filters run too late;
- custom tables or direct SQL have no language column/filter;
- Pro-only features are used without
POLYLANG_PROguards; - fragment caches/transients are shared across languages.
Low severity:
pll_e()output is not escaped for the target context;- code guesses language from locale or URL strings instead of Polylang APIs;
- language switcher HTML is rebuilt manually instead of
pll_the_languages(); - JS UI has PHP translations but no
wp_set_script_translations()or localized translated strings.
Report Format
When reporting an audit, use this structure:
- Verdict: compatible, partially compatible, not compatible, or not testable.
- Environment: Polylang core/Pro/Woo versions, languages, URL mode, Woo HPOS/Store API state if relevant.
- Findings: severity, file/line, exact behavior, why it breaks under Polylang.
- Fix plan: group by strings/options, stored IDs/URLs, CPT/taxonomies, queries/storage, REST/AJAX, Pro, Woo, and async output.
- Validation: pages, languages, REST URLs, admin settings, checkout/order/email flows, and cache cases tested.
- Residual risk: missing translations, missing Pro/Woo plugin, unavailable live languages, or untested third-party flows.
Cross-References
Use narrower skills for implementation:
polylang-language-apifor current/default languages, switchers, home URLs, and public API guards.polylang-strings-optionsfor registered strings andPLL_Translate_Option.polylang-object-translationsfor translated posts/terms, translation groups, and imports.polylang-rest-headlessfor RESTlang, Pro fields, and custom controllers.polylang-pro-slugs-sync-acffor translated slugs, sync modules, and ACF behavior.polylang-wc-compatibilityfor Woo products, variations, orders, cart, Store API, HPOS, stock, SKU, and Woo REST.wp-i18n-auditfor general WordPress gettext and JavaScript i18n.
References
- Official documentation: https://polylang.pro/doc/function-reference/
- Official documentation: https://polylang.pro/doc/developpers-how-to/
- Official documentation: https://polylang.pro/doc/rest-api/
- Official documentation: https://polylang.pro/doc/polylang-for-woocommerce/
- Verified source paths:
wp-content/plugins/polylang/polylang.phpwp-content/plugins/polylang/src/api.phpwp-content/plugins/polylang/src/translate-option.phpwp-content/plugins/polylang/src/query.phpwp-content/plugins/polylang/src/translated-post.phpwp-content/plugins/polylang/src/translated-term.phpwp-content/plugins/polylang/src/filter-rest-routes.phpwp-content/plugins/polylang/src/modules/REST/Request.phpwp-content/plugins/polylang-pro/polylang.phpwp-content/plugins/polylang-pro/src/modules/rest/rest-api.phpwp-content/plugins/polylang-pro/src/modules/sync-post/sync-post-model.phpwp-content/plugins/polylang-pro/src/modules/translate-slugs/translate-slugs-model.phpwp-content/plugins/polylang-pro/src/integrations/ACF/Main.phpwp-content/plugins/polylang-wc/polylang-wc.phpwp-content/plugins/polylang-wc/src/data-store.phpwp-content/plugins/polylang-wc/src/products.phpwp-content/plugins/polylang-wc/src/store-blocks.phpwp-content/plugins/polylang-wc/src/hpos-orders-query.phpwp-content/plugins/polylang-wc/src/modules/REST/Module.php
What ships with it: 1 file
244 B alongside SKILL.md
agents/
- openai.yaml244 B