Claude Skill

polylang-object-translations

Create, read, link, and update translated posts and terms with Polylang 3.8.5. Covers pll_get_post, pll_get_term, pll_get_post_language, pll_get_term_language, pll_save_post_translations, pll_save_term_translations, pll_insert_post, pll_insert_term, pll_update_post, pll_update_te

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-polylang_polylang-object-translations-52f6020.zip · 3 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/polylang/polylang-object-translations
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

Polylang Object Translations

Use this skill when code needs to read or write the relationship between translated posts or translated terms.

Polylang stores language and translation relationships through private taxonomies, not custom tables:

Object Language taxonomy Translation group taxonomy
Posts/CPTs/attachments language post_translations
Terms term_language term_translations

Do not write those terms or term descriptions directly. Use Polylang APIs so caches, cleanup, validation, and synchronization hooks run.

Resolve translated IDs

For posts:

$translated_id = pll_get_post( $post_id, 'fr' );

if ( $translated_id > 0 ) {
    $post = get_post( $translated_id );
}

For terms:

$translated_term_id = pll_get_term( $term_id, 'fr' );

Important return behavior in Polylang 3.8.5:

  • pll_get_post() returns an integer ID.
  • pll_get_term() returns an integer ID.
  • Missing translation, invalid language, or object without language returns 0, not false.
  • If the requested language is the object's own language, the original ID is returned.

When $lang is omitted, both functions use pll_current_language(). In cron, CLI, imports, and many admin tasks, pass the language explicitly.

Read language and groups

$post_lang = pll_get_post_language( $post_id );          // slug or false.
$term_lang = pll_get_term_language( $term_id, 'locale' );
$lang_obj  = pll_get_post_language( $post_id, \OBJECT );

$post_translations = pll_get_post_translations( $post_id );
$term_translations = pll_get_term_translations( $term_id );

Translation arrays are keyed by language slug and contain object IDs:

array(
    'en' => 123,
    'fr' => 456,
)

Only trust arrays returned by Polylang. The model validates languages and object IDs before saving.

Create posts with language

Polylang 3.7+ provides pll_insert_post() and pll_update_post() wrappers:

$post_id = pll_insert_post(
    array(
        'post_type'   => 'book',
        'post_status' => 'publish',
        'post_title'  => 'Hello',
    ),
    'en'
);

if ( is_wp_error( $post_id ) ) {
    return $post_id;
}

Create a translation and link it in one pass:

$fr_id = pll_insert_post(
    array(
        'post_type'    => 'book',
        'post_status'  => 'publish',
        'post_title'   => 'Bonjour',
        'translations' => array(
            'en' => $post_id,
        ),
    ),
    'fr'
);

Update language or group on an existing post:

$result = pll_update_post( array(
    'ID'           => $fr_id,
    'lang'         => 'fr',
    'translations' => array(
        'en' => $post_id,
        'fr' => $fr_id,
    ),
) );

pll_insert_post() returns WP_Error( 'invalid_language' ) for an invalid language. Do not call wp_insert_post() and patch Polylang's private taxonomies by hand.

Create terms with language

Use the term wrappers:

$term = pll_insert_term(
    'News',
    'category',
    'en',
    array(
        'slug' => 'news',
    )
);

if ( is_wp_error( $term ) ) {
    return $term;
}

Create and link a translated term:

$fr_term = pll_insert_term(
    'Actualites',
    'category',
    'fr',
    array(
        'slug'         => 'actualites',
        'translations' => array(
            'en' => (int) $term['term_id'],
        ),
    )
);

Update:

pll_update_term( (int) $fr_term['term_id'], array(
    'lang'         => 'fr',
    'translations' => array(
        'en' => (int) $term['term_id'],
        'fr' => (int) $fr_term['term_id'],
    ),
) );

Term parents are language-sensitive. When creating hierarchical translated terms, translate the parent ID first.

Link existing objects

When objects already exist, set language before saving translations:

pll_set_post_language( $en_id, 'en' );
pll_set_post_language( $fr_id, 'fr' );

$saved = pll_save_post_translations( array(
    'en' => $en_id,
    'fr' => $fr_id,
) );

For terms:

pll_set_term_language( $en_term_id, 'en' );
pll_set_term_language( $fr_term_id, 'fr' );

