Wpml config
Make a WordPress plugin/theme translatable with WPML by shipping a wpml-config.xml file. Covers the sections WPML honors — custom-fields/custom-field action="translate|copy|copy-once|ignore" (post meta), custom-term-fields (term meta), custom-fields-texts (translatable sub-keys inside serialized/JSON meta), custom-types /custom-type translate="0|1" with display-as-translated and automatic attributes, taxonomies/taxonomy translate="0|1", admin-texts /key name for options, shortcode-list (CSV) vs shortcodes (rich), built-with-page-builder, and gutenberg-blocks. Explains file discovery (plugin root, theme root, the wpml_config_array filter), the exact 0/1 boolean and action-enum values, that a typo'd action silently means "ignore", that the XSD is NOT enforced during normal parsing, and that admin-texts needs the String Translation add-on while gutenberg-blocks is handled by WPML's bundled page-builders add-on. Use when adding, auditing, or debugging a wpml-config.xml.From its SKILL.md
npx -y skills add Lonsdale201/wp-agent-skills --skill wpml-configAssembled 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.
SKILL.md
12.3 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
WPML: the wpml-config.xml compatibility file
wpml-config.xml is the declarative way to tell WPML what in your plugin/theme is translatable — custom fields, custom post types, taxonomies, options, and shortcodes. It needs zero runtime code: WPML reads it and configures itself. This is the first (and often only) compatibility step for most plugins. For runtime language logic see wpml-language-api; for dynamic strings see wpml-string-translation.
Where the file goes and when it's read
- Plugin:
wp-content/plugins/<your-plugin>/wpml-config.xml(plugin root). Verified discovery at class-wpml-config.php:152,169. - Theme: child then parent theme root (class-wpml-config.php:262,268).
- WPML re-reads config files in admin only, on a whitelist of pages (
plugins.php,themes.php, WPML's own pages,string-translation.phpwhen ST is active), filterable viawpml_config_white_list_pages(class-wpml-config.php:16-41). So after editing the file, visitPluginsor a WPML settings page to make WPML re-parse it — it is not re-read on every front-end request.
Skeleton (illustrative — plugin-specific keys)
<wpml-config>
<custom-fields>
<custom-field action="translate">subtitle</custom-field>
<custom-field action="copy">_my_related_ids</custom-field>
<custom-field action="copy-once">_my_layout</custom-field>
<custom-field action="ignore">_my_cache</custom-field>
<custom-field action="translate" encoding="json">footnotes</custom-field>
</custom-fields>
<custom-types>
<custom-type translate="1">my_book</custom-type>
<custom-type translate="0">my_log</custom-type>
</custom-types>
<taxonomies>
<taxonomy translate="1">my_genre</taxonomy>
<taxonomy translate="0">my_internal_tax</taxonomy>
</taxonomies>
<admin-texts>
<key name="my_plugin_options">
<key name="welcome_message" />
<key name="footer_note" />
</key>
</admin-texts>
<shortcode-list>my_cta,my_button</shortcode-list>
</wpml-config>
The sections you'll actually use
<custom-fields> — post meta (base plugin)
Each <custom-field action="...">meta_key</custom-field> sets how a meta key behaves across translations. The four action values are the enum honored by the parser (class-wpml-custom-field-xml-settings-import.php:78-96):
action | Behaviour |
|---|---|
translate | Field is offered for translation; each language has its own value. |
copy | Value is copied from original to every translation and kept in sync. |
copy-once | Copied when the translation is first created; editable independently after. |
ignore | Not touched — translations keep whatever they have (default for anything). |
Add encoding="json" for meta whose value is JSON (e.g. footnotes in WPML's own config, wpml-config.xml:24). Term meta uses the identical <custom-term-fields>/<custom-term-field action="..."> section.
Gotcha — a typo'd action silently becomes "ignore". The parser's switch has no validation branch; translate/copy/copy-once hit their cases and everything else falls to default → do nothing (:93-95). action="translated" or action="Translate" therefore silently leaves the field untranslated. There is no error — see "XSD not enforced" below.
<custom-fields-texts> — sub-keys inside a serialized/JSON meta
When a single meta key holds an array/JSON of many strings, declare exactly which inner keys are translatable with a nested <key name="..."> tree (name="*" = any key):
<custom-fields-texts>
<key name="footnotes">
<key name="*"><key name="content" label="Footnote" /></key>
</key>
</custom-fields-texts>
Verified shape at wpml-config.xml:26-32, parsed by class-wpml-custom-field-xml-settings-import.php:112-156.
<custom-types> / <taxonomies> — make CPTs & taxonomies translatable
<custom-type translate="1">slug</custom-type> and <taxonomy translate="1">slug</taxonomy>. translate is required and accepts only 0 or 1 (the wpml-integer-boolean type — not yes/no/true). Read as (int) $c['attr']['translate'] (class-wpml-tm-settings-update.php:60).
Two optional attributes on both:
display-as-translated="1"— withtranslate="1", upgrades the mode to "display as translated" (fall back to the original language when no translation exists). It's rewritten to the internal mode 2 before processing (class-wpml-config-display-as-translated.php:21-40). It is an attribute, not a<display-as-translated>section.automatic="1|0"— flags the type for automatic translation (vendor/wpml/core-api/core/settings/Automatic.php:19-30).
<admin-texts> — plugin options (needs String Translation)
Declares option names (and nested keys for serialized/array options) as translatable:
<admin-texts>
<key name="my_plugin_options">
<key name="welcome_message" />
</key>
<key type="post-ids" sub-type="attachment" name="my_logo_id" />
</admin-texts>
name is the wp_options name; nested <key> are keys inside a serialized/array option. type="post-ids" sub-type="attachment" marks a value as an object ID to convert to the translation rather than a string to translate.
Source-verified caveat: the base plugin declares admin-texts in its XSD but does not consume it — option translation is registered by WPML String Translation (which hooks wpml_config_array). On a base-only install this section does nothing. Confirmed: class-wpml-config.php initialises 'admin-texts' => array() but merge_with() never dispatches it, and no consumer exists in the base plugin. Gate any expectation on defined('WPML_ST_VERSION').
Shortcodes — two mechanisms
<shortcode-list>tag1,tag2</shortcode-list>(base) — a comma-separated list of shortcode tags whose content WPML registers for translation. Exploded on commas at class-wpml-config.php:432-434, stored viaWPML_Config_Shortcode_List.<shortcodes><shortcode><tag>…</tag><attributes><attribute>…</attribute></attributes></shortcode></shortcodes>(rich) — per-tag control over content and translatable attributes; consumed by the bundled page-builders add-on, not core. Use<shortcode-list>for the simple "translate this shortcode's inner text" case.
<built-with-page-builder><![CDATA[/<!-- wp:/]]></built-with-page-builder> marks builder-generated content via a regex; <gutenberg-blocks> declares per-block translatable parts — its config is parsed by WPML's bundled page-builders add-on (loaded unconditionally at sitepress.php:512, so it works on a base install), though translating the block content additionally needs String Translation. Full section reference in reference.md.
Programmatic config — the wpml_config_array filter
To add/modify config without a file (or for dynamically-registered post types), hook the main extension point (class-wpml-config.php:345):
add_filter( 'wpml_config_array', function ( $config ) {
$config['wpml-config']['custom-types']['custom-type'][] = [
'value' => 'my_dynamic_cpt',
'attr' => [ 'translate' => 1 ],
];
return $config;
} );
Every parsed element has the [ 'value' => ..., 'attr' => [...] ] shape; a single entry is that array, multiple entries are a list of them.
Critical rules
translate/display-as-translated/automaticare0or1— neveryes/no/true/false.actionis exactlytranslate/copy/copy-once/ignore. Anything else silently means "ignore" (no error).- The XSD is NOT enforced during normal file parsing (class-wpml-config.php:324 — validated with no XSD). Malformed elements, typo'd attributes, and wrong nesting are silently skipped, not reported. Validate your file against
res/xsd/wpml-config.xsdyourself before shipping. admin-textsrequires String Translation — declaring options does nothing without ST active.gutenberg-blocksis handled by WPML's bundled page-builders add-on (parsed on a base install); only translating the block content additionally needs ST.- Re-parse is admin-and-whitelisted-page only — edit the file, then load
Plugins/Themes/a WPML settings page to apply it; don't expect front-end reloads. - Place the file in the plugin/theme ROOT. Subfolders are not scanned.
- Ship the file; don't rely on WPML.org's remote config for your own plugin — remote config is a fallback for plugins that don't ship one.
Common mistakes
<!-- WRONG — boolean/action values WPML doesn't recognise (silently ignored) -->
<custom-type translate="yes">my_book</custom-type>
<custom-field action="translated">subtitle</custom-field>
<!-- RIGHT -->
<custom-type translate="1">my_book</custom-type>
<custom-field action="translate">subtitle</custom-field>
<!-- WRONG — expecting <admin-texts> to translate options on base WPML -->
<admin-texts><key name="my_option"/></admin-texts>
<!-- ...with no String Translation active → nothing happens. Gate on WPML_ST_VERSION. -->
Cross-references
wpml-string-translation— for options/dynamic strings whenadmin-textsisn't enough, and the ST dependency.wpml-language-api— runtime behaviour once your content is translatable.wpml-overview— the base-vs-add-on split and decision matrix.- See
reference.mdfor the complete honored-section table + the sections WPML does NOT support.
What this skill does NOT cover
<gutenberg-blocks>/ page-builder widget sections in depth — declared in base XSD but consumed by ST / the page-builders add-on; seereference.mdfor the shape.- Options/string translation runtime —
wpml-string-translation. - Sections WPML does NOT honor (
gettext-domains,custom-css,pages,post-types,custom-c2c-relationships) — listed inreference.mdso you don't invent them.
References
- Real-world example: wpml-config.xml (WPML's own).
- Schema: res/xsd/wpml-config.xsd (462 lines — every element/attribute).
- Loader/discovery + filters: classes/xml-config/class-wpml-config.php — discovery (142-274),
wpml_config_array(345), XSD-not-enforced (324), section merge (403-444). - Custom-field
actionparser: classes/settings/class-wpml-custom-field-xml-settings-import.php:78-96. - CPT/taxonomy
translateparser: classes/settings/class-wpml-tm-settings-update.php:60. - Official documentation: https://wpml.org/documentation/support/language-configuration-files/
What ships with it: 1 file
5.9 KB alongside SKILL.md
- reference.md5.9 KB