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.