Claude Skill

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

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download lonsdale201-wp-agent-skills-wpml_wpml-config-52f6020.zip · 7 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/wpml/wpml-config
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git 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.php when ST is active), filterable via wpml_config_white_list_pages (class-wpml-config.php:16-41). 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)

<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:

<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 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, 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 / 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 — 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

<!-- 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 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

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.

No comments yet.

Reviews (0)

No reviews yet.

Related