Table of Contents
Page 3 of 5

Build a multilingual site

Split the content by language

The contentDir entries you just wrote record English content as living in content/en/ and Dutch in content/nl/. Nothing enforces that, but the point of writing it down is to follow it, so the content moves:

src/content/
	content.11tydata.js
	index.md
	en/
		pages/
		posts/
	nl/
		pages/

pages/ and posts/ go under en/ exactly as they are, data files included, and nothing inside either one changes. There is no nl/posts/ because you are not going to translate the post, which is the normal state of a site with two languages and one author.

The homepage does not move, because it is already where it needs to be. It has sat at the root of content/ since you wrote it, above both languages, which is exactly right for the one address the two languages share. The rule you learned there is what makes the tree above legible: a file at the root names its own address, a file in a folder gets a slug and lets the folder decide. Until now the root held the homepage and the data file above it. Now the distinction is visible.

It does need one new key. translationKey is a string shared by translations of the same page: pages carrying the same key across languages are recognised as versions of one another, and that is what lets Baseline emit the right hreflang.

---
title: 'Hello Baseline'
slug: 'home'
description: 'A minimal Eleventy page powered by eleventy-plugin-baseline.'
permalink: '/'
translationKey: 'home'
---

The body is untouched. The about excerpt and the posts list stay exactly where you wrote them, still reading _navigator.nodes["/about/"] and collections.posts, and neither of those cares that the pages behind them moved a folder deeper. Their URLs did not change.

Then the Dutch side, which is one file and one edited copy. Take pages.11tydata.js into nl/pages/ as well: the address rule belongs to the folder, and there are two folders now. One thing changes in the Dutch copy. The rule builds /${slug}/, which is the right address in English and one segment short in Dutch, so it returns /nl/${slug}/ instead.

src/content/nl/pages/pages.11tydata.js, whole, with that one line already changed:

export default {
	// Ensure we have a value to work with by explicitly asking for the slug field.
	permalink: function ({ slug, page }) {
		if (!slug) {
			console.warn(`Warning: No slug found for ${page.inputPath}`);
			return false;
		}

		try {
			const normalizeSlug = this.slugify(slug);
			return `/nl/${normalizeSlug}/`;
		} catch (error) {
			console.error(`Error generating permalink for ${page.inputPath}:`, error);
			return false;
		}
	}
};

Only the return inside the try differs from the English copy.

src/content/nl/pages/index.md:

---
title: 'Hallo Baseline'
slug: 'home'
description: 'De Nederlandse homepagina.'
permalink: '/nl/'
translationKey: 'home'
---

Welkom op de Nederlandse homepagina.

It names its own address, the same override again, because /nl/ is not derived from a slug either.

The slug is home on both, and that is on purpose rather than an oversight. Translations of a page share a slug: a link written [[home]] resolves to the English homepage, and Baseline hops it to /nl/ for a Dutch reader.

The Dutch homepage is deliberately a stub. Rebuilding the excerpt and the posts list in Dutch would teach you nothing so that is all the Dutch side has on it.

That leaves about.md and the post sitting in English with no Dutch counterpart, and no translationKey between them. That is not an oversight to fix later. A page with no translation gets no hreflang links at all, which is exactly right: there is nothing to point at. You can see the difference in the output, where the homepage carries three alternates and /about/ carries none.

Nothing else moves. The assets you set up on the layout and stylesheet page are untouched, because compiled CSS and JS have no language.


Per-language directory data

The folders exist; nothing in them says which language they hold. Baseline reads lang off each page to group translations and emit hreflang, and a directory data file is how a whole folder gets one.

src/content/en/en.11tydata.js:

export default { lang: 'en' };

src/content/nl/nl.11tydata.js:

export default { lang: 'nl' };

The homepage gets neither, and that is deliberate rather than an omission. It is at the root, in no language folder, so no lang reaches it and Baseline falls back to defaultLanguage. A page that does not say what language it is in is in the site's own language, which for the shared homepage is exactly the right answer.

Every page now declares its language. The next page is what that buys you, starting with a line in the layout you wrote three pages ago and have not touched since.