Skip to content Skip to sidebar

This thread is resolved. Here is a description of the problem and solution.

Problem:
Generally it is expected that a Custom Post Type Archive will not be accessible if no posts have been translated yet. In this case the client tried to create a custom language switcher to solve the issue.
Solution:
For custom language switchers, note that you can use the hook wpml_active_languages to manually generate the correct URLs for your language switcher. Here's an example, but take note that you need to adapt the code to your needs. For example "post-type-name" "slug-name" would be the slug that you would like to translate and this hook will make it translatable:

function force_cpt_archive_language_urls($post_type = 'post-type-name') {
    $languages = apply_filters( 'wpml_active_languages', NULL, array(
        'skip_missing' => 0
    ) );
 
    if ( !empty($languages) ) {
        foreach ($languages as $code => &$lang) {
            // Manually generate the archive link for the CPT in this language
            $translated_slug = apply_filters( 'wpml_translate_single_string', $post_type, 'slug-name', "URL slug: $post_type", $code );
            $lang['url'] = home_url( '/' . $code . '/' . $translated_slug . '/' );
        }
    }
 
    return $languages;
}

Please note, that this is a custom solution and might not have been tested in every setup. It's beyond WPML's built-in functionality, so our support doesn’t cover debugging custom code. If this solution does not resolve your issue or seems irrelevant due to being outdated or not applicable to your case, we highly recommend checking related known issues at https://wpml.org/known-issues/, verifying the version of the permanent fix, and confirming that you have installed the latest versions of themes and plugins. If further assistance is needed, please open a new support ticket.

This is the technical support forum for WPML - the multilingual WordPress plugin.

Everyone can read, but only WPML clients can post here. WPML team is replying on the forum 6 days per week, 22 hours per day.

Tagged: 

This topic contains 5 replies, has 1 voice.

Last updated by andiG 6 months, 4 weeks ago.

Assisted by: Andreas W..

Author Posts
April 10, 2025 at 7:57 am #16916963

andiG

Background of the issue:
I am trying to implement a language switcher using do_action('wpml_add_language_selector') or by building it with the function icl_get_languages('skip_missing=0&orderby=code').

Symptoms:
When there are no posts available for a custom post type archive page, the language switcher links to the home page instead of the translated archive page. For example, it returns 'hidden link' instead of 'hidden link'. If posts are published, it works as expected.

Questions:
Why does the language switcher link to the home page instead of the translated archive page when no posts are available?
How can this issue be fixed so that the language switcher correctly links to the translated archive page?

April 10, 2025 at 12:24 pm #16918495

Bruno Kos
WPML Supporter since 12/2018

Languages: English (English ) German (Deutsch ) French (Français )

Timezone: Europe/Zagreb (GMT+01:00)

Hi,

What if you try something like this? Let’s say your CPT is called stellen. You can try something like this:

<?php
$languages = icl_get_languages('skip_missing=0&orderby=code');

if (!empty($languages)) {
    echo '<ul class="language-switcher">';
    
    foreach ($languages as $lang_code => $lang) {
        // Get the correct archive URL in this language
        $translated_url = apply_filters( 'wpml_permalink', get_post_type_archive_link( 'stellen' ), $lang_code );

        // Use WPML's native_name for label
        echo '<li>';
        echo '<a href="' . esc_url( $translated_url ) . '"';
        if ($lang['active']) {
            echo ' class="active-language"';
        }
        echo '>' . esc_html( $lang['native_name'] ) . '</a>';
        echo '</li>';
    }

    echo '</ul>';
}
?>

Does something like this work in your site?

Also, can you confirm if your custom post type is available in the other language (e.g., German)? And if you visit the translated archive URL directly, does it show the archive page or redirect?

When WPML generates archive URLs, it checks if there is at least one published post of that CPT in the target language.

April 10, 2025 at 12:57 pm #16918723

andiG

Hello Bruno

Yes, the CPT URLs exist in all languages, and all translated URLs can be accessed.

.../stellen
.../offeres-demploi
etc.

Your suggested code would work, but needs hardcoded CPTs in the function, where the navigation menu is generated. Could be a solution, but would need hardcoded stuff.

You mention this:
When WPML generates archive URLs, it checks if there is at least one published post of that CPT in the target language.

