Building a Multilingual Site with Statamic

I recently worked with INITIALS Marketing to build a website with a simple structure and content management requirements. There was only one caveat: it needed to be multilingual.

I really enjoy working with Statamic and thought that it could be the perfect fit. I wasn't wrong. After a couple of nudges in the right directions from the friendly developers and community, I was pleasantly surprised by how easy it was to set up a content-managed site that worked in English, French and Russian.

Here’s a quick run-through of what I did to build a multilingual Statamic site.

Note: I am assuming that you have experience in building Statamic sites. If not, you might want to study the documentation first.

All Content Lives in Subfolders Specific to the Language

Normally, the top-level page.html file within the _content folder contains the site’s home page content, but here it purely acts as a redirect to the page of a language-specific subfolder.

The basic folder structure within _content looks like this:


_content
  01-en
    page.html /* English home page */
  02-fr
    page.html /* French home page */
  03-ru
    page.html /* Russian home page */
  page.html  /* Redirects to /en, /fr or /ru */

This way the URLs always contain the chosen language in the first segment:

  • mydomain.com/en
  • mydomain.com/fr
  • mydomain.com/ru

Automatic redirect

If the user lands on mydomain.com without a language segment, the top-level page.html content file is called. This is linked to a redirect layout file:


---
title: Landing redirect
_layout: landing-redirect
---

This file contains PHP code that handles the redirect to the relevant language subfolder. If no language setting can be determined, it falls back to a set default:


<?php
  $site_url = Config::getSiteURL();
  $sites = array(
    "en" => $site_url . "/en",
    "fr" => $site_url . "/fr",
    "ru" => $site_url . "/ru"
  );
  // Get 2 char lang code
  $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
  // Set default language if a '$lang' version of site is not available
  if (!isset($sites[$lang])) {
    $lang = 'en';
  }
  // Finally redirect to desired location
  header('Location: ' . $sites[$lang]);
  exit;
?>

For this to work, make sure that you set _allow_php: true in the _config/settings.yaml file.

Manual Language Switcher

Users might end up seeing content in the wrong language, for example by using a computer in a different country or through a link they followed. So there needs to be a way to switch languages manually, too.

This means each content page of each language needs to be linked with the equivalent pages of the other languages. I used suggest fields in a fieldset for this:


fields:
  alternative_urls:
    type: section
    display: Alternative URLs
    instructions: "Used for the language switcher and to set the rel='alternate' in the head'
  alternative_url_en:
    display: Alternative English page/URL
    type: suggest
    max_items: 1
    content:
      folder: en*
      type: pages
      label: url
      value: url
      show_hidden: true
      show_drafts: true
  alternative_url_fr:
    display: Alternative French page/URL
    type: suggest
    max_items: 1
    content:
      folder: fr*
      type: pages
      label: url
      value: url
      show_hidden: true
      show_drafts: true
  alternative_url_ru:
    display: Alternative Russian page/URL
    type: suggest
    max_items: 1
    content:
      folder: ru*
      type: pages
      label: url
      value: url
      show_hidden: true
      show_drafts: true

In the template code, the language switch links either go to the alternative page set via the above fields, or to the language’s home page if no page has been assigned:


{{ if segment_1 == 'en' }}
  Change Language:
{{ endif }}
{{ if segment_1 == 'fr' }}
  Changer la langue:
{{ endif }}
{{ if segment_1 == 'ru' }}
  Изменить язык:
{{ endif }}
<ul>
  <li>
    {{ if segment_1 == 'en' }}
      <span class="current">EN</span>
    {{ else }}
      {{ if alternative_url_en }}
        <a href="{{ alternative_url_en }}">EN</a>
      {{ else }}
        <a href="/en">EN</a>
      {{ endif }}
    {{ endif }}
  </li>
  <li>
    {{ if segment_1 == 'fr' }}
      <span class="current">FR</span>
    {{ else }}
      {{ if alternative_url_fr }}
        <a href="{{ alternative_url_fr }}">FR</a>
      {{ else }}
        <a href="/fr">FR</a>
      {{ endif }}
    {{ endif }}
  </li>
  <li>
    {{ if segment_1 == 'ru' }}
      <span class="current">RU</span>
    {{ else }}
      {{ if alternative_url_ru }}
        <a href="{{ alternative_url_ru }}">RU</a>
      {{ else }}
        <a href="/ru">RU</a>
      {{ endif }}
    {{ endif }}
  </li>
</ul>

Setting the Document's Language

