Skip to main content
Signocore
// signocore multilanguage docs

Template functions

PHP functions for themes and plugins: current language, translations, language URLs and the switcher.

Signocore Multilanguage gives themes and plugins a set of PHP functions for the current language, translations, language URLs, the switcher and translated strings. This page covers each function, the query argument that turns language filtering on or off, the language cookie and the database tables behind it all.

Before you call a function

The functions are defined while the plugin boots on plugins_loaded at priority 10, and they only exist while the plugin is active. Theme templates and code that runs on init or later can call them directly. Code that runs earlier, such as the main file of another plugin, and any code that should keep working when Signocore Multilanguage is turned off, checks first:

if (function_exists('signocore_ml_get_current_language')) {
    $language = signocore_ml_get_current_language();
}

The current language is detected on init at priority 1. Before that, every function that depends on it works with the default language.

Language codes are lowercase, with a hyphen before a region: es, es-mx, pt-br. They stay the same when you give a language another URL segment, so use the code in your code and never the segment.

Element types

Several functions take an $elementType argument that says what the ID belongs to. They accept:

Value Meaning
post (default) A post of any post type, looked up from the ID.
term A term of any taxonomy, looked up from the ID.
A post type name, such as page or product A post of that type.
A taxonomy name, such as category or post_tag A term of that taxonomy.
A stored type, such as post_page or tax_category As stored in the data model.

A term ID passed with the default post is treated as a post ID, so always pass term or the taxonomy for terms. A value that matches no post type or taxonomy finds no translations, and its element counts as default-language content.

Languages

signocore_ml_get_current_language()

signocore_ml_get_current_language(): string

Returns the code of the language the current request is in, taken from the address, or from the language cookie when the address does not name a language. After signocore_ml_switch_language(), it returns the language switched to until you restore.

if (signocore_ml_get_current_language() === 'es') {
    get_template_part('partials/shipping-notice', 'es');
}

signocore_ml_get_default_language()

signocore_ml_get_default_language(): string

Returns the code of the default language, or en on a site where no language is set up yet.

signocore_ml_is_default_language()

signocore_ml_is_default_language(): bool

Returns true when the current language is the default language.

if (!signocore_ml_is_default_language()) {
    echo '<p class="notice">' . esc_html__('Some pages are only available in English.', 'my-theme') . '</p>';
}

signocore_ml_get_active_languages()

signocore_ml_get_active_languages(): array

Returns the active languages as objects keyed by language code, in the order they were added. Each object is a row of the languages table, with the properties id, code, slug, previous_slug, locale, name, native_name, is_default, is_active, is_rtl, flag, sort_order, domain and created_at. The values come straight from the database, so numbers such as is_default arrive as strings. The list is cached for a day and refreshed whenever a language changes.

foreach (signocore_ml_get_active_languages() as $code => $language) {
    printf('<option value="%s">%s</option>', esc_attr($code), esc_html($language->native_name));
}

signocore_ml_switch_language()

signocore_ml_switch_language(string $langCode): void

Makes another language the current one until you call signocore_ml_restore_language(). Language-filtered queries, signocore_ml_translate_string(), the locale filter and, on the front end, home_url() then follow the new language. Use it to fetch content or build links in another language, and always restore afterwards.

Parameter Type Description
$langCode string The language to switch to. It is not checked against your languages, so pass the code of an active language.

Switches nest: every call remembers the language before it, and each restore goes back one step. The function does not call core's switch_to_locale(), so translation files that are already loaded stay in the previous language. Each switch fires signocore_ml_language_switched.

signocore_ml_switch_language('es');

$latest = signocore_ml_get_posts([
    'post_type' => 'post',
    'posts_per_page' => 3,
]);

signocore_ml_restore_language();

signocore_ml_restore_language()

signocore_ml_restore_language(): void

Goes back to the language that was current before the last signocore_ml_switch_language(). Without an earlier switch it does nothing.

Translations

A translation is an ordinary WordPress post or term. The plugin links the posts, or the terms, that translate each other into a translation group, so these functions work with plain post and term IDs.

signocore_ml_get_element_language()

signocore_ml_get_element_language(int $elementId, string $elementType = 'post'): string

