Skip to main content
Signocore
// signocore multilanguage docs

Hooks

Every filter and action in Signocore Multilanguage with parameters and code examples for developers.

Signocore Multilanguage fires actions when languages and translation links change, and passes its lists, URLs and output through filters, so a theme or a small plugin can adjust them without editing the plugin. Add callbacks with add_filter() and add_action() from a plugin or a must-use plugin, or from your theme's functions.php for hooks that fire after the theme has loaded. Every hook name starts with signocore_ml_, except sml_copy_content_excluded_meta_keys.

The plugin boots on plugins_loaded. The integration actions fire during that boot, before any theme is loaded, so only a plugin can listen to them. The current language is detected on init at priority 1, and hooks that run before that see the default language as the current one.

Return the type listed for each filter. Most values go straight into typed plugin code, where a wrong type such as null causes a fatal error. For PHP functions such as signocore_ml_get_current_language(), see Template functions. The WPML filters the plugin answers are described in WPML compatibility.

No hooks match "".

Languages

These actions fire when a language is added, changed or removed under Signocore ML > Languages, in the setup wizard or during an import from WPML or Polylang. Languages are rows in the sml_languages table, described in the data model.

Fires after a language is added.

When the new language is not the default, posts and terms that had no language yet have already been given the default language when this fires, except while the setup wizard runs: the wizard gives them the default language chosen there. It also fires for the first language the plugin creates on its own, from the site language on a site that has no languages yet. signocore_ml_languages_changed fires right after it.

Parameter Type Description
$code string Language code of the new language, such as es.
$data array The values the language was saved with: code, locale, name, native_name, is_default, is_active, is_rtl, flag and sort_order.
add_action('signocore_ml_language_added', function (string $code, array $data): void {
    // Remind the site owner that the theme's footer texts need translating.
    wp_mail(
        get_option('admin_email'),
        'New site language: ' . $data['name'],
        sprintf('%s (%s) was added. Translate the footer texts under Signocore ML > String Translation.', $data['name'], $code)
    );
}, 10, 2);

Fires after a language is updated.

$data holds only the columns that were written: for example ['is_active' => 0] when a language is deactivated, ['locale' => 'es_MX'] or ['flag' => 'mx.svg'] from the language settings, ['slug' => 'espanol', 'previous_slug' => 'es'] when its URL segment changes, and ['domain' => 'example.es'] for each language when the domain mapping under Signocore ML > URLs is saved. The action only fires when a value actually changed, so saving the same values again does not fire it. A new default language fires signocore_ml_default_language_changed instead.

Parameter Type Description
$code string Language code of the updated language.
$data array The columns that were written, with their new values.
add_action('signocore_ml_language_updated', function (string $code, array $data): void {
    if (array_key_exists('is_active', $data) && (int) $data['is_active'] === 0) {
        // The language was deactivated: rebuild the theme's cached language menu.
        delete_transient('theme_language_menu');
    }
}, 10, 2);

Fires after a language is removed.

Only a language without posts or terms can be removed, and never the default language. A language with content can only be deactivated, which fires signocore_ml_language_updated. The language row is already deleted when this fires.

Parameter Type Description
$code string Language code of the removed language.
add_action('signocore_ml_language_removed', function (string $code): void {
    // The theme stores a footer text per language.
    delete_option('theme_footer_text_' . $code);
});

Fires after the default language changes.

The plugin listens to this action itself at priority 10 and gives the new default language to every translatable post and term that has no language yet. Content that already has a language keeps it. A callback at priority 11 or later runs after that assignment.

Parameter Type Description
$code string Language code of the new default language.
$old string Language code of the previous default language.
add_action('signocore_ml_default_language_changed', function (string $code, string $old): void {
    // Keep the theme's own setting in step with the site's main language.
    set_theme_mod('main_language', $code);
}, 10, 2);

Fires after any change to the languages: one is added, updated or removed, or the default language changes.

It fires right after signocore_ml_language_added, signocore_ml_language_updated, signocore_ml_language_removed and signocore_ml_default_language_changed, without arguments, for code that only needs to know that something changed. The cached list of active languages is already cleared when it runs. The plugin flushes the rewrite rules on this action.

add_action('signocore_ml_languages_changed', function (): void {
    // Rebuild the theme's cached language list on the next request.
    delete_transient('theme_language_list');
});

Translatable content

