Upgrade Your Drupal Skills

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

See Advanced Courses NAH, I know Enough
Sep 18 2019
Sep 18

As we noted in Mastering Drupal 8 Multilingual: Post 2 of 3, installing the Multilingual modules, enabling content translation and setting the configuration translation is really all you need to do in order to translate your site.  

However, the translation experience does leave a few things still to be resolved:

  • Views will show duplicate results for translations.
  • It is difficult to know what language the site is in on the admin side.
  • Interface translations can affect content edit forms to the point where it is nearly impossible to know which field you are editing.

Views translation duplicates

Out-of-the-box, any view (including the Admin Content view) will show what appears to be duplicate results for multilingual translated content. In reality, the duplicate content being registered by the view is the translated version of the node. If you are browsing the Admin Content view in English, clicking on any of the links to the node will take you to the English version, but it can be confusing to editors.

A simple solution to this is to enable the Translation Language filter and set the language to 'Interface translation selected for page.’ This will tell your views to only display the translations for the language the site is currently set to. 

When using this filter, you’ll also want to ensure that the Rendering Language is set also to be ‘Interface language selected for page’ so that the translations are displayed in the current language. 

Setting both the language filter and rendering language to the currently selected language, for all views that have translated results, will remove the duplicate results for translations and ensure that the correct content is always shown in the correct language.

It can also be helpful to add a language column to editorial views that have translated content. Displaying the language of the translation lets editors see the language of the node they are going to be editing—which can be very handy if there are languages on the site which editors are unfamiliar with. 

Current admin language

Another common issue for editors is not knowing which language is active when navigating the admin side of the site. When using Drupal provided languages the admin side of the site will often be partially translated, but the amount of admin interface translation depends on the language and the part of the site you are in.

For example, an editor starts in English and goes to the Admin Content view and clicks Add Node. They add a node, then click the Translation tab to create a Spanish version. They do the translation, save the node and see the node displayed in Spanish. Now, they want to add a link to the node in the main menu. 

They go to the main menu UI and often don’t realize that the active language is still Spanish. In the screenshot below, the only default indicator to let the editor know they are still in Spanish are a couple of admin toolbar links in Spanish and /es in the URL. 

This is because the interface translation for Spanish has not been done for the bulk of the menu UI. Even when creating the menu link, there are only a few description text strings, the Enabled checkbox and the Save button in Spanish. A sharp editor will catch that, but someone less familiar with the menu UI could easily miss these indicators. 

In our example, after adding their menu link, the editor clicks Save and then goes back to view the site. They see that there is now an English link to their new node on the Spanish translation because they unknowingly added the English link in the menu UI while the admin side was still in the Spanish language.

Providing an easy visual method for editors to detect the current language in the admin theme can be done by adding a bit of JS and CSS via a custom module or your custom admin theme. If adding via a custom module, you’ll want to be sure to add the library loading the JS and CSS files to admin pages only. 

The first thing to do is to detect the current language of the site. This can be done by reading the lang attribute of the tag. You’ll also want to fetch the current url using window.location.href. Set them both as variables.

Once the variables are defined, a simple if/else statement will create the visual tag. Since we’re creating a tag for the current language, we might as well add a link back to the default language with the tag.

Lastly, some path detection can be used to add a confirm message on edit forms when the user clicks to go back to the default language letting them know unsaved translation changes will be lost. A bit of CSS will fix the tag to the bottom of the screen.

Translated field labels on edit forms

The goal of a multilingual site is to provide translated content. However, having translated content on the admin side is not always a desirable thing—especially if your editors are not multilingual!

The screenshot below is part of the node edit form for a booklet node being translated to Spanish. Because Spanish is a Drupal-provided language, much of the admin interface is translated automatically when the language is added. Fields that have their labels translated for the end user in configuration translation will also show that translated field label to the editor.

An editor who is very familiar with the booklet node form, or one who remembers their two years of high school Spanish, may be able to puzzle out what the bottom two fields are, but what about a new editor who only ever learned some German?

Field groups are the best choice when it comes to making edit forms easier to understand, and can help give editors a clear picture of what they are editing. By grouping fields together and arranging them in the order in which they appear on the rendered node, the editing experience becomes more intuitive and tied to the content. This is helpful for both English editing and translations. 