Returns the language code of a post or term. A post or term without a language record, for example one of a post type that is not translated, returns the default language.

Parameter Type Description
$elementId int Post or term ID.
$elementType string See element types. Default post.
$language = signocore_ml_get_element_language(get_the_ID());

signocore_ml_get_translation()

signocore_ml_get_translation(int $elementId, string $langCode, string $elementType = 'post'): ?int

Returns the ID of the post or term that translates the given one into a language, or null when there is none.

Parameter Type Description
$elementId int Post or term ID.
$langCode string Language code to look up.
$elementType string See element types. Default post.

The translation is returned whatever its status, including drafts and private posts. Asking for the element's own language returns the element's own ID. An element without a language record returns null for every language, its own included.

$spanishId = signocore_ml_get_translation(get_the_ID(), 'es');

if ($spanishId !== null && get_post_status($spanishId) === 'publish') {
    printf('<a href="%s">%s</a>', esc_url((string) signocore_ml_get_permalink($spanishId, 'es')), esc_html__('Read in Spanish', 'my-theme'));
}

For a term, pass the taxonomy:

$frenchCategoryId = signocore_ml_get_translation($categoryId, 'fr', 'category');

signocore_ml_get_translations()

signocore_ml_get_translations(int $elementId, string $elementType = 'post'): array

Returns every member of the element's translation group as an array of language code to ID, the element itself included, whatever their status. An element without a language record returns an empty array.

Parameter Type Description
$elementId int Post or term ID.
$elementType string See element types. Default post.
$links = [];

foreach (signocore_ml_get_translations(get_the_ID()) as $code => $postId) {
    if ($postId !== get_the_ID() && get_post_status($postId) === 'publish') {
        $links[] = sprintf('<a href="%s">%s</a>', esc_url((string) signocore_ml_get_permalink($postId, $code)), esc_html(strtoupper($code)));
    }
}

echo implode(' ', $links);

signocore_ml_has_translation()

signocore_ml_has_translation(int $elementId, string $langCode, string $elementType = 'post'): bool

Returns true when signocore_ml_get_translation() finds an ID for the language. That includes drafts, and the element's own language.

if (!signocore_ml_has_translation(get_the_ID(), 'de')) {
    echo '<p>' . esc_html__('This article is not available in German yet.', 'my-theme') . '</p>';
}
signocore_ml_get_permalink(int $postId, string $langCode): ?string

Returns the permalink of a post's translation in a language, built with that language's URL prefix, subdomain or domain and translated base slugs, or null when the post has no translation in it. It works for posts only; for a term, use signocore_ml_get_translation() and get_term_link().

Parameter Type Description
$postId int ID of any post in the translation group.
$langCode string Language code of the translation.

Like signocore_ml_get_translation(), it also returns a link for a translation that is not published, so check the status when that matters. The function switches the language for a moment to build the link, which fires signocore_ml_language_switched.

$url = signocore_ml_get_permalink(get_the_ID(), 'fr');

if ($url !== null) {
    printf('<a href="%s" hreflang="fr">%s</a>', esc_url($url), esc_html__('Version française', 'my-theme'));
}

URLs, switcher and strings

signocore_ml_get_url_for_language()

signocore_ml_get_url_for_language(string $langCode, ?string $url = null): string

Returns an address in another language by swapping its language prefix, subdomain or domain, following the URL strategy under Signocore ML > URLs. The rest of the address stays as it is: it does not look up a translated post or translated slugs, so for a post use signocore_ml_get_permalink(). The result passes through signocore_ml_url_for_language.

Parameter Type Description
$langCode string Target language code.
$url string|null The address to convert. Default null, the current address as the visitor requested it.
// The current search results in Spanish.
$spanishSearch = signocore_ml_get_url_for_language('es');

signocore_language_switcher()

signocore_language_switcher(array $args = []): string

Returns the HTML of a language switcher. It does not print anything, so echo the result. The function has the same options and output as the [signocore_language_switcher] shortcode, described in Shortcodes, blocks and widgets.

