How can I rewrite custom post types slugs based on language?

alexhux picture alexhux · Oct 29, 2012 · Viewed 10k times · Source

I'm building a website with WordPress using it more like a CMS then a blogging platform. I made plenty use of custom post types and custom taxonomies. Last but not least I made it multilingual using WPML plugin.

During CPT declarations I wrapped strings and slugs in gettext in order to have them translation-ready within WPML.

An example of CPT declaration is as follows:

register_post_type('rooms',
    array(
        'label' => __('Rooms','valletta'),
        'description' => __('','valletta'),
        'public' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'capability_type' => 'post',
        'hierarchical' => true,
        'rewrite' => array('slug' => __('rooms','valletta')),
        'query_var' => true,
        'exclude_from_search' => false,
        'menu_position' => 25,
        'supports' => array('title','editor','excerpt','custom-fields','comments',),
        'taxonomies' => array('features','typology',),
        'labels' => array (
            'name' => __('Rooms','valletta'),
            'singular_name' => __('room','valletta'),
            'menu_name' => __('Rooms','valletta'),
            'add_new' => __('Add room','valletta'),
            'add_new_item' => __('Add New room','valletta'),
            'edit' => __('Edit','valletta'),
            'edit_item' => __('Edit room','valletta'),
            'new_item' => __('New room','valletta'),
            'view' => __('View room','valletta'),
            'view_item' => __('View room','valletta'),
            'search_items' => __('Search Rooms','valletta'),
            'not_found' => __('No Rooms Found','valletta'),
            'not_found_in_trash' => __('No Rooms Found in Trash','valletta'),
            'parent' => __('Parent room','valletta'),
        )
    )
);

As you can see I wrapped the slug term too and this (to me) make perfectly sense. What I wanted to achive was that when a user visits the "standard" website in Italian would reach a room CPT page via /camere/nome-camera/ while the English user would get it by /rooms/room-name/. This works smoothly for Italian language and WPML correctly translate slug for the English version too so if I'm viewing a room CPT in the Italian website and I switch to the English version, WordPress is taken to /rooms/ with the only problem that it shows a 404.

I checked WPML website and they admit that slugs translation is still a work in progress and is a functionality that will problably released with next version of the plugin. Ok, that's fine.

What I'm trying to achive then is an hack to use while WPML updates is plugin. My idea is to have some few custom rewrite rules in my .htaccess and have a URL rewrite of those CPT pages. I thought that I could remove slug translation in order to have a single slug for both languages and then have a rewrite rule that when someone types the translated slug gets instead the "standard" one but at least the content is correct and: a) the user won't notice anything since the browser wont't redirect the page but just rewrite the URL and b) nothing changes for WP (or it does??).

So the question is: how can I do all this? Removing slug translation is fine, just a delete on the WPML backend but I'm not very comfortable with rewrite rules and the like so I'm looking for someone to help me reach my goal and, eventually, show a better way to achieve what I need (and naturally have a feedback if my idea makes sense or not).

Sorry if I missed something. If you need more infos I'm here to give them.

Answer

Simon picture Simon · Oct 29, 2012

Basically, I think running flush_rewrite_rules() on every page load will let you localize the page slug the way you're intending, however that is an extremely poor solution since it will also write the .htaccess file on each page load which slow things tremendously and potentially giving really weird results once there is more than one user visiting the site. Looking at the documentation it appears you can supply false as the first argument, which disables writing of .htaccess, but this is still not a good solution as the documentation also states the following:

Important: Flushing the rewrite rules is an expensive operation, there are tutorials and examples that suggest executing it on the 'init' hook. This is bad practice. Instead you should flush rewrite rules on the activation hook of a plugin, or when you know that the rewrite rules need to be changed ( e.g. the addition of a new taxonomy or post type in your code ).

Each time I've had this problem myself I have ended up using a permlink structure that works for both languages, but that's not a very good solution either.

A good starting point might be checking out /wp-includes/rewrite.php and see if it is somehow possible to override the permalink for a given post type via filters, and return a slug according to the users language setting. Another option might be to try to add a second permalink for each language, so that each post in your custom post type is available at the permalink specified in register_post_type as well as through a permalink you specify elsewhere. You might be able to do this using WP_Rewrite, or by adding some mod_rewrite directives to you .htaccess. In the latter case, something like this might work:

$data = 'Rewrit­eRule ^/localized-type-name/(.*)$ /type-name/$1 [R,NC,L]';
// You might want to do a little more work on the actual rewrite rule, it's very much from the top of my head so it might not even work
insert_with_markers( ABSPATH . '/.htaccess', 'NAME OF YOUR MARKER', $data );
$wp_rewrite->flush_rules();

UPDATE: You might want to check out this thread.