WPML compatibility audit
Use this to produce a verdict on a plugin/theme's WPML compatibility. This is a
workflow skill: it tells you what to inspect and how to classify problems. Use
the narrower WPML skills for implementation details once a failing area is
identified.
Compatibility verdicts
Report one of these:
- Compatible: static gettext, dynamic strings, stored IDs, queries, URLs,
and background render paths work in the current language or deliberately
snapshot original data.
- Partially compatible: most static strings work, but one or more dynamic
strings, stored IDs, shortcodes, emails/PDFs, or custom tables need fixes.
- Not WPML-compatible: core user-facing flows hard-code one language or one
object ID, or rely on direct meta/option strings with no translation path.
- Not testable: required WPML add-ons or plugin runtime dependencies are
missing; still report source-level risks.
Audit workflow
- Identify the multilingual surface:
- Is WPML core active/installed?
defined( 'ICL_SITEPRESS_VERSION' ).
- Is String Translation installed?
defined( 'WPML_ST_VERSION' ) or
wpml_is_st_loaded().
- If String Translation is missing, dynamic option/admin strings are not
runtime-testable; the hook form still degrades to original text.
- Is WooCommerce Multilingual relevant for products/orders?
- Does the plugin also claim Polylang/TranslatePress compatibility?
- Separate static gettext from dynamic strings:
- Static UI strings using
__(), _e(), esc_html__(), etc. are normal
WordPress i18n. Check text domain, load timing, .pot, .po/.mo, and
JS translations.
- DB/admin-entered strings need WPML String Translation. They must be
registered and translated on every output path.
- Inventory stored object IDs:
- Options/meta/custom tables holding page, post, product, variation, term,
attachment, menu, or form IDs need runtime resolution with
wpml_object_id or a correct wpml-config.xml declaration.
- URLs derived from those IDs need
get_permalink( $translated_id ) or
wpml_permalink.
- Inspect shortcode/block behavior:
- A shortcode's PHP-generated output still needs runtime string/ID handling.
- Shortcode attributes/content stored inside page content need
wpml-config.xml <shortcode-list> or rich <shortcodes> config when
WPML should expose them to translators.
- Block attributes need
<gutenberg-blocks> config when not covered by
normal block translation.
- Inspect queries:
- Default WP queries usually follow the current WPML language.
- Queries for another language need
do_action( 'wpml_switch_language', $lang )
and restore with null.
- Custom SQL/custom tables are not language-filtered by WPML; they need an
explicit language column, translated joins, or deliberate snapshot logic.
- Inspect WooCommerce paths:
- Historical order item snapshots should usually stay as purchased.
- Current product/category/tag links or settings should resolve translated
IDs before display.
- Product/category/tag meta compatibility belongs in
wpml-config.xml
custom-field/custom-term-field declarations.
- Inspect async/non-page rendering:
- Emails, PDFs, cron, REST, exports, and webhooks do not automatically have
the same language context as the frontend request.
- Store the request language when creating records, then switch language
while rendering language-sensitive output and restore afterward.
Dynamic string checklist
Find option reads/writes, settings textareas, admin-entered labels, and template
data that comes from the database.
WPML core exposes wpml_is_st_loaded() as defined( 'WPML_ST_VERSION' ).
The actual handlers are in the String Translation add-on: ST 3.5.3 registers
wpml_register_single_string in inc/functions.php and filters
wpml_translate_single_string with a 5-argument callback.
Correct pattern:
do_action( 'wpml_register_single_string', 'my-plugin', 'button_label', $value );
$label = apply_filters(
'wpml_translate_single_string',
$value,
'my-plugin',
'button_label',
$language_code // optional; omit for current language
);
Rules:
- Registering alone is not enough; output must call
wpml_translate_single_string.
- Passing an explicit language is useful for emails, PDFs, exports, and jobs
rendered outside the original frontend request.
- The hook form degrades safely when String Translation is absent.
- Legacy
icl_register_string() / icl_t() must be guarded with
function_exists().
- Keep context and names stable. Changing them orphans existing translations.
- Prefer
<admin-texts> in wpml-config.xml for fixed option keys, but remember
it needs String Translation.
Stored ID checklist
Flag every stored ID and decide whether it is a snapshot or a live reference:
- Live page/post/term/product ID for display: resolve with
wpml_object_id.
- Live URL: use translated ID or
wpml_permalink.
- Admin edit links and historical order/case snapshots can usually stay in the
original object/language.
- Asset enqueueing based on
is_page( $stored_page_id ) must also account for
the translated page ID.
Typical page-option pattern:
$page_id = (int) get_option( 'my_page_id' );
$page_id = (int) apply_filters( 'wpml_object_id', $page_id, 'page', true );
$url = $page_id > 0 ? get_permalink( $page_id ) : '';
wpml-config.xml checklist
Check whether the plugin ships wpml-config.xml in the plugin root. If absent,
decide whether it needs one.
String Translation parses config through wpml_parse_config_file /
wpml_parse_custom_config. <admin-texts> entries are imported by ST and can
attach option_{$option} filters for translated option values and nested ID
translation. Without ST, <admin-texts> does not provide translated output.
Usually declare:
- CPTs/taxonomies the plugin owns:
<custom-types> / <taxonomies>.
- Product/page/post/term meta that must copy/translate:
<custom-fields> / <custom-term-fields>.
- Option strings:
<admin-texts> with nested keys.
- Option IDs:
<key type="post-ids" sub-type="page" name="..."> or the
relevant post type.
- Shortcodes:
<shortcode-list> or <shortcodes> when translators must edit
shortcode content/attributes.
Validate config values strictly: translate, display-as-translated, and
automatic are 0|1; custom-field action is exactly
translate|copy|copy-once|ignore.
Common findings
- High: user-facing shortcode/form output reads DB option strings but never
calls
wpml_translate_single_string.
- High: stored
withdrawal_page_id, product ID, term ID, or attachment ID
is used directly on translated pages.
- High: e-mail/PDF generation ignores the language used when the case/order
was submitted.
- Medium: no
wpml-config.xml for plugin options/meta/shortcodes.
- Medium:
<admin-texts> exists, but the audit/test environment has WPML
core without String Translation, so option-string behavior is not testable.
- Medium: custom table has no language column even though rows are
language-sensitive.
- Medium: JS editor strings use
@wordpress/i18n but the script never calls
wp_set_script_translations().
- Low: admin-only labels are gettext-ready but no translation file exists.
Report format
Return:
- Verdict: Compatible / Partially compatible / Not compatible / Not testable.
- Environment: WPML core version, String Translation version/presence, WCML
presence, plugin version, active competing multilingual plugins if relevant.
- Findings: severity, file/line, what breaks, and why.
- Fix plan: minimal code/config changes, grouped by string, ID/URL, query,
shortcode/block, Woo, and async rendering.
- Residual risk: what still needs browser/WPML admin verification.
Cross-references
- Use
wpml-overview for the WPML copy/translation mental model.
- Use
wpml-config when creating or validating wpml-config.xml.
- Use
wpml-string-translation for dynamic option/admin-entered strings.
- Use
wpml-language-api for current language, object IDs, permalinks, and
language switching.
- Use
wp-i18n-audit for core WordPress gettext and Loco/PO/MO issues.
References
Validated against WPML Multilingual CMS 4.9.5 and WPML String Translation 3.5.3
local source:
- API hook registration:
sitepress.class.php
- Runtime callbacks:
inc/template-functions.php
- Config parsing:
classes/xml-config/class-wpml-config.php
- String hook call sites:
classes/language-switcher/class-wpml-ls-settings-strings.php
- Explicit-language string translate example:
classes/url-handling/class-wpml-endpoints-support.php
- Config schema:
res/xsd/wpml-config.xsd
- String Translation bootstrap/version:
wpml-string-translation/plugin.php
wpml_register_single_string / wpml_translate_single_string handlers:
wpml-string-translation/inc/functions.php
- Admin text config parsing and option filters:
wpml-string-translation/inc/private-actions.php and
wpml-string-translation/inc/admin-texts/wpml-admin-texts.class.php
- Official documentation: https://wpml.org/documentation/support/wpml-coding-api/
- Official documentation: https://wpml.org/documentation/support/language-configuration-files/
- Official documentation: https://wpml.org/documentation/getting-started-guide/string-translation/
- Verified source paths:
wp-content/plugins/sitepress-multilingual-cms/sitepress.php
wp-content/plugins/wpml-string-translation/inc/admin-texts/wpml-admin-text-configuration.php
1---2name: wpml-compatibility-audit3description: Audit whether a WordPress plugin or classic theme is WPML-compatible end to end. Use when asked "is this plugin WPML compatible?", "why does this shortcode/string/page not translate?", or when code has wpml-config.xml, stored page/product/term IDs, get_permalink/home_url, shortcode attributes, get_option option strings, custom tables, WooCommerce order/product data, emails/PDFs/background jobs, wpml_register_single_string, wpml_translate_single_string, wpml_object_id, wpml_permalink, or wpml_switch_language.4---56# WPML compatibility audit78Use this to produce a verdict on a plugin/theme's WPML compatibility. This is a9workflow skill: it tells you what to inspect and how to classify problems. Use10the narrower WPML skills for implementation details once a failing area is11identified.1213## Compatibility verdicts1415Report one of these:1617- **Compatible**: static gettext, dynamic strings, stored IDs, queries, URLs,18 and background render paths work in the current language or deliberately19 snapshot original data.20- **Partially compatible**: most static strings work, but one or more dynamic21 strings, stored IDs, shortcodes, emails/PDFs, or custom tables need fixes.22- **Not WPML-compatible**: core user-facing flows hard-code one language or one23 object ID, or rely on direct meta/option strings with no translation path.24- **Not testable**: required WPML add-ons or plugin runtime dependencies are25 missing; still report source-level risks.2627## Audit workflow28291. Identify the multilingual surface:30 - Is WPML core active/installed? `defined( 'ICL_SITEPRESS_VERSION' )`.31 - Is String Translation installed? `defined( 'WPML_ST_VERSION' )` or32 `wpml_is_st_loaded()`.33 - If String Translation is missing, dynamic option/admin strings are not34 runtime-testable; the hook form still degrades to original text.35 - Is WooCommerce Multilingual relevant for products/orders?36 - Does the plugin also claim Polylang/TranslatePress compatibility?372. Separate static gettext from dynamic strings:38 - Static UI strings using `__()`, `_e()`, `esc_html__()`, etc. are normal39 WordPress i18n. Check text domain, load timing, `.pot`, `.po/.mo`, and40 JS translations.41 - DB/admin-entered strings need WPML String Translation. They must be42 registered and translated on every output path.433. Inventory stored object IDs:44 - Options/meta/custom tables holding page, post, product, variation, term,45 attachment, menu, or form IDs need runtime resolution with46 `wpml_object_id` or a correct `wpml-config.xml` declaration.47 - URLs derived from those IDs need `get_permalink( $translated_id )` or48 `wpml_permalink`.494. Inspect shortcode/block behavior:50 - A shortcode's PHP-generated output still needs runtime string/ID handling.51 - Shortcode attributes/content stored inside page content need52 `wpml-config.xml` `<shortcode-list>` or rich `<shortcodes>` config when53 WPML should expose them to translators.54 - Block attributes need `<gutenberg-blocks>` config when not covered by55 normal block translation.565. Inspect queries:57 - Default WP queries usually follow the current WPML language.58 - Queries for another language need `do_action( 'wpml_switch_language', $lang )`59 and restore with `null`.60 - Custom SQL/custom tables are not language-filtered by WPML; they need an61 explicit language column, translated joins, or deliberate snapshot logic.626. Inspect WooCommerce paths:63 - Historical order item snapshots should usually stay as purchased.64 - Current product/category/tag links or settings should resolve translated65 IDs before display.66 - Product/category/tag meta compatibility belongs in `wpml-config.xml`67 custom-field/custom-term-field declarations.687. Inspect async/non-page rendering:69 - Emails, PDFs, cron, REST, exports, and webhooks do not automatically have70 the same language context as the frontend request.71 - Store the request language when creating records, then switch language72 while rendering language-sensitive output and restore afterward.7374## Dynamic string checklist7576Find option reads/writes, settings textareas, admin-entered labels, and template77data that comes from the database.7879WPML core exposes `wpml_is_st_loaded()` as `defined( 'WPML_ST_VERSION' )`.80The actual handlers are in the String Translation add-on: ST 3.5.3 registers81`wpml_register_single_string` in `inc/functions.php` and filters82`wpml_translate_single_string` with a 5-argument callback.8384Correct pattern:8586```php87do_action( 'wpml_register_single_string', 'my-plugin', 'button_label', $value );8889$label = apply_filters(90 'wpml_translate_single_string',91 $value,92 'my-plugin',93 'button_label',94 $language_code // optional; omit for current language95);96```9798Rules:99100- Registering alone is not enough; output must call101 `wpml_translate_single_string`.102- Passing an explicit language is useful for emails, PDFs, exports, and jobs103 rendered outside the original frontend request.104- The hook form degrades safely when String Translation is absent.105- Legacy `icl_register_string()` / `icl_t()` must be guarded with106 `function_exists()`.107- Keep context and names stable. Changing them orphans existing translations.108- Prefer `<admin-texts>` in `wpml-config.xml` for fixed option keys, but remember109 it needs String Translation.110111## Stored ID checklist112113Flag every stored ID and decide whether it is a snapshot or a live reference:114115- Live page/post/term/product ID for display: resolve with `wpml_object_id`.116- Live URL: use translated ID or `wpml_permalink`.117- Admin edit links and historical order/case snapshots can usually stay in the118 original object/language.119- Asset enqueueing based on `is_page( $stored_page_id )` must also account for120 the translated page ID.121122Typical page-option pattern:123124```php125$page_id = (int) get_option( 'my_page_id' );126$page_id = (int) apply_filters( 'wpml_object_id', $page_id, 'page', true );127$url = $page_id > 0 ? get_permalink( $page_id ) : '';128```129130## `wpml-config.xml` checklist131132Check whether the plugin ships `wpml-config.xml` in the plugin root. If absent,133decide whether it needs one.134135String Translation parses config through `wpml_parse_config_file` /136`wpml_parse_custom_config`. `<admin-texts>` entries are imported by ST and can137attach `option_{$option}` filters for translated option values and nested ID138translation. Without ST, `<admin-texts>` does not provide translated output.139140Usually declare:141142- CPTs/taxonomies the plugin owns: `<custom-types>` / `<taxonomies>`.143- Product/page/post/term meta that must copy/translate:144 `<custom-fields>` / `<custom-term-fields>`.145- Option strings: `<admin-texts>` with nested keys.146- Option IDs: `<key type="post-ids" sub-type="page" name="...">` or the147 relevant post type.148- Shortcodes: `<shortcode-list>` or `<shortcodes>` when translators must edit149 shortcode content/attributes.150151Validate config values strictly: `translate`, `display-as-translated`, and152`automatic` are `0|1`; custom-field `action` is exactly153`translate|copy|copy-once|ignore`.154155## Common findings156157- **High**: user-facing shortcode/form output reads DB option strings but never158 calls `wpml_translate_single_string`.159- **High**: stored `withdrawal_page_id`, product ID, term ID, or attachment ID160 is used directly on translated pages.161- **High**: e-mail/PDF generation ignores the language used when the case/order162 was submitted.163- **Medium**: no `wpml-config.xml` for plugin options/meta/shortcodes.164- **Medium**: `<admin-texts>` exists, but the audit/test environment has WPML165 core without String Translation, so option-string behavior is not testable.166- **Medium**: custom table has no language column even though rows are167 language-sensitive.168- **Medium**: JS editor strings use `@wordpress/i18n` but the script never calls169 `wp_set_script_translations()`.170- **Low**: admin-only labels are gettext-ready but no translation file exists.171172## Report format173174Return:175176- **Verdict**: Compatible / Partially compatible / Not compatible / Not testable.177- **Environment**: WPML core version, String Translation version/presence, WCML178 presence, plugin version, active competing multilingual plugins if relevant.179- **Findings**: severity, file/line, what breaks, and why.180- **Fix plan**: minimal code/config changes, grouped by string, ID/URL, query,181 shortcode/block, Woo, and async rendering.182- **Residual risk**: what still needs browser/WPML admin verification.183184## Cross-references185186- Use `wpml-overview` for the WPML copy/translation mental model.187- Use `wpml-config` when creating or validating `wpml-config.xml`.188- Use `wpml-string-translation` for dynamic option/admin-entered strings.189- Use `wpml-language-api` for current language, object IDs, permalinks, and190 language switching.191- Use `wp-i18n-audit` for core WordPress gettext and Loco/PO/MO issues.192193## References194195Validated against WPML Multilingual CMS 4.9.5 and WPML String Translation 3.5.3196local source:197198- API hook registration: `sitepress.class.php`199- Runtime callbacks: `inc/template-functions.php`200- Config parsing: `classes/xml-config/class-wpml-config.php`201- String hook call sites:202 `classes/language-switcher/class-wpml-ls-settings-strings.php`203- Explicit-language string translate example:204 `classes/url-handling/class-wpml-endpoints-support.php`205- Config schema: `res/xsd/wpml-config.xsd`206- String Translation bootstrap/version: `wpml-string-translation/plugin.php`207- `wpml_register_single_string` / `wpml_translate_single_string` handlers:208 `wpml-string-translation/inc/functions.php`209- Admin text config parsing and option filters:210 `wpml-string-translation/inc/private-actions.php` and211 `wpml-string-translation/inc/admin-texts/wpml-admin-texts.class.php`212- Official documentation: <https://wpml.org/documentation/support/wpml-coding-api/>213- Official documentation: <https://wpml.org/documentation/support/language-configuration-files/>214- Official documentation: <https://wpml.org/documentation/getting-started-guide/string-translation/>215- Verified source paths:216 - `wp-content/plugins/sitepress-multilingual-cms/sitepress.php`217 - `wp-content/plugins/wpml-string-translation/inc/admin-texts/wpml-admin-text-configuration.php`