Key Type Default Description
style string dropdown dropdown, list, flags or inline. Any other value gives dropdown.
display string Empty Text for each language: native, name, code, short_code or none. When empty, show_names and show_native_names decide.
show_flags bool true Show a flag before each language.
show_names bool true Show text next to the flags. False shows no text when display is empty.
show_native_names bool true Use native names. False uses the English names when display is empty.
show_current bool true List the current language. The dropdown always shows it on its button.
skip_missing bool true Leave out languages without a published translation of the current post or page.
class string Empty Extra CSS classes for the wrapper.

Pass real booleans: unlike in the shortcode, the string 'false' counts as true here. The function returns an empty string when there is no language to show. The plugin loads the switcher's stylesheet and script on every front-end page, so the switcher works wherever you print it.

echo signocore_language_switcher([
    'style' => 'inline',
    'display' => 'short_code',
    'show_flags' => false,
]);

signocore_ml_translate_string()

signocore_ml_translate_string(string $context, string $name, string $default = ''): string

Returns a registered string in the current language.

Parameter Type Description
$context string The context the string is registered under, such as your theme or plugin name.
$name string The name of the string within its context.
$default string The source text. Default empty.

In the default language the function returns $default as it is, without reading the database, so always pass the source text. In another language it returns the saved translation, including one marked Needs Update, and falls back to $default when the string has no translation in that language or is not registered. Results are cached for the rest of the request. The function does not register the string: add it under Signocore ML > String Translation > Add a string, or with the signocore_ml_register_string action. The result is not escaped, so escape it for its context.

echo esc_html(signocore_ml_translate_string('My Theme', 'footer_cta', 'Book a free call'));

The [sml_string] shortcode does the same in content. See String translation for the screen where strings are translated.

Queries

On the front end, the plugin limits the main query to the current language when it queries a translatable post type, and it limits get_terms() for translatable taxonomies once the wp action has run. Secondary post queries, such as a WP_Query in a template or get_posts(), return posts in every language until you ask for filtering.

signocore_ml_get_posts()

signocore_ml_get_posts(array $args = []): array

Runs get_posts() limited to the current language. It takes the same arguments and returns what get_posts() returns, usually an array of WP_Post objects.

Parameter Type Description
$args array get_posts() arguments. The function sets sml_filter_language to true and suppress_filters to false.

In the default language, the result includes posts without a language record. In other languages it only includes posts that have that language, so use the function for translatable post types.

$related = signocore_ml_get_posts([
    'post_type' => 'post',
    'posts_per_page' => 4,
    'category__in' => wp_get_post_categories(get_the_ID()),
    'post__not_in' => [get_the_ID()],
]);

The sml_filter_language query argument

sml_filter_language is a query argument for WP_Query, get_posts() and get_terms() that turns language filtering on or off.

Value Posts (WP_Query, get_posts()) Terms (get_terms())
true Limits the query to the current language, also in wp-admin and for secondary queries. get_posts() also needs 'suppress_filters' => false. Limits the query to the current language, also in wp-admin and before the wp action.
false Turns filtering off for the main query when you set it on pre_get_posts at priority 11 or later. Returns terms in every language on the front end.
Not set Only the front-end main query is filtered. Filtered on the front end after wp, for translatable taxonomies.

Term queries for specific posts, with object_ids as get_the_terms() uses, are never filtered. Content without a language record counts as default-language content: it shows up in the default language and is left out of the others.

// A secondary query in the current language.
$query = new WP_Query([
    'post_type' => 'post',
    'posts_per_page' => 10,
    'sml_filter_language' => true,
]);

// Every category, whatever its language.
$categories = get_terms([
    'taxonomy' => 'category',
    'sml_filter_language' => false,
]);

// A post type archive that lists posts from every language.
add_action('pre_get_posts', function (WP_Query $query): void {
    if (!is_admin() && $query->is_main_query() && $query->is_post_type_archive('event')) {
        $query->set('sml_filter_language', false);
    }
}, 11);

The plugin remembers a visitor's language in a cookie named sml_language, which holds a language code.

Property Value
Name sml_language
Value A language code, such as es.
Lifetime Cookie Duration under Signocore ML > URLs, 365 days by default.
Path and domain WordPress's COOKIEPATH and COOKIE_DOMAIN.
Flags Secure on HTTPS, SameSite=Lax, readable by JavaScript.

