Creating Multi-Language Websites

In this guide, which is based on the Scrivito Example App, we will give you an idea of how separate language sites can be implemented in a single application backed by a single Scrivito CMS. The most obvious advantage of this approach, when compared to a multi-CMS solution, is that one doesn’t have to duplicate content common to several or all sites, e.g. binary content such as images or PDF files. Also, there is only one infrastructure and one editing interface, keeping maintenance and training efforts – to name just these – at the lowest level possible.

Extending the website structure

In a Scrivito CMS, the hierarchical structure of a website is usually formed by the unique paths of the individual pages. With single websites, the root object (whose path is /) usually represents the homepage, and this is how Scrivito is configured by default. Since one cannot have more than one root object – and the root object must be the object whose path is / – we will use an individual homepage object (e.g. en, de, fr) for each site.

We will prepend the paths of these objects with /lang to have a namespace under which all language sites can be accessed. This also allows us to maintain language-site-independent content by using path prefixes such as /products or /authors. Like en, de, etc., /lang is a path component, but no CMS object with this path needs to exist since its only purpose is to keep the language-specific sites together, separated from the rest.  

So this is the convention the paths of our language-specific homepages will follow, /lang plus a two-letter language identifier:

Copy
/lang/en # English
/lang/de # German
/lang/fr # French

This convention allows us to maintain any number of homepages in one CMS. Underneath the homepage objects, the websites will look just like any other Scrivito site with the default root object as the homepage.

Following what the Example App comes with, each Homepage object holds all the site settings including a logo and various API keys for third-party services. This gives you the flexibility to have these settings language-site-specific, but you must also set them individually.

Even though the Scrivito Example App serves as a starting point for this guide, you can turn any other website you developed with Scrivito into a multi-language site.

Preparing the Scrivito application

Introduce language-specific homepages

As we are assuming that the current content in the CMS is to become one of the future sites, we'll first change the paths of all CMS objects (except the ones that don’t have a path) so that they start with /lang/en. After that, we'll adapt how the app finds the homepage belonging to a page with such a language-specific path. 

Make sure you are in a working copy and in editing mode, then, in the browser console, select the scrivito_application context and execute:

Copy
Scrivito.load(() => [...Scrivito.Obj.all()]).then(a => {
  for (var obj of a) {
    if (obj.path()) {
      obj.update({_path: `/lang/en${obj.path()}`});
      console.log(obj.path()); 
    }
  }
})

The above code outputs the paths of all pages that start with /lang/en now:

Copy
/lang/en
/lang/en/product
/lang/en/pricing
/lang/en/about/contact
...

Browsing the Example App now produces a 404 error page saying that the root CMS object was not found. We know why: The root object represents the page to be rendered for the “/” URL, the standard homepage object with the / path. This object doesn’t exist anymore because we’ve changed its path to /lang/en. Also, the root object is referenced in several places in the Example App (e.g. when the navigation is rendered).

We’re going to take care of both situations, i.e. make the “/” URL point to the new /lang/en homepage, and replace all the Example App’s references to the default root object with references to the proper language-specific homepage.

Change the default homepage

As indicated, the “/” URL defaults to the root object in the CMS. Luckily, Scrivito lets us change this setting by means of configuration. To make the “/” URL point to the /lang/en homepage, open “src/config/scrivito.js” and add the homepage key to the call to Scrivito.configure, like so:

src/config/scrivito.js
Copy
import * as Scrivito from 'scrivito';

Scrivito.configure({
  homepage: () => Scrivito.Obj.getByPath('/lang/en'),
  tenant: process.env.SCRIVITO_TENANT,
});

Now, if you navigate to localhost:8080/scrivito/, you should see the new /lang/en homepage! Before adding another language, let’s provide the logic for determining the right language-specific homepage as a replacement for the no longer existing root object, so that the navigation and other features based on the former root object become functional again.

Make the new homepages known to the app

In a standard Scrivito-based app, the homepage is determined by means of Scrivito.Obj.root(). To change this, we’ll provide a helper method for finding the language-specific homepage to use instead, getHomepage(), and replace all occurrences of Scrivito.Obj.root() with it.

Create a file, “getHomepage.js”, in the “src/utils” subfolder of the app’s project directory and add the following to it:

src/utils/getHomepage.js
Copy
import * as Scrivito from 'scrivito';

function getHomepage() {
  const currentPage = Scrivito.currentPage();
  if (!currentPage) { return; }

  const path = currentPage.path();
  if (!path) { return; }
  
  let language = '/lang/en';
  if (path.startsWith('/lang/')) {
    language = path.substr(0, 8);
  }
  return Scrivito.Obj.getByPath(language);
}