These filters decide which post types and taxonomies take part in translation. The plugin reads the lists many times per request, for every query, save and link it handles, and does not cache the result, so keep callbacks fast and free of database queries. The checkboxes under Signocore ML > General only list public post types and taxonomies, and they do not show what these filters add or remove.

Filters the post types that can be translated.

The list starts as the post types checked under Translatable Post Types, or every public post type on a site where that setting does not exist. A post type you add gets the translation box, the language columns and language filtering of its queries on the front end. Posts of that type that already exist get a language the next time they are saved, and count as default-language content until then.

Parameter Type Description
$postTypes string[] Post type names.

Returns string[] The post type names to translate.

add_filter('signocore_ml_translatable_post_types', function (array $postTypes): array {
    // Registered with 'public' => false, so the settings do not list it.
    $postTypes[] = 'testimonial';

    return array_values(array_unique($postTypes));
});

Filters whether a single post type can be translated.

Runs after signocore_ml_translatable_post_types, once for every registered post type, and only while at least one callback is attached. Use it to switch one post type on or off without rebuilding the whole list.

Parameter Type Description
$translatable bool Whether the post type is in the translatable list.
$postType string Post type name.

Returns bool Whether posts of this type can be translated.

add_filter('signocore_ml_is_translatable_post_type', function (bool $translatable, string $postType): bool {
    // One media library for all languages.
    if ($postType === 'attachment') {
        return false;
    }

    return $translatable;
}, 10, 2);

Filters the taxonomies that can be translated.

The list starts as the taxonomies checked under Translatable Taxonomies, or every public taxonomy on a site where that setting does not exist. Terms of a translatable taxonomy get a language when they are created, and get_terms() on the front end returns only the terms of the current language. Menus are translated separately and do not depend on this list.

Parameter Type Description
$taxonomies string[] Taxonomy names.

Returns string[] The taxonomy names to translate.

add_filter('signocore_ml_translatable_taxonomies', function (array $taxonomies): array {
    // A private taxonomy the theme shows on its FAQ page.
    $taxonomies[] = 'faq_topic';

    return array_values(array_unique($taxonomies));
});

Filters whether a single taxonomy can be translated.

Runs after signocore_ml_translatable_taxonomies, once for every registered taxonomy, and only while at least one callback is attached.

Parameter Type Description
$translatable bool Whether the taxonomy is in the translatable list.
$taxonomy string Taxonomy name.

Returns bool Whether terms of this taxonomy can be translated.

add_filter('signocore_ml_is_translatable_taxonomy', function (bool $translatable, string $taxonomy): bool {
    // Tags are shared by all languages on this site.
    if ($taxonomy === 'post_tag') {
        return false;
    }

    return $translatable;
}, 10, 2);

Translations

These actions fire when a translation link between posts or between terms is created, changed or removed, from the translation box, the language columns, the admin bar, a bulk action or AI translation. A translation is an ordinary WordPress post or term. The link is a row in the sml_translations table, and everything that translates the same content shares one group_id, as described in the data model. An import from WPML or Polylang writes the links directly and fires none of these actions.

Fires after a new translation of a post is created with the Create option.

The new post is an empty draft titled [ES] Translation of: {title}, owned by the current user. For an attachment, it is a copy that shares the source file and gets its title, caption, description, alt text and image metadata; duplicating an attachment also fires this action. The link is stored when this fires, and for posts other than attachments the fields under Synchronise Between Translations are copied to the new post right after it. Duplicating a post with its content fires signocore_ml_translation_linked and signocore_ml_post_duplicated instead.

Parameter Type Description
$newPostId int ID of the new translation.
$sourcePostId int ID of the post it translates.
$langCode string Language code of the new translation.
$groupId int Translation group both posts belong to.
add_action('signocore_ml_translation_created', function (int $newPostId, int $sourcePostId, string $langCode, int $groupId): void {
    // Start every new translation with the hero image of the original.
    $heroImage = get_post_meta($sourcePostId, 'hero_image', true);

    if ($heroImage !== '') {
        update_post_meta($newPostId, 'hero_image', $heroImage);
    }
}, 10, 4);

Fires after an existing post is linked as a translation.

Runs when you link a post from the translation box, for every duplicated post just before signocore_ml_post_duplicated, and with Pro for each variation of a duplicated WooCommerce product. The linked post takes the given language, even when it had another one before. For linked posts, the fields under Synchronise Between Translations are copied from the group's original right after this action; for duplicates they are not, because the copy already carries the source content.