$saved = pll_save_term_translations( array(
    'en' => $en_term_id,
    'fr' => $fr_term_id,
) );

pll_set_post_language() and pll_set_term_language() return true only when the assignment changed successfully. They return false if the object already has that language or if assignment fails. Do not treat false as a fatal error without checking current state.

Translatable type requirement

Post and term APIs only make sense for object types Polylang manages:

if ( ! pll_is_translated_post_type( get_post_type( $post_id ) ) ) {
    return;
}

$term = get_term( $term_id );
if ( $term instanceof WP_Term && ! pll_is_translated_taxonomy( $term->taxonomy ) ) {
    return;
}

Register custom CPTs/taxonomies for translation with pll_get_post_types or pll_get_taxonomies early. See polylang-language-api.

Media caveat

Attachments are translated only when Polylang media support is enabled. pll_get_post( $attachment_id, $lang ) still returns 0 if the attachment has no translation.

Do not duplicate attachment rows manually unless you also need all attachment metadata, file references, alt text, language, and translation group handling. Polylang's model has internal media translation behavior and fires pll_translate_media after creating a media translation.

Imports and migrations

Safe import order:

  1. Ensure languages exist and collect explicit slugs.
  2. Register CPT/taxonomy translation support before importing.
  3. Insert original objects with pll_insert_post() or pll_insert_term().
  4. Insert translations with translations arrays or link existing IDs with pll_save_*_translations().
  5. Resolve relationship fields after all target translations exist.
  6. Flush rewrite rules only if translated slugs or rewrite structures changed.

Do not rely on the current language during imports. Use explicit source and target slugs.

Hooks worth knowing

  • pll_save_post fires after post language/translations are saved.
  • pll_save_term fires after term language/translations are saved.
  • pll_translate_media fires after media translation creation.
  • pll_maybe_translate_term lets sync code substitute a term ID while copying taxonomy relations.

Hook only when you need to cooperate with Polylang's save/sync pipeline. Avoid recursive writes without a re-entry guard.

Common mistakes

  • Saving translations before every object has a language.
  • Comparing pll_get_post() result with false instead of checking > 0.
  • Linking IDs from different post types or taxonomies into one translation group.
  • Updating post_translations or term_translations term descriptions directly.
  • Using get_term_by( 'slug', ... ) without lang => '' or without translating term IDs.
  • Assuming a translated post's slug must be unique globally. Polylang Pro can share slugs by language.

Verification

Local source checked against:

  • API wrappers and return values: wp-content/plugins/polylang/src/api.php
  • Translation group storage and validation: src/translated-object.php
  • Post type and post translation behavior: src/translated-post.php
  • Term language, WXR group behavior, and term wrappers: src/translated-term.php

References

  • Official documentation: https://polylang.pro/doc/function-reference/
  • Official documentation: https://polylang.pro/doc/developpers-how-to/
  • Verified source paths:
    • wp-content/plugins/polylang/src/api.php
    • wp-content/plugins/polylang/src/translated-object.php
    • wp-content/plugins/polylang/src/translated-post.php
    • wp-content/plugins/polylang/src/translated-term.php
    • wp-content/plugins/polylang/src/crud-posts.php
    • wp-content/plugins/polylang/src/crud-terms.php
    • wp-content/plugins/polylang/src/translatable-object.php