Field groups are particularly key for translation. Field groups come with names shown only to the editor. By using a field group even for just single fields on a content type and naming the group just slightly different then the field it contains, the fields themselves can have translated labels for the end-user but the editor can still see the non-translated Field group label.

Aug 12 2019
Aug 12

Drupal already comes with 100 languages to choose from, and you can also add custom languages. This means that you can translate content into any language you can think of with no limitations.

To add a language, go to Configuration > Languages and click Add Language.  If you choose one of the default languages from the select list, Drupal will automatically fetch the most up-to-date interface translation files. These interface translation files automatically provide translations for a large part of the administration interface when viewed in that language.

Note: You will often see errors or PHP warnings when installing default languages. This is due to the difficulty of keeping 100 languages updated with all the changes between Drupal versions and compatibility with the vast sphere of contributed modules. Do not worry, the language will still work perfectly for content, configuration, and custom string translations.

If you choose to add a custom language (at the very bottom of the language list) you will be prompted to choose a language code and language name as well as the language direction you wish to use. It is very important that you use an approved language code. Approved language codes can be found in the Commerce Guys Address repository.

Using approved language codes will prevent fatal errors with Views and other possible bugs. The list of approved codes may not contain the specific code you want to use for the language prefix—often seen in the URL when viewing the site in a different language–but the prefixes can customized to display whatever you want to the end-user.

Once you add a language to the site, the Drupal language switcher block will become available to your list of blocks. Place this via block layout (or your own preferred method) to allow users to change the site language. The page header is a typical location.

After you've added all of the languages you need for the site, click on the 'Detect and selection tab' in the languages interface. The settings here will let Drupal know how to tell which language to display. Each method has pros and cons, depending on your site and user base. We prefer the URL method. Using this method will add the language prefix of the user-selected language to the URL and will keep the site in that language until a different language is selected or the prefix is manually removed from the URL.  

Configuring the URL method will allow you to choose the prefix you wish to use for each installed language. Using a custom prefix along with the language name you set means that you can use approved codes (as mentioned above) without being culturally insensitive to your users.

For example, on a recent project requiring Hmong translations, we did not have a pre-existing prefix. On the backend, we used the pre-existing Vietnamese prefix but rewrote it to display "hm" instead of "vi" (recognizing that these are two different languages). 

Language names can also be translated in the Languages interface. 

Aug 08 2019
Aug 08

Once the decision is made to build a multilingual site, it is time to start planning. Since multilingual impacts so many areas–project management, design, development, and content creation—the sooner you start planning, the better.

Some questions to ask:

  1. What languages are going to be used?
  2. Do you need to design for any right-to-left languages?
  3. Is the entire site going to be translated, or just certain sections or content types?
  4. Where are the translations coming from? Will they be provided by a service, by the client?

Right-to-left languages may provide an obvious design and layout challenge, but also remember that many languages use more words or much longer words than English. Leave some extra white space in the design of translated elements to account for this.

It will also be beneficial for site builders, developers and project managers to team up early to define any non-content translations needed. Non-content translations include things like Views filters, field labels, and text set in Twig templates or modules that cannot be accessed by site editors. These translations will have to be provided by the client and then added by developers.

Developing a spreadsheet or list of non-content translations, and providing that to the client or translation service early in the project, will allow site builders and developers to add translations to the site as they become available. Try to make sure, however, that these translations are finalized before adding them because each change to base language configuration or interface text is compounded on a multilingual site. The amount of work for each change is multiplied by the number of languages that change needs to be translated into.

Jan 10 2018
Jan 10

The next thing we need is a way to display search results. For that, we’ll create a custom view page.

Go to /admin/structure/views/add and add a view. We named ours Site Search. Make sure that in the view settings the Show select list is set to Index Content and the box to Create a Page is checked.

Add Fields to Show as the View Results

The fields you show in each row of your results are specific to the datasource. For instance, to add the node title, you’d choose Title for the Content datasource. We usually use just the node title and body field for results, but any field on can be added as long as a field on its parent datasource was indexed. For instance you can add a banner image field from a node content type to your results because you indexed the node's title field.

You can filter by more fields than what you are showing so if you have more indexed fields, you can still get a solid keyword search but only show the results field you want—similar to the Drupal core Search excerpt.