Parameter Type Description
$targetPostId int ID of the post that was linked.
$sourcePostId int ID of the post it was linked to.
$langCode string Language code the linked post now has.
$groupId int Translation group both posts belong to.
add_action('signocore_ml_translation_linked', function (int $targetPostId, int $sourcePostId, string $langCode, int $groupId): void {
    // The theme caches the language versions of its landing pages.
    delete_transient('landing_page_versions_' . $groupId);
}, 10, 4);

Fires after a post is unlinked from its translations.

The post keeps its language and its content, and moves to a translation group of its own. Its notice that the source has changed is cleared.

Parameter Type Description
$postId int ID of the unlinked post.
$langCode string Language code of the unlinked post.
add_action('signocore_ml_translation_unlinked', function (int $postId, string $langCode): void {
    // The post no longer translates anything, so drop the note that named its original.
    delete_post_meta($postId, 'translated_from_note');
}, 10, 2);

Fires after the translation record of a permanently deleted post is removed.

Runs on core's before_delete_post, so the post itself still exists. It fires for every post of a translatable post type that is deleted permanently, originals included, and not when a post is moved to the trash. The other posts in its group stay linked to each other.

Parameter Type Description
$postId int ID of the post being deleted.
$elementType string post_ followed by the post type, such as post_page.
add_action('signocore_ml_translation_deleted', function (int $postId, string $elementType): void {
    if ($elementType === 'post_product') {
        delete_transient('shop_language_map');
    }
}, 10, 2);

Fires after a new translation of a term is created.

The new term is named [ES] Translation of: {name} and is ready to be renamed. It is created from the translation box on the term screen, the language columns, the admin bar, a bulk action, and by AI translation when the language has no term yet. The link is stored when this fires.

Parameter Type Description
$newTermId int ID of the new term.
$sourceTermId int ID of the term it translates.
$taxonomy string Taxonomy of both terms.
$langCode string Language code of the new term.
$groupId int Translation group both terms belong to.
add_action('signocore_ml_term_translation_created', function (int $newTermId, int $sourceTermId, string $taxonomy, string $langCode, int $groupId): void {
    // Give the new category the color of its original.
    $color = get_term_meta($sourceTermId, 'color', true);

    if ($color !== '') {
        update_term_meta($newTermId, 'color', $color);
    }
}, 10, 5);

Fires after an existing term is linked as a translation.

The linked term takes the given language, even when it had another one before.

Parameter Type Description
$targetTermId int ID of the term that was linked.
$sourceTermId int ID of the term it was linked to.
$taxonomy string Taxonomy of both terms.
$langCode string Language code the linked term now has.
$groupId int Translation group both terms belong to.
add_action('signocore_ml_term_translation_linked', function (int $targetTermId, int $sourceTermId, string $taxonomy, string $langCode, int $groupId): void {
    if ($taxonomy === 'product_cat') {
        delete_transient('shop_menu_' . $langCode);
    }
}, 10, 5);

Fires after a term is unlinked from its translations.

The term keeps its language and moves to a translation group of its own.

Parameter Type Description
$termId int ID of the unlinked term.
$taxonomy string Taxonomy of the term.
$langCode string Language code of the unlinked term.
add_action('signocore_ml_term_translation_unlinked', function (int $termId, string $taxonomy, string $langCode): void {
    if ($taxonomy === 'product_cat') {
        delete_transient('shop_menu_' . $langCode);
    }
}, 10, 3);

Fires after the translation record of a term that is being deleted is removed.

Runs on core's pre_delete_term, so the term itself still exists. It fires for every term of a translatable taxonomy that is deleted, originals included. The other terms in its group stay linked to each other.

Parameter Type Description
$termId int ID of the term being deleted.
$taxonomy string Taxonomy of the term.
$elementType string tax_ followed by the taxonomy, such as tax_category.
add_action('signocore_ml_term_translation_deleted', function (int $termId, string $taxonomy, string $elementType): void {
    if ($taxonomy === 'category') {
        delete_transient('theme_category_menu');
    }
}, 10, 3);

Duplication and sync

