There are basically two options when creating a multi-language website based on WordPress. One involves duplicating posts and pages by creating one different post for each language, wich is the approach followed by some plugins like WPML, Polylang or xili-language. Another approach is to introduce all the translations into the same post, separated by meta-tags within the content itself.
Note:
The issues and fixes described in this post are applicable to these plugin versions:
- Qtranslate 2.5.39
- Qtranslate Slug 1.1.7
Each translation is displayed in different tabs within the HTML editor of a single post. This is the case of Qtranslate, which happens to be the simplest solution from my point of view, as it is not necessary to modify the WordPress database in order to create relationships between a post or page and their translations, as well as among other WordPress objects like categories, tags, widgets, menus, etc. Therefore Qtranslate is my favorite choice when it comes to creating a multilingual website based on WordPress.
However, Qtranslate is not without complications as it has some bugs and lack of features that we need to overcome in order to make our site truly multilingual:
1. Qtranslate doesn’t support URL’s slug translation
The main impediment we find is that Qtranslate doesn’t translate our site’s URLs properly, which is a major limitation that can completely invalidate its use and force us to choose another alternative. Fortunately there is an additional plugin that will allow full URL translation: Qtranslate Slug. It supports not only posts and pages slug translation, but also categories and tags.
2. Qtranslate Slug causes duplicate content
However, Qtranslate does not solve another awkward problem: although our URLs are properly translated by Qtranslate Slug, when we request any post, page or category using the slug in our site’s default language, no matter what variable or subdirectory language we indicate, the same content is always displayed. So, being English our default language, if you request an URL like /es/article-name-in-english you’ll get exactly the same result as if you request /en/article-name-in-english, which is wrong, because the post’s address in Spanish is /es/nombre-del-articulo-en-espanol. The /es/article-name-in-english URL makes no sense and should always return a 404 not found error. However the same post in English is displayed with exactly the same content as in the case of /en/article-name-in-english, which results in duplicate content that Google and other search engines can index and even flag as duplicate content and create us problems.
The same applies to pages, categories and tags. For example, the address /es/category/programming shows the same content as /en/category/programming.
To fix this issue and force WordPress to display 404 errors when bad URLs are requested it is necessary to make some changes to the wp-content/plugins/qtranslate-slug/qtranslate-slug.php file of Qtranslate Slug plugin:
882 // <DLA> 883 if (!$post) 884 { 885 $query['name'] = 'dladladladla'; 886 return $query; 887 } 888 // </DLA> ... 900 // <DLA> 901 if (!$term) 902 { 903 $query['category_name'] = 'dladladladla'; 904 return $query; 905 } 906 // </DLA> ... 1068 /*} else { 1069 $last_part = array_pop($parts); 1070 $page_id = $wpdb->get_var( "SELECT ID FROM $wpdb->posts WHERE post_name = '$last_part' AND (post_type = '$post_type_sql' OR post_type = 'attachment')" ); 1071 1072 if ( $page_id ) 1073 return $page_id; 1074 }*/ ... 1560 /*if ( !$term && 'slug' == $original_field ) { 1561 $field = 't.slug'; 1562 $term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND $field = % s LIMIT 1", $taxonomy, $value) ); 1563 }*/
3. Pagination doesn’t work properly
When we are in an internal page of the blog, category or archived posts and we select another language from the change language widget the paging information is lost and always takes us to the main page, so we can never see the translated version of that page in other languages available. That is, if we point to /es/blog/page/2 and change language to English, the page /en/blog is displayed instead of /en/blog/page/2.
Error: Your Requested widget " ai_widget-6" is not in the widget list.
- [do_widget_area above-nav-left]
- [do_widget_area above-nav-right]
- [do_widget_area footer-1]
- [do_widget id="wpp-4"]
- [do_widget_area footer-2]
- [do_widget id="recent-posts-4"]
- [do_widget_area footer-3]
- [do_widget id="recent-comments-3"]
- [do_widget_area footer-4]
- [do_widget id="archives-4"]
- [do_widget_area logo-bar]
- [do_widget id="oxywidgetwpml-3"]
- [do_widget_area menu-bar]
- [do_widget id="search-3"]
- [do_widget_area sidebar]
- [do_widget id="search-4"]
- [do_widget id="ai_widget-2"]
- [do_widget id="categories-5"]
- [do_widget id="ai_widget-3"]
- [do_widget id="ai_widget-4"]
- [do_widget id="ai_widget-5"]
- [do_widget_area sub-footer-1]
- [do_widget id="text-4"]
- [do_widget_area sub-footer-2]
- [do_widget_area sub-footer-3]
- [do_widget_area sub-footer-4]
- [do_widget_area upper-footer-1]
- [do_widget id="search-2"]
- [do_widget id="recent-posts-2"]
- [do_widget id="recent-comments-2"]
- [do_widget id="archives-2"]
- [do_widget id="categories-2"]
- [do_widget id="meta-2"]
- [do_widget_area upper-footer-2]
- [do_widget_area upper-footer-3]
- [do_widget_area upper-footer-4]
- [do_widget_area widgets_for_shortcodes]
- [do_widget id="search-5"]
- [do_widget id="ai_widget-6"]
- [do_widget_area wp_inactive_widgets]
- [do_widget id="wpp-2"]
- [do_widget id="text-1"]
- [do_widget id="recent-posts-3"]
- [do_widget id="categories-3"]
- [do_widget id="archives-3"]
- [do_widget id="icl_lang_sel_widget-3"]
I solved it adding the following lines at the end of the filter_request() function in qtranslate-slug.php file, although there is probably a better way to achieve this if we dive deeper into qTranslate Slug’s code:
972 // <DLA> 973 // Add support for pagination to qtranslate slug widget 974 if (isset($query['paged'])) 975 { 976 if ($this->current_url == null) 977 { 978 global $q_config; 979 foreach ($q_config['enabled_languages'] as $lang) 980 { 981 $this->current_url[$lang] = '/'.$lang . str_replace('/'.qtrans_getLanguage().'/', '/'.$lang.'/', $_SERVER['REQUEST_URI']); 982 } 983 } 984 else 985 { 986 foreach ($this->current_url as $lang => $language_url) 987 { 988 $this->current_url[$lang] .= 'page/'.$query['paged']; 989 } 990 } 991 } 992 // </DLA>
4. Qtranslate generates wrong hreflang links
Another undesirable side effect caused by Qtranslate not supporting slug translation is that wrong hreflang links are created inside HEAD section within all site’s pages. Thus, the URL of this post you are reading would be rendered as /en/solucionando-problemas-qtranslate-slug/ instead of /es/solucionando-problemas-qtranslate-slug/ in the Spanish version, while in the English version would be rendered as /es/fixing-qtranslate-slug-problems/ instead of /en/fixing-qtranslate-slug-problems/, resulting in a lot of 404 page not found errors in Google Webmaster Tools.
This can be solved by making a small change to the wp-content/plugins/qtranslate/qtranslate_hooks.php file:
Before:
41 foreach($q_config['enabled_languages'] as $language) { 42 if($language != qtrans_getLanguage()) 43 echo '<link hreflang="'.$language.'" href="'.qtrans_convertURL('',$language).'" rel="alternate" />'."\n"; 44 }
After:
41 foreach($q_config['enabled_languages'] as $language) { 42 if($language != qtrans_getLanguage()) 43 // <DLA> 44 { 45 global $qtranslate_slug; 46 $language_url = $qtranslate_slug->get_current_url($language); 47 //echo '<link hreflang="'.$language.'" href="'.qtrans_convertURL('',$language).'" rel="alternate" />'."\n"; 48 echo '<link hreflang="'.$language.'" href="'.$language_url.'" rel="alternate" />'."\n"; 49 } 50 // </DLA> 51 }
5. Comments reply links doesn’t include language
Each post comment normally includes a “Reply” link wich is not filtered by Qtranslate in order to add the language variable or subdirectory to href’s URL. This causes that links that should be like /en/fixing-qtranslate-slug-problems/?replytocom=1868#respond appear like /fixing-qtranslate-slug-problems/?replytocom=1868#respond, wich also causes 404 errors on Google Webmaster Tools.
Fix qtranslate-slug.php file like this:
533 // <DLA> 534 add_filter( 'comment_reply_link', array(&$this, 'fix_comment_reply_link')); 535 add_filter( 'cancel_comment_reply_link', array(&$this, 'fix_cancel_comment_reply_link')); 536 // </DLA> 537 538 } 539 540 // <DLA> 541 /* 542 * fixes comments reply link adding language subdirectory to href URL 543 */ 544 function fix_comment_reply_link($link) 545 { 546 preg_match('/href=\'([^\']*)\'/', $link, $href); 547 548 return str_replace($href[1], qtrans_convertURL($href[1]), $link); 549 } 550 /* 551 * Fixes cancel comment reply link by adding language subdirectory to href URL 552 */ 553 function fix_cancel_comment_reply_link($link) 554 { 555 preg_match('/href=\"([^\']*)\"/', $link, $href); 556 557 return str_replace($href[1], qtrans_convertURL($href[1]), $link); 558 } 559 // </DLA>
6. Google XML Sitemaps v3 for Qtranslate doesn’t work with Qtranslate Slug
This problem causes wrong URLs are generated in our sitemap, which results in a sitemap riddled of wrong URLs returning 404 errors. I describe better this problem and its resolution in another article: Google XML Sitemaps v3 for Qtranslate doesn’t work with Qtranslate Slug.
5 comments
Join the conversationAlex - 25/10/2016
Hi Daniel, very interesting your article. I would try to customize qTraslate slug plugin as you have shown in your post.
In particoular, I’m interested in the first snipped of code: I don’t understand how to insert that code inside qtranslate-slug.php. At what point do I have to insert that code? And then, I see you have put some dummy text, what I have to put inside $query[‘name’] = ‘…’; for example?
The same thing, for other snippets.
Than you very much
Jörg - 30/04/2017
Hi Alex, thank’s for the detailed explanation but I’m having the same issue as Alex. Could you please provide us with an update? Thank you very much in advance!
Prihod - 13/06/2018
Hi Daniel!
Now this code
I need (without /ru)
Russian version by delault.
Can u help me?
Prihod - 13/06/2018
cut code _https://prntscr.com/jufzxx screenshot
Daniel - 02/08/2018
Sorry, but the version of Qtranslate in the article is now too old and I am not aware of the problems of the most recent versions, as I switched to WPML – The WordPress Multilingual Plugin.