Files (wp-agent-skills)
  • agents
    • openai.yaml 257 B
      interface:
        display_name: "Polylang Object Translations"
        short_description: "Polylang post and term translation linking"
        default_prompt: "Use $polylang-object-translations to create, link, or repair translated WordPress posts and terms with Polylang."
      
  • SKILL.md 8.6 KB
    ---
    name: polylang-object-translations
    description: "Create, read, link, and update translated posts and terms with Polylang 3.8.5. Covers pll_get_post, pll_get_term, pll_get_post_language, pll_get_term_language, pll_save_post_translations, pll_save_term_translations, pll_insert_post, pll_insert_term, pll_update_post, pll_update_term, translation group storage, language assignment order, media translation caveats, and why direct DB writes to language/post_translations/term_translations are unsafe. Use when a plugin imports multilingual content, syncs CPTs/taxonomies, maps IDs across languages, or repairs translation groups."
    metadata:
      wp-skills-author: "Soczo Kristof"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "polylang"
      wp-skills-plugin-version-tested: "Polylang 3.8.5"
      wp-skills-wp-version-tested: "7.0"
      wp-skills-php-min: "7.4"
      wp-skills-last-updated: "2026-07-01"
    ---
    
    # Polylang Object Translations
    
    Use this skill when code needs to read or write the relationship between translated posts or translated terms.
    
    Polylang stores language and translation relationships through private taxonomies, not custom tables:
    
    | Object | Language taxonomy | Translation group taxonomy |
    |---|---|---|
    | Posts/CPTs/attachments | `language` | `post_translations` |
    | Terms | `term_language` | `term_translations` |
    
    Do not write those terms or term descriptions directly. Use Polylang APIs so caches, cleanup, validation, and synchronization hooks run.
    
    ## Resolve translated IDs
    
    For posts:
    
    ```php
    $translated_id = pll_get_post( $post_id, 'fr' );
    
    if ( $translated_id > 0 ) {
        $post = get_post( $translated_id );
    }
    ```
    
    For terms:
    
    ```php
    $translated_term_id = pll_get_term( $term_id, 'fr' );
    ```
    
    Important return behavior in Polylang 3.8.5:
    
    - `pll_get_post()` returns an integer ID.
    - `pll_get_term()` returns an integer ID.
    - Missing translation, invalid language, or object without language returns `0`, not `false`.
    - If the requested language is the object's own language, the original ID is returned.
    
    When `$lang` is omitted, both functions use `pll_current_language()`. In cron, CLI, imports, and many admin tasks, pass the language explicitly.
    
    ## Read language and groups
    
    ```php
    $post_lang = pll_get_post_language( $post_id );          // slug or false.
    $term_lang = pll_get_term_language( $term_id, 'locale' );
    $lang_obj  = pll_get_post_language( $post_id, \OBJECT );
    
    $post_translations = pll_get_post_translations( $post_id );
    $term_translations = pll_get_term_translations( $term_id );
    ```
    
    Translation arrays are keyed by language slug and contain object IDs:
    
    ```php
    array(
        'en' => 123,
        'fr' => 456,
    )
    ```
    
    Only trust arrays returned by Polylang. The model validates languages and object IDs before saving.
    
    ## Create posts with language
    
    Polylang 3.7+ provides `pll_insert_post()` and `pll_update_post()` wrappers:
    
    ```php
    $post_id = pll_insert_post(
        array(
            'post_type'   => 'book',
            'post_status' => 'publish',
            'post_title'  => 'Hello',
        ),
        'en'
    );
    
    if ( is_wp_error( $post_id ) ) {
        return $post_id;
    }
    ```
    
    Create a translation and link it in one pass:
    
    ```php
    $fr_id = pll_insert_post(
        array(
            'post_type'    => 'book',
            'post_status'  => 'publish',
            'post_title'   => 'Bonjour',
            'translations' => array(
                'en' => $post_id,
            ),
        ),
        'fr'
    );
    ```
    
    Update language or group on an existing post:
    
    ```php
    $result = pll_update_post( array(
        'ID'           => $fr_id,
        'lang'         => 'fr',
        'translations' => array(
            'en' => $post_id,
            'fr' => $fr_id,
        ),
    ) );
    ```
    
    `pll_insert_post()` returns `WP_Error( 'invalid_language' )` for an invalid language. Do not call `wp_insert_post()` and patch Polylang's private taxonomies by hand.
    
    ## Create terms with language
    
    Use the term wrappers:
    
    ```php
    $term = pll_insert_term(
        'News',
        'category',
        'en',
        array(
            'slug' => 'news',
        )
    );
    
    if ( is_wp_error( $term ) ) {
        return $term;
    }
    ```
    
    Create and link a translated term:
    
    ```php
    $fr_term = pll_insert_term(
        'Actualites',
        'category',
        'fr',
        array(
            'slug'         => 'actualites',
            'translations' => array(
                'en' => (int) $term['term_id'],
            ),
        )
    );
    ```
    
    Update:
    
    ```php
    pll_update_term( (int) $fr_term['term_id'], array(
        'lang'         => 'fr',
        'translations' => array(
            'en' => (int) $term['term_id'],
            'fr' => (int) $fr_term['term_id'],
        ),
    ) );
    ```
    
    Term parents are language-sensitive. When creating hierarchical translated terms, translate the parent ID first.
    
    ## Link existing objects
    
    When objects already exist, set language before saving translations:
    
    ```php
    pll_set_post_language( $en_id, 'en' );
    pll_set_post_language( $fr_id, 'fr' );
    
    $saved = pll_save_post_translations( array(
        'en' => $en_id,
        'fr' => $fr_id,
    ) );
    ```
    
    For terms:
    
    ```php
    pll_set_term_language( $en_term_id, 'en' );
    pll_set_term_language( $fr_term_id, 'fr' );
    
    $saved = pll_save_term_translations( array(
        'en' => $en_term_id,
        'fr' => $fr_term_id,
    ) );
    ```
    
    `pll_set_post_language()` and `pll_set_term_language()` return `true` only when the assignment changed successfully. They return `false` if the object already has that language or if assignment fails. Do not treat `false` as a fatal error without checking current state.
    
    ## Translatable type requirement
    
    Post and term APIs only make sense for object types Polylang manages:
    
    ```php
    if ( ! pll_is_translated_post_type( get_post_type( $post_id ) ) ) {
        return;
    }
    
    $term = get_term( $term_id );
    if ( $term instanceof WP_Term && ! pll_is_translated_taxonomy( $term->taxonomy ) ) {
        return;
    }
    ```
    
    Register custom CPTs/taxonomies for translation with `pll_get_post_types` or `pll_get_taxonomies` early. See `polylang-language-api`.
    
    ## Media caveat
    
    Attachments are translated only when Polylang media support is enabled. `pll_get_post( $attachment_id, $lang )` still returns `0` if the attachment has no translation.
    
    Do not duplicate attachment rows manually unless you also need all attachment metadata, file references, alt text, language, and translation group handling. Polylang's model has internal media translation behavior and fires `pll_translate_media` after creating a media translation.
    
    ## Imports and migrations
    
    Safe import order:
    
    1. Ensure languages exist and collect explicit slugs.
    2. Register CPT/taxonomy translation support before importing.
    3. Insert original objects with `pll_insert_post()` or `pll_insert_term()`.
    4. Insert translations with `translations` arrays or link existing IDs with `pll_save_*_translations()`.
    5. Resolve relationship fields after all target translations exist.
    6. Flush rewrite rules only if translated slugs or rewrite structures changed.
    
    Do not rely on the current language during imports. Use explicit source and target slugs.
    
    ## Hooks worth knowing
    
    - `pll_save_post` fires after post language/translations are saved.
    - `pll_save_term` fires after term language/translations are saved.
    - `pll_translate_media` fires after media translation creation.
    - `pll_maybe_translate_term` lets sync code substitute a term ID while copying taxonomy relations.
    
    Hook only when you need to cooperate with Polylang's save/sync pipeline. Avoid recursive writes without a re-entry guard.
    
    ## Common mistakes
    
    - Saving translations before every object has a language.
    - Comparing `pll_get_post()` result with `false` instead of checking `> 0`.
    - Linking IDs from different post types or taxonomies into one translation group.
    - Updating `post_translations` or `term_translations` term descriptions directly.
    - Using `get_term_by( 'slug', ... )` without `lang => ''` or without translating term IDs.
    - Assuming a translated post's slug must be unique globally. Polylang Pro can share slugs by language.
    
    ## Verification
    
    Local source checked against:
    
    - API wrappers and return values: `wp-content/plugins/polylang/src/api.php`
    - Translation group storage and validation: `src/translated-object.php`
    - Post type and post translation behavior: `src/translated-post.php`
    - Term language, WXR group behavior, and term wrappers: `src/translated-term.php`
    
    ## References
    
    - Official documentation: <https://polylang.pro/doc/function-reference/>
    - Official documentation: <https://polylang.pro/doc/developpers-how-to/>
    - Verified source paths:
      - `wp-content/plugins/polylang/src/api.php`
      - `wp-content/plugins/polylang/src/translated-object.php`
      - `wp-content/plugins/polylang/src/translated-post.php`
      - `wp-content/plugins/polylang/src/translated-term.php`
      - `wp-content/plugins/polylang/src/crud-posts.php`
      - `wp-content/plugins/polylang/src/crud-terms.php`
      - `wp-content/plugins/polylang/src/translatable-object.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related