Duplicate creates a translation as a full copy of the source post: in the translation box, as a row or bulk action, from the admin bar and the dashboard, and when AI translation needs a draft to translate into. Its hooks run in wp-admin or in AJAX requests. With a Pro license and WooCommerce active, products are copied through WooCommerce, which keeps variations, prices and stock, so signocore_ml_pre_duplicate_post and signocore_ml_post_meta_to_duplicate do not run for products. Fields under Synchronise Between Translations are not pushed while a post is being duplicated.

Filters the ID of a copy made by your own code, in place of the built-in copy.

Return the ID of a post you created to take over the copy. The plugin then skips its own copy of the post, its meta and its featured image, but still removes any language record the new post got on save, gives it the translated terms of the source in each taxonomy the source has terms in, and links it as a translation. If linking fails, for example because the language already has a translation, the plugin deletes your post permanently. Return anything other than a positive integer to let the plugin copy the post. The value starts as null; the WooCommerce integration uses this filter at priority 10 for products, so check the value before replacing it.

Parameter Type Description
$newPostId int|null ID of a copy made by an earlier callback, or null.
$sourcePostId int ID of the post being duplicated.
$langCode string Language code of the copy.

Returns int|null The ID of your copy, or null for the built-in copy.

add_filter('signocore_ml_duplicate_post_handler', function (?int $newPostId, int $sourcePostId, string $langCode): ?int {
    if ($newPostId !== null || get_post_type($sourcePostId) !== 'event') {
        return $newPostId;
    }

    $source = get_post($sourcePostId);

    $copyId = wp_insert_post(wp_slash([
        'post_type' => 'event',
        'post_status' => 'draft',
        'post_title' => $source->post_title,
        'post_content' => $source->post_content,
    ]), true);

    if (is_wp_error($copyId)) {
        return null; // Fall back to the built-in copy.
    }

    // Only the meta an event needs; the built-in meta copy is skipped.
    update_post_meta($copyId, 'event_date', get_post_meta($sourcePostId, 'event_date', true));

    return $copyId;
}, 10, 3);

Filters the post data of a duplicate before it is inserted.

The array holds post_title, post_content, post_excerpt, post_password, post_type, post_author, post_parent, menu_order, comment_status and ping_status from the source post, and post_status set to draft. post_parent is the translation of the source post's parent in the copy's language, or 0 when the parent has no translation there. The values are unslashed; the plugin slashes the result and passes it to wp_insert_post(). Only runs for the built-in copy, so not when signocore_ml_duplicate_post_handler returned an ID.

Parameter Type Description
$postData array Arguments for wp_insert_post().
$sourcePostId int ID of the post being duplicated.
$langCode string Language code of the copy.

Returns array The post data to insert.

add_filter('signocore_ml_pre_duplicate_post', function (array $postData, int $sourcePostId, string $langCode): array {
    // Spanish copies go to the review queue instead of the drafts.
    if ($langCode === 'es') {
        $postData['post_status'] = 'pending';
    }

    return $postData;
}, 10, 3);

Filters the meta keys copied to a duplicate.

The list starts with every meta key of the source post, and each value of a listed key is copied. Some keys are never copied, even when they are in the list: keys starting with _edit_ or _sml_, and _wp_old_slug. Canonical URLs from Genesis, Yoast SEO and Rank Math are left out, and a primary category stored by Yoast SEO, Rank Math or under _primary_category is swapped for its translation. The featured image is set on the copy whatever the list holds, as its translation in the copy's language when one exists. With Elementor active, the plugin adds Elementor's own keys to the list at priority 10.

Parameter Type Description
$metaKeys string[] Meta keys of the source post.
$sourcePostId int ID of the post being duplicated.
$langCode string Language code of the copy.

Returns string[] The meta keys to copy.

add_filter('signocore_ml_post_meta_to_duplicate', function (array $metaKeys, int $sourcePostId, string $langCode): array {
    // View counters start from zero on every translation.
    return array_values(array_diff($metaKeys, ['post_views_count']));
}, 10, 3);

Fires after a post has been duplicated and linked as a translation.

The copy is a draft (or the status set through signocore_ml_pre_duplicate_post) with its meta, featured image and translated terms in place. signocore_ml_translation_linked has already fired for it. Not fired for attachments, which fire signocore_ml_translation_created.

Parameter Type Description
$newPostId int ID of the copy.
$sourcePostId int ID of the post that was duplicated.
$langCode string Language code of the copy.
add_action('signocore_ml_post_duplicated', function (int $newPostId, int $sourcePostId, string $langCode): void {
    // Hand Spanish copies to the Spanish editor.
    $editor = get_user_by('login', 'maria');

    if ($langCode === 'es' && $editor instanceof WP_User) {
        wp_update_post(['ID' => $newPostId, 'post_author' => $editor->ID]);
    }
}, 10, 3);