That sounds like the explanation of the cause. But honestly, I really do not understand this point of view in WPML. Why should it not be usefull to always link to the translated page? If posts are published or not is completly irrelevant. This is always better then linking to the Home, what is done right now.

Don't you think?

Best, Andi

April 11, 2025 at 4:48 pm #16923870

Andreas W.
WPML Supporter since 12/2018

Languages: English (English ) Spanish (Español ) German (Deutsch )

Timezone: America/Lima (GMT-05:00)

Hello,

I can not confirm this behavior on my WPML test site.

I have a category that is translated and has no posts assigned.

Original German: hidden link
Translation English: hidden link

This issue might be inside of the logic of your custom function.

Maybe you could provide us with some code examples?

Or it is influenced by the way how the taxonomy or CPT is registered. Did you use the theme or ACF PRO to register CTP and custom taxonomies?

Best regards
Andreas

April 14, 2025 at 8:07 am #16927676

andiG

Hello,

Okay, this is the function I'm using. I know that icl_get_languages() is deprecated, but do_action('wpml_add_language_selector') returns the same result. ACF Pro is used for the CPT.

function language_selector_flags($output_type = null){
if ( !function_exists('icl_get_languages') ) {
        return '';
    }
$languages = icl_get_languages('skip_missing=0&orderby=code');
    $ret = '';

if(!empty($languages)) {
ksort($languages);  //de sonst nicht an erster Stelle
$ret .= '<ul class="d-flex langs-public">';
                $i = 0;
                foreach($languages as $l){
                    
                    $css_active = ($l['active']) ? 'active' : '';
$icon = '';
                    $ret .= '<li><a class="'.$css_active.'" href="'.$l['url'].'">'.$icon.$l['translated_name'].'</a></li>';
                    $i++;
                }
                $ret .= '</ul>';
}

}

Thanks for checking!
Best, Andi

Screenshot 2025-04-14 at 10-04-30 Settings ‹ SASIS — WordPress.png
April 15, 2025 at 9:24 am #16932727

Andreas W.
WPML Supporter since 12/2018

Languages: English (English ) Spanish (Español ) German (Deutsch )

Timezone: America/Lima (GMT-05:00)

Indeed, I suggest you use this hook instead:
https://wpml.org/wpml-hook/wpml_active_languages/

But the issue is not really with the switcher code you were writing, but with how WPML generates the translated URL for a CPT archive.

Your code might rely on something like for example:

$translated_url = apply_filters( 'wpml_permalink', get_post_type_archive_link('stellen'), $lang_code );

But WPML may return the homepage or even false if there are no posts in that language and this would be expected.

Example:

$languages = apply_filters( 'wpml_active_languages', NULL, array( 'skip_missing' => 0 ) );

This would give you something like:

[
  'de' => [ 'url' => '<em><u>hidden link</u></em>', ... ],
  'fr' => [ 'url' => '<em><u>hidden link</u></em>', ... ],
  'it' => [ 'url' => '<em><u>hidden link</u></em>' ]  ← fallback to homepage
]

Because for 'it', there are no published CPT posts, so WPML gives up and just links to the homepage.

Here is what you could do:

function force_cpt_archive_language_urls($post_type = 'stellen') {
    $languages = apply_filters( 'wpml_active_languages', NULL, array(
        'skip_missing' => 0
    ) );

    if ( !empty($languages) ) {
        foreach ($languages as $code => &$lang) {
            // Manually generate the archive link for the CPT in this language
            $translated_slug = apply_filters( 'wpml_translate_single_string', $post_type, 'WordPress', "URL slug: $post_type", $code );
            $lang['url'] = home_url( '/' . $code . '/' . $translated_slug . '/' );
        }
    }

    return $languages;
}

Then try using it in your switcher like this:

$languages = force_cpt_archive_language_urls('stellen');

foreach ( $languages as $lang ) {
    echo '<a href="' . esc_url($lang['url']) . '">' . esc_html($lang['translated_name']) . '</a>';
}

Note: This is a custom solution and hasn’t been tested in every setup. It's beyond WPML's built-in functionality, so our support doesn’t cover debugging custom code, but we hope this helps point you in the right direction.

April 15, 2025 at 3:01 pm #16934906

andiG

I still do not really understand, why this WPML "feature" depends on number of posts. From my point of view, WPML simply should just return the translated slug.