We also recommending linking the title to its node, trimming the body field and stripping out HTML tags using the rewrite option for the body field. NOTE: If you are using the highlight processor in your index, you’ll want to use excerpt as the field you want to show. The Highlight processor needs an excerpt to work.

Create Your Custom Search Filters

Since we want to use this search in the same ways as Drupal’s core Search, we’ll create the same sort of keyword filter. Remember when we changed all the fields in our index to Fulltext? Guess what filter we are going to use?

The Fulltext search filter performs the same function as Drupal’s core Search filter by searching all of the fields assigned to it at once. This is similar to Views Global Combined filter except that filter reads the view results instead of the search index, so it would not use the query processors outlined in Search API nor the indexed field data. Using a Global Combined filter would return an error instead of search results.

You can set the Fulltext search to work how you want, but we prefer to run it to contain all of the keywords a user puts in and to parse them as a single string. In this way a search for green eggs would return more specific results because the result would have to contain the phrase green eggs rather than just the word green and/or eggs.

For searched fields, set the filter to all of the fields you have set in your index. There is no reason to index fields if you are not going to allow a filter for them (unless you are doing multiple Fulltext Search filters on different fields—for instance, a keyword filter that looks at title and body fields and categories filter rather than looks at a term reference field. The minimum word length should be set to the same minimum word length assigned in your index.

Set Your Sort for the Results

We recommend sorting your results by relevance. If you do so, make sure you choose descending so you get the results with the highest relevance first. You can also sort by any of the fields you added if you want to sort results alphabetically by title or something.

Configure the Rest of the View

We usually use a pager set to 6 for the results and enter some helpful No Results Behavior text for when a user’s keyword string is not found. Set the path to something that makes sense (/search or /site-search).

Most importantly, you want to go to Exposed Form in the Advanced settings section and change Exposed form in block to Yes. This is how you get a search filter block to replace the core Search block we lost when we disabled that module. The Exposed form style should be set to Input required so that a user doesn’t see any results before they have searched for something. We also like to set some helpful Text on demand text in case someone gets to the search page before they have searched for anything. Including a reset button and changing the text for the submit button to ‘Search’ is also good UX.

Pro Tips:

  1. Your view must be set to be a view of Index Content in order for it to read the index you just created. Each field you want to show in results must be an indexed field and if you are using the Fulltext search filter, all of your fields in your index must be set to Fulltext.

  2. If using the highlight processor, use Excerpt as the field you show for results.

  3. If you want global search block, you’ll need to set Exposed form in block to Yes.

Dec 14 2017
Dec 14

Drupal 7 views were about the best thing ever and, thankfully, they’ve been included in Drupal 8 core. Strangely though, we find ourselves using less and less of them in Drupal 8 because of the ease and power of Twig.

We did notice that views are by far the most difficult thing to customize through Twig that we’ve found in D8. It's difficult to bring in content from one views field into another in a views field twig template—although it can be done with Twig in the Views UI rewrite. If we need to customize multiple fields on a view, we usually find ourselves rewriting the views display views-views-unformatted--myview--mydisplay.html.twig template—which can be an overkill if we are only customizing a couple of fields on the view.

We recently found that Views UI rewriting and the global custom text field stripped the audio formatting of a field we needed to group into a custom HTML structure with a couple of other fields. Our only choice was to render the field with the audio formatter and write the HTML needed around it and the other fields in the view display template itself. 

We looped through the rows and found the row.content[‘#view’].style_plugin.render_tokens[ loop.index0 ] to get the field output. That gave the custom display template the rendered output of each field in the view—basically creating exactly what the view outputs without the views-field div wrappers. We could then print the rendered fields in the template in any order and with any HTML structure needed and the necessary rendered formatting.

If empty with views custom templates and Twig debugging

In the example above, we have some fields that we know are going to be constantly populated and some that aren’t. Since we’re writing custom HTML around the view fields, we need to use Twig’s {% if %} functionality to make sure that HTML is printed only when there is content to fill it.  Normally it is very easy in Twig to write an if statement to include things if there is content to fill them. With views templates however, things we're not quite so easy. We tried hiding the rewriting on the field in Views UI. We tried hide if empty in the Views UI. We tried creating a views field template for that field and using a Twig if statement there to see if a lower level hierarchy or more specificity than just the rendered field in the display template would work.  

Finally, we realized that Twig debug adds content to the view.  All of that green ← THEME DEBUG → code you see in your browser inspector when debug is enabled counts as content to views. You can beat that by using Twig's |striptags|trim filters on your rendered views field output. For some fields though, you need to preserve some tags and Twig's autoescape function likes to print the tag as content in the views display template.

In the example above, we're printing the rendered body field of the podcast episode which can have HTML added by an editor. We could probably have set up some sort of Twig {{ autoescape false }} in the views field template for the body (by the time its rendered in the display template, its too late) and done it that way, but since debugging should only be used on local development sites, why bother? Turning debugging off in the services.yml file makes the if statements work as intended so we know that they will function properly when they get to production.

Dec 13 2017
Dec 13

After setting up aD8 site and going into the theme folders for Bartik and Classy you'll notice that there are lot of templates in D8. This is because Drupal 8 really takes advantage of Twig’s template inheritance capabilities. Twig allows templates to be extended, included and embedded into other templates which allows for a very atomic approach to site building. Extends, Include, and Embed are quite similar, but there are some key points to be aware of.

Use Extends to inherit a parent twig template

Extends brings in the parent template (the one being extended) into the child template. No content outside of blocks defined in the parent template will be allowed in the child template. In a parent paragraphs template (paragraph.html.twig) there are some default classes and html structure you'll want to set that will be used in every paragraph bundle you create:

In a child text paragraph bundle (paragraph--text.html.twig) you would extend the parent template (paragraph.html.twig) and switch out the block content so the child gets the classes you want from the parent but uses its own content.

Since the parent template is being inherited with extends, all content in the child template must be inside a block defined in the parent. Content outside of parent blocks (as shown below) will not be allowed and will throw a Twig error on your site.

Use include to insert one template into another

Include also inherits content of a file, but also allows variables from the current template to replace those from the parent template. Include also differs from extends in that template you are including is included directly where the call is placed in the child. Think of it this way—extends is like bringing a parent template into a child template and changing some of the content of the parent in the child. Include brings a child template into a parent by inserting the entire child template into the parent a specific point. You could also insert an entire parent into a child and replace variables but not content.

The biggest advantage of this in my opinion is including templates from outside a Drupal site into a Drupal site or from one entity type to another. We use Pattern Lab as part of our approach to theming but while our Pattern Lab instance lives inside our Drupal theme it is technically outside of the Drupal site structure so it cannot use variables like {{ content.field_long_text }}. It even has a different suffix for files: paragraph.twig instead of paragraph.html.twig.

To get around that we use include instead of extends and use the ‘with’ function to replace variables from Pattern Lab in Drupal. The parent template paragraph--text.twig in Pattern Lab extends the Pattern Lab paragraphs.twig template to get the html structure then adds example {{ text }} variable content defined in a yml file. To get the final Pattern Lab example output.

Then in Drupal, the paragraph--text.html.twig file uses include to bring in the entire content of the Pattern Lab paragraph-text.twig file and replaces the {{ text }} variable with the Drupal output of the long text field in my text paragraph bundle {{ content.field_long_text }}.

The Drupal template uses the Component Libraries module to find the Pattern Lab Twig template and includes all the Pattern Lab structure but uses the Drupal text field for content.

Embed a twig template to replace variables and override parent blocks

Embed combines include and extends. You can embed a template in another template and replace the variables using 'with' and switch out block content from the child template you are embedding into the parent (or vice versa).

An example of this would be creating paragraphs demo content in Pattern Lab and bringing it into Drupal. Since Pattern lLb doesn’t have modules, we have to hand build the paragraph's HTML structure then exclude it in Drupal (since we don’t want the hand-built paragraphs html structure and Drupal’s).

In Pattern Lab the text.twig template gives basically the same HTML structure that the final Drupal paragraph bundle does but uses some dummy text from a yml file.

The Drupal template paragraph--text.html.twig both extends and embeds templates. It extends the Drupal paragraph.html.twig to add bring global classes and html structure into the text bundle template and it embeds the Pattern Lab text.twig template in place of the parent paragraph.html.twig content block while replacing the {{ text }} variable with the Drupal {{ content.field_long_text }} variable. Adding comments to the plwidgetopen and plwidgetclose blocks prevents them from printing the paragraph structure created in Pattern Lab.

With the setup above, you can theme the Drupal text bundle directly in Pattern Lab and have whatever structure is built around the text variable be reflected both there and around the Drupal long text field as well.

Pro Tips:

  1. It is often common to get ‘template not defined’ errors when using include, extends, or embed. You need to make sure that either the template you are inheriting is defined in the theme registry through a theme suggestion hook or that you define the correct path in your inheritance delimiter so drupal knows where to look {% include directory ~ '/templates/widgets/text/text.twig' %}.

  2. Blocks are your friend. Block structure can be switched out or added to at will with any of the inheritance methods and placeholder blocks can be used in parent templates to allow for later customization. You can use placeholder header and footer blocks in page.html.twig file to allow for custom scripts, libraries and other code to be added to different content types as needed.

Oct 18 2017
Oct 18

We’ve covered writing fun and fancy CSS through a preprocessor, using a framework or library to take the pain out of JavaScript, and putting a task runner to work bring it all together. Lastly, we need a proof reader. Some way to know that what we built is working or tell us what’s wrong.

Think back over all the time you’ve spent trying to find a missing semicolon or unclosed bracket that brought a project to its knees, while you manually scanned 1000s of lines of code to figure out what you did wrong. Wish you had that month or so of your life back? Tools like Stylelint, ESLint, and your browser’s built in development tools will not get that time back for you, but they will help prevent that pain from happening again.

Stylelint and ESLint are command line lint programs that comb your code and compare it against a set of specified rules. You can write your own rules, use an existing library, or a combination of both. Your linting can be targeted to follow a set of best practices, just the rules you most care about, or help you overcome bad coding habits.

For instance, you can lint your Sass with Stylelint to show an error when you use spaces instead of tabs, leave too many blank lines after a block, or miss a semi-colon. ESLint can lint your javascript to detect if you are missing a bracket somewhere or have a comparison in which both sides are the same. For more in-depth information on Sass linting with Stylelint read my recent blog post Sass Lint: or How I Learned to Love the Lint.

Debugging

Another critical front-end tool is web browser developer tools. Firebug was an early version of these tools, available in Firefox, but now all browsers come with a set of developer tools. These developer tools allow you to inspect your CSS and HTML, test and debug style rules directly in the browser, and look for errors in the JavaScript console. A browser like Chrome even allows you to slow and stop animations, set the color scheme of the inspector, view print styles, and expand minified CSS and JS source files.

May 11 2017
May 11

Step 6: Configure Your Entity Browsers

Just like the "Media" View displays we discussed in Step 3, each media bundle needs its own Entity browser (plus one for WYSIWYG embedding). Each browser will be set up almost exactly the same with a few small but key differences (admin/config/content/entity_browser).

Settings – Using a modal display for field-based browsers helps to keep the node edit form clean and having no selection plugin streamlines the editor experience. When using a modal view, using tabs as the widget selector is a handy way to partition the browser functionality. If creating an embeddable browser you must use the iframe display plugin to prevent AJAX errors.

On the display screen you can choose to set a height and width for your modal and the link text that will be shown at the bottom to allow editors to use their selected image. Leave auto open entity browser unchecked for non-embeddable browser. The Inline Entity form module will control how we open field-based browsers. For embeddable media you will want to auto open since entity embed automatically uses a modal to house the browser iframe.

The widget selector screen will always show a no configuration message unless you are using a custom selection plugin and unless you are using the multi-step option for your selection plugin, there will also be nothing to configure on the selection display page.

Widget plugins control the functionality of the entity browser. Each plugin defines a piece of the media browser. The first plugin I use is the browser view that displays the media library. Since I also want editors to be able to upload media right in the modal if they can’t find something they like, I also add an Entity Form widget that uses the form display of the media bundle. This ensures that my custom fields will be available to editors when uploading media. The embeddable entity browser will need to have an entity form plugin for each type of media.

Pro tip:

When using the Entity Form widget, set the entity type to media—not media bundle. This is misleading, but if you use the media bundle option you will get the global media bundle form (for adding new bundles) instead of the form for a specific bundle when adding new media.

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