Filters the meta keys that are not copied when a translation copies the content of its source.

Runs when an editor clicks Copy content from in the translation box of a translation. The title, content and excerpt are copied, and every meta key of the source that is not excluded replaces the same key on the translation. Keys starting with _sml_ and canonical URLs from Genesis, Yoast SEO and Rank Math are never copied, and a primary category is swapped for its translation. The default list holds _edit_lock, _edit_last, _wp_old_slug, _wp_trash_meta_status, _wp_trash_meta_time, _wp_trash_meta_comments, _wp_trash_meta_reason, _encloseme and _pingme. Unlike the other hooks, its name starts with sml_.

Parameter Type Description
$excludedKeys string[] Meta keys to leave untouched on the translation.
$sourcePostId int ID of the post the content is copied from.
$targetPostId int ID of the translation the content is copied to.

Returns string[] The meta keys to leave untouched.

add_filter('sml_copy_content_excluded_meta_keys', function (array $excludedKeys, int $sourcePostId, int $targetPostId): array {
    // Keep the SEO title and description already written for the translation.
    $excludedKeys[] = '_yoast_wpseo_title';
    $excludedKeys[] = '_yoast_wpseo_metadesc';

    return $excludedKeys;
}, 10, 3);

Filters the fields kept in sync between a post and its translations.

The list starts as the fields checked under Synchronise Between Translations in Signocore ML > General, and applies to every translatable post type. The IDs are thumbnail, taxonomies, page_template, menu_order, parent, comment_status, sticky, post_date and custom_fields; other values are ignored. custom_fields syncs the keys under Custom Field Keys and is removed after this filter without a Pro license, so adding it here has no effect on the free plan. thumbnail sets the featured image's translation where one exists. The filter runs each time a translatable post is saved and when a translation is created or linked. Saving the original of a group pushes the fields to every translation the current user can edit; saving a translation never changes the original.

Parameter Type Description
$fields string[] Enabled field IDs.

Returns string[] The field IDs to sync.

add_filter('signocore_ml_sync_fields', function (array $fields): array {
    // Featured images are always shared between translations on this site.
    if (!in_array('thumbnail', $fields, true)) {
        $fields[] = 'thumbnail';
    }

    return $fields;
});

Fires after the synced fields were copied to a translation.

Fires once per translation: when the original of a group is saved, and when a translation is created empty or linked. The plugin writes the fields directly to the translation's columns, meta and terms, without wp_update_post(), so save_post does not fire for it; this action is the place to react to the change. It fires whenever at least one field is enabled, also when nothing changed.

Parameter Type Description
$targetId int ID of the translation that received the fields.
$sourceId int ID of the post the fields came from.
$fields string[] Field IDs that were synced.
add_action('signocore_ml_translation_synced', function (int $targetId, int $sourceId, array $fields): void {
    // Record when the translation last received fields from its original.
    update_post_meta($targetId, '_last_synced_from', $sourceId);
    update_post_meta($targetId, '_last_synced_at', time());
}, 10, 3);

Language switcher

The switcher block, widget, shortcode and signocore_language_switcher() render through the same code, so these filters reach all of them. The switcher added to menus under Signocore ML > Switcher uses the links but builds its own menu items. See Shortcodes, blocks and widgets for the options.

Filters the HTML of a rendered language switcher.

Runs for the block, the widget, the shortcode and signocore_language_switcher(), not for the menu switcher. $links is already reduced by skip_missing. The filter does not run when there is no language to show, and the switcher prints nothing then. The result is printed as is, so escape anything you add.

Parameter Type Description
$html string The switcher markup.
$args array The switcher arguments with defaults applied: style, display (resolved to native, name, code, short_code or none), show_flags, show_names, show_native_names, show_current, skip_missing and class.
$links array The language links in the switcher, in the shape described under signocore_ml_language_links.

Returns string The switcher HTML.

add_filter('signocore_ml_switcher_html', function (string $html, array $args, array $links): string {
    return '<nav class="site-languages" aria-label="' . esc_attr__('Language', 'my-theme') . '">' . $html . '</nav>';
}, 10, 3);

Filters the language links for the current page.

