Partially Multilingual Site and SEO

Between 2006 and 2010, I wrote a series of blog posts that I translated into Spanish. At the time, they were posted on a dedicated WordPress site, using a somewhat-hacked-together "Language Switcher" plugin that I'd found and fixed up so that it actually worked. But at some point, I decided that I didn't want to continue to maintain the plugin, and it was a bit klunky to use, so I migrated the content into my then-existing Drupal 6 poplarware.com site (using some custom code, along with the Drupal Migrate and WordPress Migrate modules -- I needed the custom code to deal with the multilingual articles).

This worked out fairly well, but I ran into a search engine optimization (SEO) issue: the blog posts had a language switcher block, to allow readers to view them in either Spanish or English. But once they had switched into Spanish, if they clicked through to other pages in the site (outside the blog), they would still be trying to view the site in Spanish, although the rest of the site was only in English. So, for example, if they went to the About page on the site, they might be on URL /es/node/2, whereas the URL (alias) for the page in English (with the same content) was /about. Having two URLs with the same content on the same site is not good for SEO; besides which, the Spanish URL was ugly, and the page header would have indicated that the page was in Spanish rather than its actual English, which is an accessibility bug.

So, in September, when I redid my web site using Drupal 8, I didn't include my old blog posts, not wanting to continue this SEO and accessibility problem. But that was only a temporary solution. What I really wanted:

All of the old English and Spanish blog content to be available. I put a lot of work into the Spanish translations, as well as the writing of the blog articles, and with recent political developments, they were seeming quite relevant again.

When on a page with both English and Spanish content (limited to my older blog posts), have links that let the viewer switch languages.

When on an English-only page, don't have a link for Spanish. The standard Language Switcher blocks provided by Drupal 8 show all the languages defined on the site, which would be confusing on an English-only page.

Redirect ugly and misleading URLs like /es/node/2 for pages that only exist in English, back to the English (aliased) URL for the page (like /about).

Content Migration

I had tried earlier to use the core Migrate module to migrate the Drupal 6 site to Drupal 8, and this didn't work. The issue I created at the time is still open, so I think it's probably still broken -- many of my pages came through with their latest revision completely blank... not very useful! So, to get the blog posts and tags imported to the new site, I did the following:

On a local development copy of the old Drupal 6 site, I used the Views module to make an RSS feed output containing the full content of all the blog posts, in both languages, including the tags.

On my live Drupal 8 site (after testing on a development site first!), I used a development version of the Feeds module for Drupal 8 to import the RSS feed. (I would have preferred to use a CSV export/import, but Feeds doesn't yet support CSV files in Drupal 8.)

After importing, I had to manually go through the posts and turn the Spanish ones into translations of the English ones. This was tedious, but I didn't have a huge number, so it was not impossible. (If I'd been able to use a CSV export/import, I would have been able to tell Feeds that the Spanish posts were translations of particular English ones, and this would have been handled automatically. Or, if the Drupal 6 to 8 Migrate module had worked, this also would have been handled. Alas!)

I also translated the tags to Spanish (they were created in English during the Feeds import), by copying the translations I had on my Drupal 6 site. Again, a manual process.

I uninstalled and removed the unsupported, development version of Feeds.

Languages Block

A bit of background information: When you create a Content view on a multilingual site, each output row in the view (unless you filter) represents a translation of a content item into one language (or the source language version).

The Block display uses fields. The only field is the Translation Language field, and it's set to "Link to the content" and "Display in native language".

There's a Contextual Filter (in the Advanced section of the Views UI) on the Content ID field, with "Provide a default value" selected, and "Content ID from URL".

Taken together, what this block does is that if you're on a page displaying one of my blog articles, it detects the content ID (contextual filter), finds all available language versions of that content item (views rows are individual translations), and for each one, displays a link to the content item, whose link text is the language. Using the standard Block Layout user interface in Drupal 8, I displayed this block on just the pages of my blog content type.

Redirecting

The last piece of the puzzle was the most fun to work out (yes, programming is fun!). I wanted a site visitor who had previously switched to Spanish to view a blog article, to be redirected back into English if they visited an English-only page on the site. The solution to this turned out to be a module with under 200 lines of code (including comments and help) -- fairly straightforward! The way it works is that in Drupal 8, near the beginning of the process of handling a page request, the embedded Symfony framework sends out a "Request" event, which allows modules to respond to the fact that there is a page being requested. (If you are interested in more details on this, I'm giving a talk next week at the Pacific Northwest Drupal Summit about this topic, and will be posting my presentation here afterwards. Or, you can read the Symfony documentation about the HTTP request framework.)

My module, in responding to this event:

Checks to see if the request is for a content item page (base URL node/12345 for example).

If so, loads the content item and finds out what languages are available for the content.

Compares the content language list to the language of the request.

If the request is for a language without a translation, the request is redirected to the source language for the content.

I've attached open-source code for the module in an archive below -- use at your own risk of course (just install the module; there is no configuration). If you're browsing the code for learning purposes, the two important bits are the partial_multi.services.yml file, which defines the service so that Symfony and Drupal know that this module wants to respond to the event, and src/EventSubscriber/PartialMultiRequestSubscriber.php, which is the class that implements the event listener.