Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Apr 14 2021
Apr 14

In November 2022, the Drupal community and the Drupal Security Team will end their support for Drupal 7. By that time, all Drupal websites will need to be on Drupal 8 to continue receiving updates and security fixes from the community. The jump from Drupal 7 to 8 is a tricky migration. It often requires complex transformations to move content stuck in old systems into Drupal’s new paradigm. If you are new to Drupal migrations, you can read the official Drupal Migrate API, follow Mauricio Dinarte’s 31 Days of Drupal Migrations starter series, or watch Redfin Solutions’ own Chris Wells give a crash course training session. This blog series covers more advanced topics such as niche migration tools, content restructuring, and various custom code solutions. To catch up, read the previous blog posts Custom Migration Cron Job and Migration Custom Source Plugin.

Most often in Drupal 8, your migration source will be a CSV file, JSON file, or another Drupal database. In some cases your Drupal website needs to coexist in a larger infrastructure. Thankfully, there are various modules for synchronizing Drupal with tools like Salesforce, Bynder, and GatherContent. However, not everything is as clean as those user-friendly modules. This article will dig into migrating data from a Microsoft server using Transact-SQL into a standard Drupal 8 website.

As with any database in Drupal, it starts in the settings.php file. The basic setup for a Transact-SQL database looks like this:

$databases['YOUR_DATABASE_NAME']['default'] = array ( 'database' => '', 'username' => '', 'password' => '', 'prefix' => '', 'host' => '', 'port' => '', 'namespace' => 'Drupal\\Driver\\Database\\sqlsrv', 'driver' => 'sqlsrv', );

“YOUR_DATABASE_NAME” is the key Drupal will use to reference this database, but it does not need to match the actual database name. The other credentials such as database, username, password, prefix, host, and port, need to be filled out based on your specific setup and server, but the last two keys are more general and refer to the type of database.
By default, Drupal uses a MySQL database, so the “driver” field is typically set to “mysql.” However, Drupal by itself does not know how to communicate with a Transact-SQL database, so just setting the “driver” to “sqlsrv” will throw an error

To provide that support, first install and enable the SQL Server module (sqlsrv). But the “drivers” folder in the SQL Server module needs to be accessed at the Drupal root folder (usually called “web” or “docroot”). There are two ways to do this:

  1.  Manually copy the “drivers” folder from the SQL Server module (modules/contrib/sqlsrv/drivers) into the Drupal root folder.
  2. Create a symbolic link (symlink) to the “drivers” folder from the Drupal root folder with a command like this “ln -s modules/contrib/sqlsrv/drivers/ drivers”. The symlink allows the module to update without manual adjustments. 

With the proper credentials and connections, Drupal will now be able to read from the Transact-SQL database.

Now the actual migration can be written. There is no core migration source plugin for this, so you will need to write a custom source plugin that extends DrupalSqlBase. Use Drupal’s dynamic query API to get the database's data, ensuring at least one field can be used as a unique identifier for each row. Once the source plugin is written, the rest of the migration will work as usual.

Mar 11 2021
Mar 11

In November 2022 the Drupal community and the Drupal Security Team will end their support for Drupal 7. By that time, all Drupal websites will need to be on Drupal 8 to continue receiving updates and security fixes from the community. The jump from Drupal 7 to 8 is a tricky migration, often requiring complex data transformations to fit legacy content into Drupal’s new paradigm. If you are new to Drupal migrations, you can read the official Drupal Migrate API, follow Mauricio Dinarte’s 31 Days of Drupal Migrations starter series, or watch Redfin Solutions’ own Chris Wells give a crash course training session. This blog series will cover more advanced topics such as niche migration tools, content restructuring, and various custom code solutions. See the first blog in the series Custom Migration Cron Job.

There are lots of tools built into Drupal 8 (D8) and Drupal 9 (D9) to assist with migrating from a Drupal 7 (D7) website. There is a whole suite of source and destination plugins that allow you to take data from any D7 node, file or user and migrate it into whatever D8 or D9 entity you want. But Drupal can’t account for every single data source you might have, so at some point in your migration you may hit a snag and need to write your own custom migration source plugin. Luckily, Drupal makes this straightforward.

If you don’t already have a custom migration module built, you can follow Mauricio Dinarte’s tutorial. Once you have that set up, go to your custom migration module and create the following nested folder structure for your custom source plugin:

your_custom_module/ ├─ src/ │ ├─ Plugin/ │ │ ├─ migrate/ │ │ │ ├─ source/ │ │ │ │ ├─ CustomSourcePlugin.php