Every switcher reads these links: the block, widget, shortcode and function, the menu switcher and the language menu in the admin bar on the front end. Browser language detection also takes its redirect targets from them, so a language you remove is never a redirect target either. Each link has code, name, native_name, url, is_current, has_translation and flag_url. On a single post or page of a translatable post type, and on a static front page, posts page or shop page, a language with a published translation links to that translation, and a language without one gets has_translation set to false and a url that only swaps the language in the current address. Everywhere else every language counts as translated and gets such a swapped address. With the domain strategy, languages without a domain are left out.

Parameter Type Description
$links array One link per reachable active language, in the order of the languages.

Returns array The links, in the same shape.

add_filter('signocore_ml_language_links', function (array $links): array {
    // German is still being translated: keep it out of the switchers for now.
    return array_values(array_filter($links, fn (array $link): bool => $link['code'] !== 'de'));
});

URLs and SEO

How language URLs are built, which base slugs can be translated, and the hreflang tags in the page head. See Language URLs and Multilingual SEO for the settings behind them.

Filters a URL converted to another language.

The plugin converts a URL by swapping its language prefix, subdomain or domain, depending on the URL strategy; the rest of the address stays the same, and it does not look up a translated post or slug. The filter runs for every conversion: switcher links on archives and on pages without a translation, hreflang tags on archives, signocore_ml_get_url_for_language(), and the WPML filters wpml_permalink and wpml_active_languages.

Parameter Type Description
$result string The converted URL.
$langCode string Target language code.
$url string The URL that was converted, the current address when the caller passed none.

Returns string The URL in the target language.

add_filter('signocore_ml_url_for_language', function (string $result, string $langCode, string $url): string {
    // Do not carry campaign parameters into another language.
    return remove_query_arg(['utm_source', 'utm_medium', 'utm_campaign'], $result);
}, 10, 3);

Filters the base slugs listed under Permalink Translation.

Runs only when the Signocore ML > URLs screen is shown or saved, and decides which rows it offers. Each entry is keyed front, category, tag, author, post:{post_type} or tax:{taxonomy}, and holds label, slug (the base slug as registered), pro and, for post types and taxonomies, key (the name shown next to the label). The list holds the front of the permalink structure, the category, tag and author bases, and public custom post types and taxonomies that have a rewrite slug. Other keys are shown but have no effect on URLs. Whatever pro says, only the front, category, tag and author bases are translated on the free plan; every other key needs a Pro license.

Parameter Type Description
$slugs array Slug definitions keyed by slug key.

Returns array The slug definitions to list.

add_filter('signocore_ml_translatable_permalink_slugs', function (array $slugs): array {
    // Registered with 'public' => false, but it has public URLs under /events/.
    $slugs['post:event'] = [
        'label' => 'Events',
        'key' => 'event',
        'slug' => 'events',
        'pro' => true,
    ];

    return $slugs;
});

Filters whether the plugin prints hreflang tags on the current page.

Runs on wp_head at priority 1 on every front-end page, before the tags are built, so conditional tags such as is_singular() work. Return false when another plugin prints hreflang tags, or for pages that should have none.

Parameter Type Description
$enabled bool Whether to print the tags. Default true.

Returns bool Whether to print the tags.

add_filter('signocore_ml_hreflang_enabled', function (bool $enabled): bool {
    // Landing pages for ads are not meant for search results.
    return is_singular('landing_page') ? false : $enabled;
});

Filters the hreflang tags printed in the head of the current page.

Each tag is an array with hreflang, the language code in lowercase with a hyphen such as es or pt-br, and href. When the default language is among them, an x-default tag pointing to it comes last. A single post or page, and a static front page, posts page or shop page, lists only its published translations. A category, tag or term archive lists the terms it is linked to. Other archives list every language, except that author, date and post type archives leave out languages without published posts there, and a list with a single language is dropped. The list is empty, and the filter still runs, on 404 and search pages, on page 2 and later, on pages marked noindex through core's wp_robots filter, and on sites with fewer than two languages. Return an empty array to print nothing. The values are escaped on output.

Parameter Type Description
$tags array The tags, each an array with the keys hreflang and href.

Returns array The tags to print.

add_filter('signocore_ml_hreflang_tags', function (array $tags): array {
    foreach ($tags as $tag) {
        if ($tag['hreflang'] === 'es') {
            // Offer the Spanish pages to searchers in Mexico as well.
            $tags[] = ['hreflang' => 'es-mx', 'href' => $tag['href']];
            break;
        }
    }

    return $tags;
});