Defining the language of the document has several benefits and it’s easily set by checking the first segment:


{{ if segment_1 == 'en' }}
  <html lang="en">
{{ endif }}
{{ if segment_1 == 'fr' }}
  <html lang="fr">
{{ endif }}
{{ if segment_1 == 'ru' }}
  <html lang="ru">
{{ endif }}

Defining Alternative URLs

To ensure that search engines serve the correct URL in their results, the rel="alternate" hreflang="x" attributes need to be defined in the <head>. Have a read of the Google’s help article to find out more about this.

This is the code I used:


{{# Alterntive URLs for home pages #}}
{{ if !segment_2 }}
  <link rel="alternate" hreflang="en" href="{{ _site_url }}/en">
  <link rel="alternate" hreflang="fr" href="{{ _site_url }}/fr">
  <link rel="alternate" hreflang="ru" href="{{ _site_url }}/ru">
{{ endif }}

{{# Alterntive URLs for subpages - set in CMS via alternative_urls fields #}}
{{ if segment_2 }}
  {{ if segment_1 == 'en' }}
    <link rel="alternate" hreflang="en" href="{{ current_url }}">
  {{ else }}
    {{ if alternative_url_en }}
    <link rel="alternate" hreflang="en" href="{{ alternative_url_en }}">
    {{ endif }}
  {{ endif }}

  {{ if segment_1 == 'fr' }}
    <link rel="alternate" hreflang="fr" href="{{ current_url }}">
  {{ else }}
    {{ if alternative_url_fr }}
    <link rel="alternate" hreflang="fr" href="{{ alternative_url_fr }}">
    {{ endif }}
  {{ endif }}

  {{ if segment_1 == 'ru' }}
    <link rel="alternate" hreflang="ru" href="{{ current_url }}">
  {{ else }}
    {{ if alternative_url_ru }}
    <link rel="alternate" hreflang="ru" href="{{ alternative_url_ru }}">
    {{ endif }}
  {{ endif }}
{{ endif }}

XML Sitemap

To help search engines index the site correctly, you will probably want to create and submit a XML sitemap.

This is what my sitemap.html layout file looks like:


{{ noparse }}
<?xml version="1.0" encoding="UTF-8"?>
{{ /noparse }}

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> 
  {{# Home pages #}}
  {{ get_content from="/en" }}
    <url>
      <loc>{{ _site_url }}</loc>
      <xhtml:link
        rel="alternate"
        hreflang="fr"
        href="{{ _site_url }}/fr"
        />
      <xhtml:link
        rel="alternate"
        hreflang="ru"
        href="{{ _site_url }}/ru"
        />
      <lastmod>{{ last_modified format="Y-m-d" }}</lastmod>
      <changefreq>weekly</changefreq>
      <priority>0.7</priority>
    </url>
  {{ /get_content }}

  {{# Subpages #}}
  {{ nav from="/en" max_depth="5" include_content="true" }}
    <url>
      <loc>{{ _site_url }}{{ url }}</loc>

      {{ if alternative_url_fr }}
      <xhtml:link
        rel="alternate"
        hreflang="fr"
        href="{{ _site_url }}{{ alternative_url_fr }}"
        />
      {{ endif }}

      {{ if alternative_url_ru }}
      <xhtml:link
        rel="alternate"
        hreflang="ru"
        href="{{ _site_url }}{{ alternative_url_ru }}"
        />
      {{ endif }}

      <lastmod>{{ last_modified format="Y-m-d" }}</lastmod>
      <changefreq>monthly</changefreq>
      <priority>1.0</priority>
    </url>

    {{ if children }}
      {{ children }}
        <url>
          <loc>{{ _site_url }}{{ url }}</loc>

          {{ if alternative_url_fr }}
          <xhtml:link
            rel="alternate"
            hreflang="fr"
            href="{{ _site_url }}{{ alternative_url_fr }}"
            />
          {{ endif }}

          {{ if alternative_url_ru }}
          <xhtml:link
            rel="alternate"
            hreflang="ru"
            href="{{ _site_url }}{{ alternative_url_ru }}"
            />
          {{ endif }}

          <lastmod>{{ last_modified format="Y-m-d" }}</lastmod>
          <changefreq>monthly</changefreq>
          <priority>1.0</priority>
        </url>
      {{ /children }}
    {{ endif }}
  {{ /nav }}
</urlset>

That’s it! This basic set-up should be all you need to get you on your merry, multilingual, content-managed way.

Next note: Back to Basics