export default getHomepage;

As you can see, the homepage object associated with the currently displayed page is determined via the first path components of Scrivito.currentPage(). If, for example, the path of the current page reads /lang/en/product, the homepage path can be derived by stripping everything after the first eight characters, resulting in /lang/en in this case. Of course, this is a very rudimentary approach that needs to be refined with variable-length path components.

Next, in your app, wherever Scrivito.Obj.root() is used, import “/src/utils/getHomepage” (using a relative path) and replace Scrivito.Obj.root() with getHomepage(), for example in:

src/Components/Navigation/Nav.js
Copy
...
import getHomepage from '../../utils/getHomepage.js';
...
//      parent={ Scrivito.Obj.root() } becomes
        parent={ getHomepage() }

There’s about a dozen occurrences in the files in the “src” subfolders to which this change needs to be applied. If you extended the Example App, make sure to replace Scrivito.Obj.root() in your own code as well.

Adding another language-specific homepage

Now that everything required for handling more than one homepage is in place, let’s create another Homepage object and set its path to /lang/de (use the two-letter language identifier you need). Again, make sure the working copy containing the /lang/en objects is selected and in editing mode, and the context in the browser console is scrivito_application. Here we go:

Copy
Scrivito.getClass('Homepage').create({ _path: '/lang/de' }).id()

That’s all! We’re outputting the ID of the new homepage because we don’t have a language switch yet but still want to be able to view the page in the browser. Just paste the ID to the address line, so that the URL path looks like “/scrivito/32909cd5c076acfe”. Of course, the empty homepage needs to be configured via its page properties later on. To add a subpage to it, use the blue navigation handle at the top right, or select “Add subpage” from the page menu.

Providing a language switch

Having a language switch is a must with multi-language websites. How else – if not with such a switch – could visitors select their preferred language? So let’s create a simple React component for this purpose. Since the switch is going to be part of the navigation, we’ll place the component file in “src/Components/Navigation”.

The component is a function that first finds the homepages by searching for pages based on the Homepage object class. Then it iterates over the result and renders for each homepage the substring of the path that indicates the language (en, de) and links it to the homepage object. Again, this is very basic to keep it simple; no fancy styling is applied, no flag icon shown, etc.

src/Components/Navigation/LanguageSwitch.js
Copy
import * as React from 'react';
import * as Scrivito from 'scrivito';
import getHomepage from '../../utils/getHomepage';

function LanguageSwitch() {
  const homepages = [...Scrivito.getClass('Homepage').all()];
  return (
    <ul className="nav navbar-nav">
      { homepages.map(homepage =>
        <li key={homepage.id()}>
          <Scrivito.LinkTag to={ homepage }>
            { homepage.path().substr(6, 2) }
          </Scrivito.LinkTag>
        </li>
      ) }
    </ul>
  );
}

export default Scrivito.connect(LanguageSwitch);

Now put the LanguageSwitch component into action by adding it to the navigation, i.e. rendering it in the FullNavigation component:

src/Components/Navigation/FullNavigation.js
Copy
// Other imports
import LanguageSwitch from './LanguageSwitch';
// …

render() {
// …
          <Collapse isOpen={ this.state.expanded } navbar={ true }>
            <div className="navbar-collapse">
              <LanguageSwitch />
              <Nav closeExpanded={ this.closeExpanded } expanded={ this.state.expanded } />
            </div>
          </Collapse>
// …
}

All of a sudden, we’re done. :) Clicking the Scrivito logo at the top left (or where the logo is supposed to be) should now open the homepage of the language-specific site you’re on. Add a couple of subpages to the second site you created, and see for yourself. Keep in mind that each homepage lets you provide individual site settings (e.g. for the logo). 

What’s next?

Currently, a new homepage can only be created via the console. To let editors create language-specific homepages underneath the /lang node you could build a custom component for adding a homepage either in place or via the properties of an existing homepage.

Other aspects worth thinking about:

  • To fine tune the language switch and make language-specific settings accessible to editors, add attributes for the language name, the image to be used, and similar properties to the Homepage class definition and the editing configuration. You could also highlight the currently active language.
  • Consider assigning permalinks to the homepages to make their URL speaking and more search engine friendly (e.g. “example.com/de” instead of “example.com/32909cd5c076acfe”).
  • A language-aware site search can easily be implemented by restricting the search results to the active language path.
  • Determine the default homepage based on the browser language or the top-level part of the domain name.