Visitor language

Which language a visitor ends up in, and what code runs in another language. The redirects are set under Visitor Language Detection and Missing Translations in Signocore ML > URLs; see Visitor language and fallbacks.

Filters the language a first-time visitor is sent to, based on the browser language.

Runs on template_redirect at priority 1 when Detect Browser Language is on and the request qualifies: a GET or HEAD page view without the language cookie, on an address that does not name a language, from a visitor that does not look like a crawler, on the front page only when the setting says so. The plugin tries the browser's languages in order of preference and matches each by code or locale, then by its first part, so de-CH finds German. The visitor is only redirected when the returned language is not the current one, the page is the front page, a single post or page, or a posts or shop page, and it has a published translation in that language. The JavaScript fallback for cached pages makes the same choice in the browser and does not run this filter.

Parameter Type Description
$target string|null Language code picked from the header, or null when none of the active languages matches.
$header string The Accept-Language request header.

Returns string|null A language code, or null to keep the visitor where they are.

add_filter('signocore_ml_detected_language', function (?string $target, string $header): ?string {
    // Visitors whose browser prefers Catalan see the Spanish pages.
    if ($target === null && str_starts_with(strtolower($header), 'ca')) {
        return 'es';
    }

    return $target;
}, 10, 2);

Filters where a visitor goes from a page that has no translation in their language.

Runs on template_redirect at priority 2 when Untranslated Page is set to redirect to the default-language page, and only for a GET or HEAD request that ends in a 404 in a language other than the default. The plugin looks up the same address in the default language and passes the permalink of that post when it is published and in the default language, or an empty string. Return an empty string to keep the 404. Any other URL gets a temporary 302 redirect through wp_safe_redirect(), which only accepts the site's own host and hosts added with core's allowed_redirect_hosts filter, and sends the visitor to the admin URL for any other host.

Parameter Type Description
$targetUrl string Permalink of the published default-language page, or an empty string.
$currentCode string Language code of the request.
$defaultCode string Default language code.

Returns string The URL to redirect to, or an empty string for the 404 page.

add_filter('signocore_ml_missing_translation_redirect', function (string $targetUrl, string $currentCode, string $defaultCode): string {
    if ($targetUrl === '') {
        return $targetUrl;
    }

    // Let the default-language page show a "not translated yet" notice.
    return add_query_arg('untranslated', $currentCode, $targetUrl);
}, 10, 3);

Fires after the current language is switched temporarily.

Fires on every call of signocore_ml_switch_language() and of the WPML action wpml_switch_language, and on the switches the plugin makes itself: to build switcher links on translated pages, the sitemap lines in robots.txt, signocore_ml_get_permalink(), the lookup for a missing translation, and WooCommerce emails in the customer's language. It can fire several times per request. Restoring the previous language fires nothing.

Parameter Type Description
$langCode string Language code switched to.
$previousLang string Language code before the switch, or an empty string when the switch happens before the language is detected on init.
add_action('signocore_ml_language_switched', function (string $langCode, string $previousLang): void {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log(sprintf('Language switched from "%s" to "%s".', $previousLang, $langCode));
    }
}, 10, 2);

Strings

Strings are texts outside posts and terms, such as widget titles, Customizer text or your theme's own labels, translated under Signocore ML > String Translation. Output a registered string with signocore_ml_translate_string() or the [sml_string] shortcode; see String translation.

Fires after the built-in string sources have been scanned, so you can register your own strings.

The scan runs when Signocore ML > String Translation is opened and the last scan is more than 12 hours old, and when you click Scan for strings there. It only runs with at least two active languages. The built-in scan covers the date and time formats, active widgets, Customizer texts and, with Pro, WooCommerce emails, payment and shipping methods and product attributes. Register your strings from here with signocore_ml_register_string.

add_action('signocore_ml_scan_strings', function (): void {
    $notice = (string) get_option('my_plugin_store_notice', '');

    if ($notice !== '') {
        do_action('signocore_ml_register_string', 'My Plugin', 'store_notice', $notice, 'line');
    }
});

Registers a string for translation. You fire this action with do_action(), and the plugin stores the string.

