Custom Post Type slug and WPML gets 404 error

My solution is for a WordPress Installation (Version 5.2.2), for translation is only WPML Multilingual CMS (Version 4.2.7.1) installed.

The modules WPML String Translation (which is very performance-consuming) and WPML Translation Management are not required.

I’m using this WPML url structure: www.url.com/en/postname.

My Custom Post Type should have the slug “produkte” in German, “products” in English and “produkty” in Czech. In this example i’m using “produkte” as the custom post type slug of my source language, and “products” and “produkty” for the other languages. Replace this words with your slugs, add languages as you need.


First of all, assign a variable to the rewrite-slug of your custom post definition, not a fixed name (yes, that works…;-):

if( ICL_LANGUAGE_CODE == "de" ) $stt_product_slug = 'produkte';
if( ICL_LANGUAGE_CODE == "en" ) $stt_product_slug = 'products';
if( ICL_LANGUAGE_CODE == "cs" ) $stt_product_slug = 'produkty';

  register_post_type( 'stt-products',
    array( 'labels' => array( 'name' => 'Products', 'singular_name' => 'Product' ),
        'public' => true,
        'has_archive' => true,
        'supports' => array('title','custom-fields','editor','thumbnail','revisions','page-attributes'),
        'hierarchical' => true,
        'rewrite' => array( 'slug' => $stt_product_slug, 'with-front' => false )
    ));

Note: the custom post type registration has to be called in the ‘init’ hook.

Go to WordPress admin/permalinks and click “Save changes” without any changing: flush_rewrite_rules() is called once and so the rewrite rules for the slug-name of your SOURCE language (my case DE “produkte”) should be generated in the database table wp_options, option name “rewrite_rules”.

Go into the table wp_options and copy all the text of the option name “rewrite_rules”, “option_value” into a text editor (eg. Notepad++). The value of the database field is presented as a single line in the editor. For easier editing, replace all entries of

";s:

with

";\ns:

Find out, which rewrite rules are added for “produkte”. Eg. one entry pair will look like:

s:11:"produkte/?$"; s:32:"index.php?post_type=stt-products";

Each of this pairs should be transformed into:

add_rewrite_rule( '^produkte/?$', 'index.php?post_type=stt-products' );

Create a list with all properly formed rewrite rules. Then, replace at once “produkte” with “products”. Copy the whole list of rewrite rules, paste it beneath, and replace “products” with “produkty” and so on.

Then, you should have several blocks prepared looking like this:

add_rewrite_rule( '^products/?$', 'index.php?post_type=stt-products' );
.
.
.

and

add_rewrite_rule( '^produkty/?$', 'index.php?post_type=stt-products' );
.
.
.

Copy and paste this blocks beneath you custom post type registration in the ‘init’ hook.
Go to WordPress admin and press Permalinks/Save changes again (for writing the new rewrite rules in the database).

Now, the translated custom post type slugs should work.

If not, go into your database, table wp_options and check under the option_name rewrite_rules if the new rewrite rules are added. WordPress is rather picky about the correct syntax and will reject rules without notice if they are not well formed.


Additionally, you have to customize your language switch manually, eg:

function stt_wpml_language_switch() {
    $languages = icl_get_languages('skip_missing=0&orderby=KEY&order=DIR');
    if(!empty($languages)){
        echo '<ul>';
        foreach($languages as $l) {
            if( ICL_LANGUAGE_CODE == "de" ) {
                if( $l['code'] == "en" ) $l['url'] = str_replace( "produkte", "products", $l['url'] );
                if( $l['code'] == "cs" ) $l['url'] = str_replace( "produkte", "produkty", $l['url'] );
            }
            if( ICL_LANGUAGE_CODE == "en" ) {
                if( $l['code'] == "de" ) $l['url'] = str_replace( "products", "produkte", $l['url'] );
                if( $l['code'] == "cs" ) $l['url'] = str_replace( "products", "produkty", $l['url'] );
            }
            if( ICL_LANGUAGE_CODE == "cs" ) {
                if( $l['code'] == "de" ) $l['url'] = str_replace( "produkty", "produkte", $l['url'] );
                if( $l['code'] == "en" ) $l['url'] = str_replace( "produkty", "products", $l['url'] );
            }
            echo '<li>';
            if(!$l['active']) echo '<a href="'.$l['url'].'" class="stt-languageswitch-link">';
            if($l['active']) echo '<span class="wpml-ls-current-language">' . icl_disp_language($l['native_name']) . '</span>';
            if(!$l['active']) echo icl_disp_language($l['native_name']);
            if(!$l['active']) echo '</a>';
            echo '</li>';
        }
        echo '</ul>';
    }
}

Of course, all language-specific renderings of the translated custom post type slug (eg. in header.php, footer.php etc.) should be manually reformed in this manner.


Maybe this is a not so clean workaround, on the other hand, the setting of the rewrite rules seems to be correct.

Please leave a note, if you have better suggestions on this topic.