Table of Contents
Page 2 of 5

Build a multilingual site

Turn multilingual on

Open eleventy.config.js and pass an options object as baseline()'s second argument. Until now it has had settings only, which left every option at its default.

 export default async function (eleventyConfig) {
	await eleventyConfig.addPlugin(
		baseline(settings, {
			multilingual: true,
			head: {
				titleSeparator: ' | ',
				showGenerator: true
			}
		})
	);
 
 	eleventyConfig.addCollection('posts', (collectionApi) =>
 		collectionApi.getFilteredByGlob('src/content/**/posts/*.md')
 	);
 }

multilingual is the one that matters here. The other two are along for the ride so the object is not a single key: titleSeparator sets what sits between the page title and the site title in <title>, and showGenerator adds a <meta name="generator"> naming the Eleventy version. Both are head options, both are optional, and the head module reference has the rest of them.

The scripts do not change either. start still sets BASELINE_URL to localhost, and hreflang is anchored to it the same way canonical URLs and the sitemap already are.

Baseline's multilingual support is a thin layer over Eleventy's built-in i18n plugin. The Eleventy i18n docs cover the mechanics underneath.


Site data and languages

Open src/_data/settings.js from the previous pages and add the languages map. defaultLanguage has been in there since wire in Baseline, where it was doing nothing much; it is about to start mattering. Everything else carries forward untouched, and the merged file looks like the below.

Baseline reads the map to know which languages exist, and defaultLanguage to mark the canonical one. Off each entry it takes title, tagline, locale and languageName. That last one is the language's name in its own language, and it becomes the label on every entry in page.translations, which is what a language switcher renders.

contentDir is the odd one out. It records where that language's content lives, and the plugin never reads it. It is a convention, there so a switcher, or anything else that needs to find a language's content, has one place to ask. What actually files a page under a language is the lang key, further down.

export default {
	title: 'Simple Baseline Site',
	tagline: 'Hello, Eleventy + Baseline',
	url: process.env.BASELINE_URL,
	noindex: false,

	defaultLanguage: 'en',
	languages: {
		en: {
			contentDir: 'content/en/',
			locale: 'en',
			languageName: 'English',
			title: 'Simple Baseline Site',
			tagline: 'Hello'
		},
		nl: {
			contentDir: 'content/nl/',
			locale: 'nl',
			languageName: 'Nederlands',
			title: 'Eenvoudige Baseline-site',
			tagline: 'Hallo'
		}
	},

	head: {
		link: [{ rel: 'stylesheet', href: '/assets/css/index.css' }],
		script: [{ src: '/assets/js/index.js', defer: true }],
		meta: [{ name: 'color-scheme', content: 'light dark' }]
	}
};

There are two title and tagline pairs in there now, and they answer different questions. The per-language pair is the site's name in that language, and it is what Baseline puts in a page's <title>, so a Dutch page gets the Dutch name without you writing a condition anywhere. The top-level pair is the site's name full stop, which is what your layout prints in the header through settings.title. English repeats the top-level name because the site was already named in English; Dutch is the one that had something to say.

Multilingual is on now, and the site has one language's worth of content sitting in folders that say nothing about language. The next page fixes that.