The Context
I recently worked on my first translation site for WordPress. We used the WPML (WordPress Multilingual) plugin. The client wanted to have all pages that had an associated translation automatically have users whom have set their browser language to be redirected to the translated page…every time.
There is a setting with the value Redirect visitors based on browser language only if translations exist
.
I mistakenly assumed this meant every time, set it and gave it a quick test and off I was to the next task!
How mistaken I was:
WPML will do this language redirect only once and then not redirect anymore. We do this to allow visitors to still switch languages. If we always push back to the browser’s language, visitors will not be able to manually switch languages after WPML redirects them to the language set by their browser.
This single-time redirect operation resets every 24 hours (which you can change too).
This meant the user would get redirected the first time they got to a translated page but any time they went back to that page or to another page with a translation they would not be redirected to the translation.
After going down some dead ends I finally came upon a simple (relative to what I had been trying) solution.
The Fix
Setup
You must have these settings in wpml found currently on the path in WP admin: /wp-admin/admin.php?page=sitepress-multilingual-cms%2Fmenu%2Flanguages.php
Under SEO options set Display alternative languages in the HEAD section.
checked and have Position of hreflang links
set to As early as possible
Set Browser language redirect
to Disable browser language redirect
The Code
The idea is to use the existing <link rel="alternate" hreflang="nl" href="https://example.com/nl/" />
that is output to each page for each language set to be used in WPML. We can compare the browser’s language code to the code in the <link>
and if it matches then redirect. This particular implementation doesn’t allow for locale’s like nl-BE
to have a fallback language match of nl
but that could be done within the js
.
We need to filter out the pages which the js
script is output to as the <link>
for a matching language will show even if the language doesn’t have a translation. It will just have the URL of the English version (original version). This particular detail may differ depending on how you specify certain settings, I haven’t tested it out as of yet, but if your site doesn’t act like this review settings and see if changing any get’s you to what you need to see.
function tbw_get_current_page_translated_languages(): array|bool {
if ( function_exists( 'icl_object_id' ) ) {
global $sitepress;
$current_page_id = get_the_ID();
$translated_languages = [];
// Get all the available languages
$languages = $sitepress->get_active_languages();
// Iterate through languages and check if the current page has a translation
foreach ( $languages as $language ) {
$translated_page_id = apply_filters( 'wpml_object_id', $current_page_id, get_post_type( $current_page_id ), false, $language['code'] );
if ( $translated_page_id ) {
$translated_languages[] = $language['code'];
}
}
return $translated_languages;
}
return false;
}
function tbw_header(): void {
global $post;
$current_page_translation_state = apply_filters( 'wpml_element_translation_type', null, $post->ID, $post->post_type );
$current_page_is_english_and_translated = $current_page_translation_state === 1;
$translated_languages = json_encode( tbw_get_current_page_translated_languages() );
// Output language redirection script if we have any translations for a language,
// possibly not the language the users browser is set to but the js script below will handle that part.
if ( $current_page_is_english_and_translated ) {
$redirect_script = <<<HTML
<script type="text/javascript">
const browserLanguage = window.navigator.language;
const alternateTranslationPage = document.querySelector('link[rel="alternate"][hreflang="' + browserLanguage + '"]');
const pagesTranslatedLanguages = '{$translated_languages}';
if(alternateTranslationPage !== null
&& window.location.href !== alternateTranslationPage.href
&& pagesTranslatedLanguages.includes(browserLanguage)
){
window.location = alternateTranslationPage.href;
}
</script>
HTML;
echo $redirect_script;
}
}
// low priority so it runs as soon as possible and higher in the head
add_action( 'wp_head', 'tbw_header', 1 );