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()
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);
Language cookie
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 and meta
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.