The plugin sets the cookie on page views only when something reads it: when browser language detection is on, or when an address without a language prefix can be in any language, which is the case for directory URLs with the default language prefix shown. Then each page view stores the page's language. The switcher script also sets the cookie whenever a visitor clicks a language in the block, widget, shortcode or signocore_language_switcher(), and so does the JavaScript fallback of browser language detection. Browser language detection never redirects a visitor who has the cookie. Code that needs the current language should call signocore_ml_get_current_language() rather than read the cookie, because the address comes first.

Data model

Translations are ordinary WordPress posts and terms, each with its own ID, content, slug and meta. Signocore Multilanguage keeps the language of every post and term, and the links between translations, in its own tables, which are named with your table prefix, such as wp_sml_translations. Prefer the functions above; when you do query the tables, treat them as read-only, because the plugin keeps caches of its own.

sml_translations

One row per post or term that has a language.

Column Type Description
id bigint Row ID.
group_id bigint The translation group. Posts, or terms, that translate each other share one group_id, and a group holds at most one of them per language. A post or term that is not translated sits in a group of its own.
element_id bigint The post ID or term ID.
element_type varchar(60) post_ followed by the post type, such as post_post, post_page or post_attachment, or tax_ followed by the taxonomy, such as tax_category or tax_post_tag. Together with element_id it identifies the post or term.
language_code varchar(10) The language of the post or term.
source_language_code varchar(10) The language it was translated from, or NULL for the original of its group and for content that is not a translation.
status varchar(20) published or draft, set when the row is created and not updated afterwards. Read the post's own post_status instead.
created_at, updated_at datetime When the row was created and last changed.

Each post or term has one row at most. The plugin adds a unique index on element_id and element_type to enforce it, once any duplicate rows left by older versions are resolved. A post or term without a row counts as default-language content. Group IDs come from one counter, stored in the sml_next_group_id option.

To find the Spanish translation of a page by hand:

global $wpdb;

$table = $wpdb->prefix . 'sml_translations';

$spanishId = $wpdb->get_var($wpdb->prepare(
    "SELECT t.element_id FROM {$table} t
     WHERE t.language_code = %s
       AND t.group_id = (SELECT group_id FROM {$table} WHERE element_id = %d AND element_type = %s)",
    'es',
    $pageId,
    'post_page'
));

sml_languages

One row per language, active or not.

Column Type Description
id int Row ID.
code varchar(10) Language code, unique.
slug varchar(20) URL segment, when it differs from the code. Empty means the code is used.
previous_slug varchar(20) The URL segment used before the last change, so old addresses can be redirected.
locale varchar(35) WordPress locale, such as es_ES or es_MX.
name varchar(255) English name, such as Spanish.
native_name varchar(255) Native name, such as Español.
is_default tinyint(1) 1 for the default language.
is_active tinyint(1) 1 when the language is active.
is_rtl tinyint(1) 1 for right-to-left languages.
flag varchar(50) File name of the flag image, such as es.svg.
sort_order int Position in the language lists and switchers, set when the language is added.
domain varchar(255) Host name used with the domain URL strategy, or NULL.
created_at datetime When the language was added.

sml_strings and sml_string_translations

Registered strings and their translations.

Table Column Description
sml_strings id String ID.
context, name Where the string belongs and its name, up to 160 characters each, unique together.
value Source text in the default language.
language_code The default language when the string was registered or its source text last changed.
type line or text.
created_at When the string was registered.
sml_string_translations id Row ID.
string_id The id of the string in sml_strings.
language_code Language of the translation, unique together with string_id.
value The translated text.
status translated, needs_update when the source text changed afterwards, or needs_translation.
translator_id ID of the user who saved it, or NULL.
created_at, updated_at When the row was created and last changed.

Menus are not in these tables. A translated menu is an ordinary menu with two term meta keys: _sml_menu_group, shared by the menus that translate each other, and _sml_menu_lang, its language code. A translation whose original changed after it was saved gets the post meta _sml_source_changed_at, a Unix timestamp, and _sml_source_changed_by, the ID of the post that changed. With Pro and WooCommerce, an order stores the language it was placed in under the order meta key _sml_language.

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