Then create a new PHP file in the source folder.

Now we can write our plugin. If you’ve never written a source plugin before, you can use the d7_node source plugin for reference (this is the Drupal core source plugin for migrating nodes from a Drupal 7 database). Set up your namespace and underneath it add use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;. Then create a class that extends DrupalSqlBase with an @MigrateSource definition commented above it like so:

Note that your class name should be in CamelCase as usual, but the ID should be in snake_case. The ID is how you will reference your source plugin in a migration YAML file.

Inside your custom source plugin, you will need to create four public functions: public function query(), public function initializeIterator(), public function fields(), and public function getIds().

public function query()

Use this to query your source database (such as an old D7 website or whatever external database you are pulling data from). You will build your query using Drupal’s Dynamic Query API. In this example, I have a D7 website that uses an unlimited text field called field_footnote to add footnotes to a node. But in the new D8 website, I want each footnote to be its own entity, while making sure they each stay in the correct order on the correct page. This means I need to process footnotes one by one even though a single node can have several. I also need each footnote to know which node it came from and its order on the page, so the footnotes don’t get scrambled.

To handle this, the query is grabbing the “footnote” text field off all the nodes in the Drupal 7 source database with the entity_id, delta, and field_footnote_value fields selected:

$query = $this->select('field_data_field_footnote', 'f')->fields('f', [ 'entity_id', 'delta', 'field_footnote_value', ]); $query->condition('entity_type', 'node', '='); return $query;

This means that I process my old Drupal 7 field_data_field_footnote table row by row, turning each footnote into its own entity while keeping track of its parent (entity_id), order (delta) and value (field_footnote_value). This can be the trickiest step to get right because the whole source plugin depends on how the data is queried here.

public function initializeIterator()
The initializeIterator function is necessary to run the query and start the iterator. All you need is:

$results = $this->query()->execute(); $results->setFetchMode(\PDO::FETCH_ASSOC); return new \IteratorIterator($results);

If you need to set any constants for the migration, you can do that at this point as well: see d7_file.

public function fields()
The fields function lets you state the queried fields from the source table and provide labels:

return [ 'entity_id' => $this->t('Entity ID'), 'delta' => $this->t('Delta'), 'field_footnote_value' => $this->t('Footnote'), ];

public function getIds()
In the getIds function, you define which data field or fields will be used as unique identifiers. In my footnote example, multiple footnotes can be on the same node, so I need more than just the node ID to uniquely identify them. I also need to add the delta (the footnote’s order on the node).

return [ 'entity_id' => [ 'type' => 'integer', ], 'delta' => [ 'type' => 'integer', ], ];

The entity_id and delta fields will get translated as sourceid1 and sourceid2 respectively in the migrate_map table.

That’s all you need to create your custom source plugin. Now you can write a migration to use it. Remember that in your migration YAML file you will use the @MigrateSource ID that you set in the comment above the class, not the class name.

If you want some extra credit, you can add a fifth public function, prepareRow. This allows you to make data manipulations on each row before it gets sent to the rest of the migration. If I wanted to set a character limit on my footnotes I could use prepareRow to trim or flag any footnotes that are too long. This can also be done as a hook in your module file, hook_migrate_prepare_row.

Feb 25 2021
Feb 25

In November 2022 the Drupal community and the Drupal Security Team will end their support for Drupal 7. By that time, all Drupal websites will need to be on Drupal 8 to continue receiving updates and security fixes from the community. The jump from Drupal 7 to 8 is a tricky migration, often requiring complex data transformations to fit legacy content into Drupal’s new paradigm. If you are new to Drupal migrations, you can read the official Drupal Migrate API, follow Mauricio Dinarte’s 31 Days of Drupal Migrations starter series, or watch Redfin Solutions’ own Chris Wells give a crash course training session. This blog series will cover more advanced topics such as niche migration tools, content restructuring, and various custom code solutions.

There’s a certain leniency afforded to migrating content from an old Drupal 7 website to its new Drupal 8 rebuild. Preserving old content with all its outliers can be complicated and tedious. However, it is typically run only once in production then augmented and completed. Take notes for future migrations and move on. This leniency is not available for ongoing migrations that need to be run routinely to keep a website’s content up to date. This can be anything from a website's directory to up-to-the-minute news and events data where the data comes from outside the website and requires a custom migration. Here the code needs to work consistently and report errors gracefully.