A new context and name is added to the String Translation list. When the context and name exist with another source text, the text is updated and existing translations are marked Needs Update; they are still shown until they are updated. The same text again changes nothing. Each call queries the database, so register strings from signocore_ml_scan_strings, on activation or when your settings are saved, not on every page load. The plugin fires this action itself for every string its scan finds, so a callback added with add_action() sees those too. Context and name can be up to 160 characters.

Parameter Type Description
$context string Group the string is listed under, such as your theme or plugin name.
$name string Identifier of the string within its context.
$value string Source text in the default language.
$type string line for a single line or text for longer text. Optional, default line.
// Register the text when your settings are saved...
add_action('update_option_my_plugin_store_notice', function ($oldValue, $newValue): void {
    do_action('signocore_ml_register_string', 'My Plugin', 'store_notice', (string) $newValue, 'line');
}, 10, 2);

// ...and print it in the visitor's language, for example in a template.
echo esc_html(signocore_ml_translate_string('My Plugin', 'store_notice', (string) get_option('my_plugin_store_notice', '')));

AI translation

AI translation needs a Pro license and an API key under Signocore ML > AI. See AI translation.

Filters the words and phrases that AI translation keeps as they are.

The list starts with the entries under Never Translate, split on new lines and commas. It is sent with every AI translation of a post, a term or a batch of strings, and the provider is told to keep these names exactly as written while translating the words around them.

Parameter Type Description
$glossary string[] Words and phrases to keep.

Returns string[] The words and phrases to keep.

add_filter('signocore_ml_ai_glossary', function (array $glossary): array {
    // Product names from the catalog stay in English in every language.
    return array_merge($glossary, ['Acme Cloud', 'TurboSync']);
});

Integrations

These actions fire while the plugin boots its integrations on plugins_loaded at priority 10, only when the other plugin is active. A theme's functions.php loads later and cannot catch them; check did_action('signocore_ml_woocommerce_loaded') there instead.

Fires after the WooCommerce integration is loaded.

Fires when WooCommerce is active and the site has a Pro license. By then the plugin has added its filters for translated shop pages, cart items, product duplication and order emails in the customer's language. See WooCommerce stores.

add_action('signocore_ml_woocommerce_loaded', function (): void {
    // Load this plugin's own WooCommerce language tweaks only when the integration runs.
    require_once __DIR__ . '/includes/multilingual-shop.php';
});

Fires after the Yoast SEO integration is loaded.

Fires when Yoast SEO is active and the site has a Pro license. By then the plugin has added its filters for translated titles, descriptions and canonical URLs, and for one sitemap per language.

add_action('signocore_ml_yoast_loaded', function (): void {
    // Yoast's sitemaps are built per language now; turn off this plugin's own sitemap.
    add_filter('my_plugin_sitemap_enabled', '__return_false');
});

Fires after the Rank Math integration is loaded.

Fires when Rank Math is active and the site has a Pro license. By then the plugin has added its filters for translated titles, descriptions and canonical URLs, and for one sitemap per language.

add_action('signocore_ml_rankmath_loaded', function (): void {
    add_filter('my_plugin_sitemap_enabled', '__return_false');
});

Fires after the Elementor integration is loaded.

Fires when Elementor is active, on every plan. By then the plugin makes sure duplicates keep Elementor's layout data, through signocore_ml_post_meta_to_duplicate.

add_action('signocore_ml_elementor_loaded', function (): void {
    add_filter('signocore_ml_post_meta_to_duplicate', function (array $metaKeys): array {
        // An Elementor add-on that stores its settings in its own meta key.
        $metaKeys[] = '_my_addon_settings';

        return array_values(array_unique($metaKeys));
    });
});

Lifecycle

Fires after the plugin is activated and its tables and default options are in place.

Fires in the request that activates the plugin, also when it is activated again later. The plugin has not booted in that request, so its template functions do not exist yet; guard calls with function_exists().

add_action('signocore_ml_activated', function (): void {
    // The theme's language menu depends on the plugin: build it again.
    delete_transient('theme_language_menu');
});

Fires after the plugin is deactivated.

Fires after the plugin has flushed the rewrite rules and deleted its transients. Content, translation links, languages and settings stay in place.

add_action('signocore_ml_deactivated', function (): void {
    delete_transient('theme_language_menu');
});

Stuck on something the docs don't cover?

Questions go straight to the developer who builds the plugins. Replies usually within a day.

September Sale

€20 off Signocore SEO Pro

Pay €49 instead of €69, one time for unlimited sites. code SEP20