wpml-overview

Orient a developer making a WordPress plugin or theme compatible with WPML (sitepress-multilingual-cms). Explains WPML's mental model — it translates COPIES (each translation is a separate post/term w

SKILL.md

GitHub

WPML: compatibility overview (orient)

For developers making a plugin or theme work correctly with WPML (the sitepress-multilingual-cms base plugin). This skill orients you — the mental model, how to detect WPML and its add-ons, and which of the three compatibility mechanisms to reach for. The mechanics of each live in dedicated skills (wpml-config, wpml-language-api, wpml-string-translation).

Mental model — WPML translates COPIES

The single most important thing to internalise: WPML creates a separate post/term for each language. A page in English (ID 10) and its Hungarian translation (ID 42) are two distinct WP objects, linked by a shared trid (translation group ID) in the icl_translations table. WPML sets the current language, and WordPress queries return the objects in that language.

This is the opposite of live-translation plugins (e.g. TranslatePress) that translate one page's output on the fly. Consequences for your code:

  • To display "the translation of object X in the current language", resolve the translated ID with wpml_object_id — never assume ID 10 is valid in every language.
  • IDs, permalinks, and menu items are per-language. Don't cache an absolute URL or an object ID across a request that may switch language.
  • Free-form strings your plugin outputs (option values, labels not in a .po file) aren't "copied" — they must be registered for translation (String Translation).

Detecting WPML and its add-ons

WPML is not one plugin — the base handles languages and content copies (and bundles the Translation Management workflow); separately-installed add-ons add string/option translation (String Translation) and media (Media Translation).

// Base plugin active? (defines the version constant in its bootstrap)
if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { /* WPML core is active */ }

// Separately-installed add-ons (each defines its own version constant):
defined( 'WPML_ST_VERSION' );     // String Translation — string & option translation
defined( 'WPML_MEDIA_VERSION' );  // Media Translation

// Translation Management: BUNDLED in core WPML (defined in tm.php, loaded via
// Plugins::loadEmbeddedTM) — NOT a separate download. Present whenever core is,
// once enabled in setup. Detect the feature, not a "plugin":
defined( 'WPML_TM_VERSION' );

Verified: ICL_SITEPRESS_VERSION is defined in sitepress.php:35 ('4.9.5') and WPML's own bootstrap guards on it (sitepress.php:26). WPML_TM_VERSION is defined inside core at tm.php:9 (loaded via Plugins::loadEmbeddedTM(), classes/plugins/Plugins.php:86) — TM ships with the base download, unlike ST/Media. The base ships a helper for the ST check — wpml_is_st_loaded() returns defined( 'WPML_ST_VERSION' ) (inc/functions.php:922-924); class_exists( 'WPML_String_Translation' ) is also used internally.

Why this matters: option translation (admin-texts) and all string registration are consumed by String Translation, not base runtime — declaring them does nothing without the ST add-on. Gutenberg-block config, by contrast, is handled by WPML's bundled page-builders add-on (parsed on a base install; only translating block content additionally needs ST). Detect before you depend.

The three compatibility mechanisms

Mechanism What it's for Needs Skill
wpml-config.xml Declare which custom fields, CPTs, taxonomies, options, shortcodes are translatable/copied base (custom fields / CPT / taxonomy); ST for admin-texts; bundled page-builders add-on for gutenberg-blocks wpml-config
Runtime language API Behave per-language: current/active language, resolve translated IDs, switch language, build language URLs base wpml-language-api
String registration / translation Translate free-form strings your plugin emits (settings values, dynamic labels) String Translation add-on wpml-string-translation

Decision matrix — intent → mechanism

  • "My CPT / taxonomy should be translatable" → wpml-config.xml <custom-types> / <taxonomies> (base — TM is only for the translation workflow, not for marking a type translatable).
  • "This post meta should copy / translate / stay put across translations" → wpml-config.xml <custom-fields action="..."> (base).
  • "My plugin's option (a string in wp_options) should be translatable" → wpml-config.xml <admin-texts> and String Translation active.
  • "I output a dynamic label / message that isn't a gettext string" → register it via wpml_register_string + translate via wpml_translate_single_string (ST). Static UI strings → normal WordPress i18n (__()), no WPML needed.
  • "I need to know the current language / show content in it / link to the translation" → runtime API (wpml_current_language, wpml_object_id, wpml_permalink).
  • "I query posts and want them in a specific language" → wpml_switch_language around the query, then restore.

Where WPML ships its own compatibility

The base plugin bundles integrations for popular plugins/themes under compatibility/ (gutenberg, jetpack, Flatsome, GoogleSiteKit, disqus, google-sitemap-generator, tiny-compress-images, twentyseventeen, …), wired via compatibility/wpml-compatibility-factory.php. When integrating a well-known plugin, check there first — the pattern may already exist. WPML also hosts remote config for many plugins (the wpml_config_index mechanism), so a plugin can be translatable via WPML.org's hosted wpml-config.xml even without shipping one.

Critical rules

  • Detect the right layer. defined('ICL_SITEPRESS_VERSION') for the base; WPML_ST_VERSION / class_exists('WPML_String_Translation') before anything string/option-translation related.
  • Prefer declarative config (wpml-config.xml) over code for custom fields / CPTs / taxonomies / shortcodes — it's what WPML expects and needs no runtime hooks.
  • Never hardcode a cross-language ID or URL. Resolve with wpml_object_id / wpml_permalink at render time.
  • Static translatable UI text is plain WordPress i18n (__(), _e()), not WPML. WPML enters only for content copies, options, and dynamic/registered strings.
  • Degrade gracefully. Your plugin must work with WPML absent and with the base present but an add-on missing.

Cross-references

  • wpml-config — the wpml-config.xml file: custom fields, CPTs, taxonomies, admin-texts, shortcodes.
  • wpml-language-api — runtime language hooks: current/active language, wpml_object_id, wpml_switch_language, permalinks.
  • wpml-string-translation — registering & translating dynamic strings (needs the ST add-on).

What this skill does NOT cover

  • Mechanics of each mechanism — see the three dedicated skills.
  • WPML's admin UI / translation editor workflow — this is a developer-compatibility family, not an end-user guide.
  • Building a language switcher UI — WPML provides [wpml_language_switcher] / the LS API; custom switchers are a separate topic.
  • The internal icl_translations schema — you interact via the hook API, not the tables directly.
  • WooCommerce Multilingual (WCML) — a separate WPML add-on with its own hooks.

References