To run these migrations on cron, there are a few different options. The easiest is to use a module like Migrate Cron Scheduler or Migrate Cron. Both of these allow you to quickly pick a custom migration to run on cron and choose its frequency. Migrate Cron Scheduler, however, allows the user to configure the “update” and “sync” flags on the migration import.

The "update" flag will update all previously imported entities. The "sync" flag will also remove any previously imported entities that are no longer in the source database, so that the source and destination are synchronized.

These modules also let you choose a migration group, so that the migration import will automatically respect migration dependencies. The downside is that any custom code that needs to be run in conjunction with the migration will have to be split into a different cron job, and there is no setting on these modules to run the migration at a certain time of day or at an irregular frequency.

For situations that require these additional features, install and configure the Ultimate Cron module. This is a powerful tool that can be used to fine-tune any cron job to an exact time and frequency. The combination of Ultimate Cron and a migration cron module will cover the majority of use-cases. However, if custom code or reporting is necessary immediately before or after the migration runs, then a custom cron job may be needed. To build this, go to your custom migration module and create a hook_cron function in the .module file. This file will need to use the following classes:

use Drupal\migrate_tools\MigrateExecutable; use Drupal\migrate\MigrateMessage; use Drupal\migrate\Plugin\MigrationInterface;

Exactly how you configure this hook will depend on your situation. For each migration that you want to run on this cron job, you will need to follow these steps. First create an instance of the migration plugin by feeding the migration’s id into the Drupal plugin manager: $migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);

Next, check the status of the migration. Often if there is an error, the migration will be hung in the “importing” state. Running a migration with the “importing” status will result in an error. To get around this, check the status and if necessary reset it to idle:

// Reset migration status. if ($migration->getStatus() !== MigrationInterface::STATUS_IDLE) { $migration->setStatus(MigrationInterface::STATUS_IDLE); }

At this point there are a couple options. You can leave the migration as-is and run a straight import where the content that has already been migrated will remain the same and any new content will be imported. Or set the “update” flag so that new content will be imported and existing content will be updated to match the source: $migration->getIdMap()->prepareUpdate();

Additionally, you can set the “sync” flag to remove any migrated content that no longer exists in the source database (e.g. an event that was cancelled). Note that this option requires at least migrate_tools version 5.

$migration->set('syncSource', TRUE);

Finally, create the MigrationExecutable plugin and run the migration:

$executable = new MigrateExecutable($migration, new MigrateMessage()); $status = $executable->import();

The “status” variable indicates whether the migration ran into any errors. If the status is empty, then there was an error and we can send a report and make custom adjustments to help the migration fail gracefully. Otherwise the migration ran fine.

Once the cron hook is ready, go to the Ultimate Cron jobs page (/admin/config/system/cron/jobs) and click the “Discover jobs” button. Your custom cron job should show up in the table, and you can click the “edit” button to fine-tune the exact timing. For updating or syncing migrations with hundreds of entities, the page may timeout if you manually hit the “run” button for your custom cron job. However, Ultimate cron version 2.x adds the drush commands cron-list and cron-run. Use cron-list to find your cron job id. Then use cron-run with that id and the “--force” flag to manually run your cron job for testing.

The Migrate Cron Scheduler or Migrate Cron module route is much faster to set up and is the better choice for quick, simple migrations that need to be run on the hour. However, for bigger, more complicated migrations that require additional code either immediately before or after the migration, or need to be run in the middle of the night, the Ultimate Cron module combined with a custom cron job provides the most customization and flexibility.

Jan 19 2021
Jan 19

As a designer and front-end developer, the infamous designer-developer handoff has often been between me and myself. Having spent a lot of time in both roles, as well as having worked closely with other designers and developers, I’ve learned a few practices that help me to answer developer questions right from the start.

Responsive Design

The first question to think about in the visual design stage is how a design will translate from a static idea to something people will use. In the real world, people are using all kinds of devices to interact with a website. There are a number of different use cases to think about here. For example, mobile users may be accessing a webpage without internet access, making an autoplaying video a concern for data usage. However, the most common and notable case to think about here is simply different screen sizes.

It’s pretty common to see mobile, tablet, and desktop designs handed off to a developer. If done well, these are generally enough for defining most of a website. Developers usually are working from 320px up, so it’s important to create mobile mockups at this size. From there we widen the browser to make sure the website still looks correct at the sizes in between what was provided in the mockups. The design for the mobile will be applied all the way up to one pixel below tablet size, and the tablet design will be applied up to one pixel below desktop (you may want to specify this for your developer).

