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 i
Install
npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/wpml/wpml-config
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
git clone https://github.com/Lonsdale201/wp-agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
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/
Files (wp-agent-skills)
-
reference.md 5.9 KB
# wpml-config — reference Complete honored-section map for `wpml-config.xml`, from the XSD (`res/xsd/wpml-config.xsd`) cross-checked against the parser (`classes/xml-config/class-wpml-config.php`, `merge_with()` / `parse_config_index()`). "Consumer" = which component actually acts on the section: **base** = sitepress core, **ST** = WPML String Translation add-on, **PB** = bundled page-builders add-on. ## All honored `<wpml-config>` child sections | Section | Child element | Key attributes / values | Consumer | Parser ref | |---|---|---|---|---| | `custom-fields` | `custom-field` | `action` = `translate\|copy\|copy-once\|ignore`; `encoding` (e.g. `json`); `style` (`line\|textarea\|visual`); `label`; `group`; `translate_link_target` (0/1); `convert_to_sticky` (0/1); `type`; `sub-type` | base | class-wpml-custom-field-xml-settings-import.php:32-98 | | `custom-term-fields` | `custom-term-field` | same as `custom-field` | base | class-wpml-custom-field-xml-settings-import.php:40-72 | | `custom-fields-texts` | `key` | `name` (req), `label`, `type`, `sub-type`, `encoding=json`, `search-method` (`wildcards\|regex`); nestable; `name="*"` = any | base | class-wpml-custom-field-xml-settings-import.php:112-156 | | `custom-types` | `custom-type` | `translate` (req, 0/1); `display-as-translated` (0/1); `automatic` (0/1) | base | class-wpml-tm-settings-update.php:60; Automatic.php:19-30 | | `taxonomies` | `taxonomy` | `translate` (req, 0/1); `display-as-translated` (`automatic` is XSD-valid but only honored on `custom-type`) | base | class-wpml-config.php:88-90 | | `admin-texts` | `key` | `name` (req); nestable for serialized options; `type="post-ids" sub-type="attachment"` for ID conversion; `encoding=json`; `search-method` | **ST** | XSD:203-209 (no base consumer) | | `shortcode-list` | (CSV text) | comma-separated shortcode tags; content registered for translation | base | class-wpml-config.php:432-434 | | `shortcodes` | `shortcode` → `tag` + `attributes`/`attribute` | tag: `encoding`, `encoding-condition`, `raw-html`, `label`, `ignore-content` (0/1), `type=media-url`; attribute: `type`, `sub-type`, `encoding`, `label` | **PB** | class-wpml-pb-config-import-shortcode.php:36-180 | | `built-with-page-builder` | (CDATA regex) | marks builder-generated content | base | class-wpml-config.php:436-438 | | `gutenberg-blocks` | `gutenberg-block` | `type` (req), `translate` (req 0/1), `label`; children `<xpath>` (opt `type=link`, `sub-type`, `label`) and/or `<key>` | **PB** (bundled; block *content* translation also needs ST) | class-wpml-config.php:412 (merge); addons/wpml-page-builders/classes/Integrations/Gutenberg/class-wpml-gutenberg-config-option.php:22-27 | | `elementor-widgets` | `widget` | `name`; children `conditions`, `fields` (`field` with `type`/`sub-type`/`editor_type=LINE\|AREA\|VISUAL\|LINK`), `fields-in-item`, `integration-classes` | **PB** | class-wpml-config.php:414; Elementor/Config/* | | `beaver-builder-widgets` | `widget` | same as elementor-widgets | **PB** | class-wpml-config.php:415 | | `cornerstone-widgets` | `widget` | same | **PB** | class-wpml-config.php:416 | | `siteorigin-widgets` | `widget` | same | **PB** | class-wpml-config.php:417 | | `language-switcher-settings` | `key` | nested `name` tree of LS options | base | class-wpml-config.php:422-430, :83 | | `allow-translatable-job-fields` | `allow-translatable-job-field` | `type` (req, regex), `value` (req, regex) — whitelists otherwise-untranslatable job fields | base (TM is bundled in core) | class-allow-translatable-job-fields.php:27-68 | | `notices` | `notice` | `id` (req), `type`, `dismissible`; children `conditions` (theme/plugin, `relation=AND\|OR`), `locations`/`screenId`, `content` | base | class-wpml-config.php:419; RemoteNotices/Hooks.php | `wpml-integer-boolean` (used by every `translate` / `display-as-translated` / `automatic` / `dismissible` / `ignore-content`) accepts **only `0` or `1`** (XSD:133-138). ## Custom-field `action` → internal status constant `inc/constants.php:129-132`: `translate` → `WPML_TRANSLATE_CUSTOM_FIELD (2)`, `copy` → `WPML_COPY_CUSTOM_FIELD (1)`, `ignore`/unknown → `WPML_IGNORE_CUSTOM_FIELD (0)`, `copy-once` → `WPML_COPY_ONCE_CUSTOM_FIELD (3)`. ## CPT/taxonomy `translate` → mode constant `inc/constants.php:161-163`: `0` → `WPML_CONTENT_TYPE_DONT_TRANSLATE`, `1` → `WPML_CONTENT_TYPE_TRANSLATE`, and `display-as-translated="1"` rewrites `1` → `WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED (2)`. ## File discovery order (class-wpml-config.php) 1. Active plugins → `WPML_PLUGINS_DIR/<plugin-dir>/wpml-config.xml` (:152), plus must-use plugins (:185-191). 2. Child theme (:262) then parent/template theme (:268) root. 3. Global (remote notices) config (:293-303). 4. Custom XML option (`wpml-tm-custom-xml`) — the **only** XSD-validated path (:358-395). 5. Remote override index (`wpml_config_index`) when `override_local` set or no local file (:198-248). Extension filters: `wpml_config_array` (:345, primary), `icl_wpml_config_array` (:344), actions `wpml_parse_config_file` (:335), `wpml_parse_custom_config` (:388). ## Sections WPML does NOT honor (don't invent these) Verified absent from XSD, parser, and the whole plugin tree: - `<gettext-domains>` — not a config section. Static gettext strings are handled at runtime by String Translation's gettext hooks + your normal `.po`/`.mo` files, not declared here. - `<custom-css>` — not honored. - `<pages>` — no such section. - `<post-types>` / `<posts>` — no such sections; automatic-translation control is the `automatic` attribute on `<custom-type>`. - `<custom-c2c-relationships>` — not a WPML-core section. - `<display-as-translated>` as a standalone section — it is only the `display-as-translated="1"` **attribute** on `<custom-type>`/`<taxonomy>`. ## Validating before shipping The XSD is not enforced at parse time, so validate manually: ```bash xmllint --noout --schema \ wp-content/plugins/sitepress-multilingual-cms/res/xsd/wpml-config.xsd \ your-plugin/wpml-config.xml ``` -
SKILL.md 12.3 KB
--- name: wpml-config description: 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. metadata: wp-skills-author: "Soczó Kristóf" wp-skills-contact: "mailto:lonsdale201@hotmail.com" wp-skills-plugin: "sitepress-multilingual-cms" wp-skills-plugin-version-tested: "4.9.5" wp-skills-php-min: "7.4" wp-skills-last-updated: "2026-07-03" --- # 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](class-wpml-config.php). - **Theme:** child then parent theme root ([class-wpml-config.php:262,268](class-wpml-config.php)). - WPML re-reads config files **in admin only**, on a whitelist of pages (`plugins.php`, `themes.php`, WPML's own pages, `string-translation.php` when ST is active), filterable via `wpml_config_white_list_pages` ([class-wpml-config.php:16-41](class-wpml-config.php)). So after editing the file, visit `Plugins` or 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) ```xml <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](class-wpml-custom-field-xml-settings-import.php)): | `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](wpml-config.xml)). 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](class-wpml-custom-field-xml-settings-import.php)). `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): ```xml <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](wpml-config.xml), parsed by [class-wpml-custom-field-xml-settings-import.php:112-156](class-wpml-custom-field-xml-settings-import.php). ### `<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](class-wpml-tm-settings-update.php)). Two optional attributes on both: - `display-as-translated="1"` — with `translate="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](class-wpml-config-display-as-translated.php)). 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](Automatic.php)). ### `<admin-texts>` — plugin options (needs String Translation) Declares option names (and nested keys for serialized/array options) as translatable: ```xml <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](class-wpml-config.php), stored via `WPML_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](sitepress.php), 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](class-wpml-config.php)): ```php 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` / `automatic` are `0` or `1`** — never `yes`/`no`/`true`/`false`. - **`action` is exactly `translate` / `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](class-wpml-config.php) — 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.xsd` yourself before shipping. - **`admin-texts` requires String Translation** — declaring options does nothing without ST active. **`gutenberg-blocks` is 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 ```xml <!-- 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> ``` ```xml <!-- 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 when `admin-texts` isn'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.md` for 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; see `reference.md` for 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 in `reference.md` so you don't invent them. ## References - Real-world example: [wpml-config.xml](wpml-config.xml) (WPML's own). - Schema: [res/xsd/wpml-config.xsd](wpml-config.xsd) (462 lines — every element/attribute). - Loader/discovery + filters: [classes/xml-config/class-wpml-config.php](class-wpml-config.php) — discovery (142-274), `wpml_config_array` (345), XSD-not-enforced (324), section merge (403-444). - Custom-field `action` parser: [classes/settings/class-wpml-custom-field-xml-settings-import.php:78-96](class-wpml-custom-field-xml-settings-import.php). - CPT/taxonomy `translate` parser: [classes/settings/class-wpml-tm-settings-update.php:60](class-wpml-tm-settings-update.php). - Official documentation: <https://wpml.org/documentation/support/language-configuration-files/>
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.