For the most part, we want a consistent experience for each of these devices. If, for example, a user opens the website on an external monitor instead of their laptop, it shouldn’t look like a different website. That said, with some layouts the right breakpoint (browser width at which a component changes) is not right at one of the defined device sizes. As you’re designing, check if any of your components or page layouts need to switch at a more specific browser width. A good test of this is to create an artboard that’s a tiny bit smaller than tablet, and put everything in the mobile layout at that width. If anything looks off, consider defining a different breakpoint for that particular component.

Another case to consider is when the website gets extra wide. The last thing you want is to present the final website to your stakeholders and find the components in disarray on a big TV. Generally I’ll define up to two widths for this. One is the content maximum width, and the other is the website maximum width.

The content maximum width is the size at which the components (such as text or an image) on the website stop increasing in width but the backgrounds (such as a background color or image) keep going. If your content maximum width is particularly large, keep in mind how all your components look stretched to that width. For readability and accessibility of any text on the website, remember that optimal line length is 45-75 characters. 

This width may be all you need, but you can also define a website maximum width if you want something like an image background or full width slideshow to stop growing at a wider point. This may just apply to those full width components that are using images, or (less common in modern design) to the whole website. When deciding on whether or not full width components should keep stretching infinitely wide, think about how you want images to look on very large screens. Unless it’s a background image that is matching the height of that component, the wider an image gets, the more vertical space it will take. Also consider if you want large images to look clear at very large sizes or load quickly at normal and smaller sizes. Limiting the width an image component can reach is one way to strike a balance.

One last thing to define is the padding on the sides between tablet and desktop. When the width gets close to your content maximum width, how much padding stays on the sides as the content begins to shrink? I usually leave 16px between mobile and tablet, and 32px between tablet and the content maximum width (technically the content maximum width plus 64px so I don’t lose the padding too early).

Other common responsive considerations are typography and hover states. For typography, you’ll likely want to define heading sizes for mobile, tablet, and desktop. Also pay attention to line height, as well as spacing above and below a heading. For hover states, just remember that a mobile or tablet user can’t hover, so make sure none of the functionality is lost because of this.

Other tips

This covers most of the important points to keep in mind, but here are a few others:

Try to use a consistent base for sizing, preferably based on the body text size. I usually make my base font size 16px and then try to make typography and spacing stick with multiples of 4px.

Never type with caps lock. Always type in lower or sentence case and use your design software to transform the text to uppercase. This will help if you need to change that style without having to retype, and it allows developers (depending on your method of sharing designs) to copy and paste your text rather than retyping it. It also can be a good reminder to the developer that they should be using css to make text uppercase for accessibility (screen readers read text typed in all caps one letter at a time).

Make sure your images and text are flexible. If you have an image that perfectly lines up with the text overlaying it, be aware of how that component might be used and if the content editors will be changing the image or text. Similarly, be aware of the amount of text a content editor might add to any component. Character counts can always be limited, but it won’t hurt to supply that or show how the component should respond to having more text, especially if that text area is highly dependent on browser width.

One thing that can be easily overlooked is hover and focus states for any interactive elements of your website. For a website to be accessible, it has to be navigable with a keyboard. Focus states are what you see if you tab through a page, and if the difference isn’t notable it will be difficult to do this. Often, hover and focus states can be the same, but there may be some cases where you want to separate them. A form, for example, is an important place to have focus states defined, but you may not need hover states to work the same. Focus states that aren’t specified will generally be automatically provided by the browser, resulting in different appearances between browsers.

One last thing to establish while working with developers is a shared language. A paragraph or a block may mean something different to you as a designer then to a developer working in a content management system. Work together to make sure you have a common understanding of what some of these terms mean. A design system, or even a component library, is a great way to document this. We usually use Invision DSM to share a design system between designers and developers.


To review, remember the following things as you’re designing:

  • Design mobile at 320px
  • Define a content and website maximum width
  • Create font styles for mobile, tablet, and desktop
  • Use a consistent base for sizing, based on your body text size
  • Get in the habit of using your UI to transform your text to uppercase
  • Make images and text flexible whenever possible, and show components with varying amounts of content
  • Provide hover and focus states
  • Establish a shared language

When working with developers, it’s important to keep in mind the gaps between design and development when creating a website. While small design details may be obvious to you, a developer may be less aware of pixel distances and small color variations, focused instead on building the website in the best way possible. Ideally, design handoff is not one moment in a months long process, but a continual collaboration. Getting developer feedback while in the design process can save you from wasting time going down an unfeasible pathway, and getting across clear design patterns can ensure a developer makes the right assumptions from the beginning. Understanding a little of each other’s world can help make the whole process a lot smoother.

Jun 19 2020
Jun 19

In response to the COVID-19 pandemic, the Redfin Solutions team has shifted to working remotely. Because of the nature of the pandemic, some of our clients have tight deadlines in order to get information out to people as quickly as possible. A recent client with a special need for a quick turnaround is the Rural Aspirations Project, a non-profit in Maine committed to connecting parents and educators with resources, support, and networking opportunities provided by trusted local organizations. When the schools closed, parents and educators’ need for help grew exponentially. 

Rural Aspirations came to us with the idea for Community Learning for ME. The goal of this project was to create a tool for educators and families to find reliable resources specific to their needs. We knew that for this to be successful it was crucial to keep our focus on the user. At the same time, we had to finish the project within two weeks, including design and implementation, all while adjusting to the new normal of remote work.

User personas + User journeys

Our first step was to get acquainted with the user audiences. Luckily, those at Rural Aspirations are very connected with who their users are, as they work with them personally. In their documentation for the site, they had already included a mix of what we refer to as user personas and user journeys. These outline rough groups of people who will use the website, and the situation they might be in at that moment. 

One of our user personas was Caitlin, a mom of two kids, one in second grade and one in eighth. Her partner is an essential service worker and she has a job where she has to be in meetings during the day. She is overwhelmed by work and childcare but wants to help her kids more. 

Each persona allows the Redfin team to understand a little bit more about one type of person that might be using the site including basic information, the challenges they face, and their goals. We also wanted to understand how they might be using the site – the user journey. For instance, when Caitlin uses this site she wants to get to content for her kids quickly (she may be minutes away from an important meeting) and not have to spend time figuring out if it’s safe and quality content. She would also benefit from discovering that there are resources to help her, in case she wanted to come back when she has some time.

Screen Shot 2020-06-19 at 2.31.36 PM.png

User flow

Once we started wireframing (the first step in the design process where we map out different pages the website will need), we decided we needed to look at the user flow. The main difference between a user journey and a user flow is that the user flow takes a closer look at how the user interacts with the product once they are on the website. We looked at the main entry points of the website: the homepage and an individual resource, taking into account the possibility of these resources being shared on social media. The challenge was to orient the user, and then create a balance of quick access and discovery. We provided several paths from the homepage to the end goal of finding a resource, and included those filterable resource landing pages as the primary links in the global navigation. From an individual resource, we provided a link back to the filter landing page in order to create a loop from the filtered page to the different resources until the user found the right one for them.


Wireframes, annotated designs, and building in Drupal

One of the user personas that Redfin Solutions always anticipates is a content editor, so we kept the user experience in mind even while building the administrator side of the website. We used Drupal to create a content editor experience focused on clarity and ease-of-use, as well as automation, so that Rural Aspirations could focus less on updating the website and more on their jobs. We also gave contributing organizations the ability to add and update their own resources, making use of Drupal’s user permissions to limit them as needed.

One aspect of this project that made it successful was having key members of the team included in every step. We started meeting with our clients with a lead developer, a designer, and a project manager. Later in the building process we would involve others, but having both a design and technical architect from the beginning and throughout the entire project was crucial. It ensured we didn't waste time designing features that couldn’t be built in time, and that features were prioritized and adjusted based on the users’ needs.

The Redfin team was also able to test out some of our design processes and add new ones to make the most of remote collaboration. We used Invision to annotate and share designs, and we tried out Invision’s live whiteboard tool Freehand for the first time to create and share wireframes. Since we’re used to working together on a whiteboard at the office, this helped us to avoid designing in isolation and get quick feedback from each other.

Rural Aspirations’ main concern is helping people through this stressful and overwhelming time. As we worked on this project we realized the logic of sorting through these resources would be complex, but that its success would depend on making it all look simple, thus removing any extra stress from the user. With more time to spend on this project our next step would be to test the success of this tool with google analytics and live user testing, but for now we have created calls for feedback throughout the site. In times of crisis it is particularly important to focus on users first and building sites that relieve burdens.

Mar 26 2020
Mar 26

First, I want to thank everyone in the Drupal community for all you have done in the past to support each other and the Drupal project. I was fortunate when I started my Drupal journey back in 2011 to have been introduced to and embraced by the Drupal community right from the start. I have met so many incredible people who I now consider friends. My life wouldn’t be what it is today without so many of you. Thanks to the awesome community for that.

As one of the community-elected members of the Drupal Association Board of Directors, I am reaching out to the Drupal community for your support.

Please start by taking a few minutes to read the recent post by Dries Buytaert (founder and project lead of Drupal) and the recent post by Heather Rocker (Executive Director of the Drupal Association) regarding the uncertain times which the Drupal Association faces with DrupalCon Minneapolis due to the COVID-19 pandemic. These posts explain why it is so important for folks to step up now and help support the Drupal Association.

So how can the Drupal Community help?

What can an organization do?

What can an individual do?

What can everyone do?

  • Reach out to companies and organizations that you support that depend on Drupal and encourage them to join the Drupal Association as a Supporting Partner or to make a Donation. So many great organizations depend on Drupal for their websites and functionality, yet do not know that the Drupal Association exists or understand why supporting the DA is so important to the Drupal project.  (Template to potentially be used as a starting point for your email)
  • Reach out to those in your local Drupal communities to encourage folks to learn about how the Drupal Association supports the Drupal project and why it’s important for everyone in the Drupal Community to help the Drupal Association during these uncertain times.
  • Share this message on social media to help us reach as many folks as possible.

Thanks to all the incredible folks in the Drupal Community for your help. I look forward to seeing everyone online for now and can’t wait until we can meet in person once again. Stay safe and healthy!

Leslie Glynn
Drupal Association At-Large Board Member
Drupal.org - leslieg

Mar 18 2020
Mar 18

The Drupal 8 core Views module is a big part of why Drupal 8 websites work so well. It takes advantage of Drupal’s structure to create features from recommended content to directories and search pages. However, you can quickly run into complications when implementing a view, especially if your website is multilingual. So get ready to learn how to display multilingual Drupal 8 views of entities (such as nodes, media, and taxonomy terms) as well as entities indexed through the Drupal 8 Search API module.

Use case

A multilingual Drupal 8 website with these guidelines:

  • the default language is English
  • users can choose to view the site in French or Spanish regardless of available translations
  • if an entity does not have the requested French or Spanish translation, then display the English version

What could go wrong with multilingual Drupal 8 views? It starts with how views get data and how translations are stored in Drupal. The database contains separate rows for each translation of an entity, so when the view queries it without specifying a language, it can return duplicates of the same entity. However, if you filter by the page’s interface language, the database will only return entities translated in the page’s language. Additionally, if this is a view of indexed entities, then the language rendering options which fix the above issues are not available. Because of this discrepancy, regular entity views and indexed entity views need different solutions.

Regular entity views

If you are unfamiliar with adding a filter to a view, check out these instructions from drupal.org.

1. In the view edit page, find the FILTER CRITERIA section on the left side under the FIELDS section. Click ADD to get the FILTER CRITERIA pop-up.

2. Next, select Translation Language and click APPLY.

3. Then, in the next pop-up, select Site’s default language, and click APPLY again.

4. Back on the view edit page in the LANGUAGE section, click on the setting underneath Rendering Language. 

5. In the pop-up, change the select box to Interface text language selected for page and click APPLY.

6. Save your view!

This means that all the entities in your view will display in the requested language, but default to your default language so nothing gets left out. This filter also removes any duplicates, because the language is held constant during the query.

Keep in mind that if any entities in your view do not have a version in the default language, they will not show up, even if the page is set to the language they do have. This was not an issue for the example site as all the content started with English and was later translated, but this could create problems if your content is originating from multiple languages.

Indexed entity views

This was done using version 8.x-3.8 of the Search API Solr module. Since Search API Multilingual Solr Search was not merged into this module until 8.x-2.x, older versions of Search API Solr may not have this functionality (namely the “Language (with Fallback)” field which was added to fix this problem).

1. In the admin menu, go to Configuration > Search and metadata > Search API.

2. Edit the search index your view is pulling from and go to the Fields tab.

3. Click ADD FIELDS.

4. In this pop-up under Global fields, click ADD next to Language (with Fallback).

5. Save this configuration and re-index.

6. In your view’s filter criteria, add the Language (with Fallback) field and set it to Interface text language selected for page.

Using multiple languages complicates everything in Drupal, but the Drupal community is always developing patches, updates, and modules to make it easier. If you have a multilingual Drupal website in need of custom solutions, see our contact page to get in touch!

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web