EnglishNederlandsFrançais Toggle theme

Eleventy Baseline

Start building your site, skip the recurring setup work.
Table of Contents

multilang

INTERNAL_KEY: '_multilang'

What it does

The multilang module wires Eleventy's built-in I18nPlugin (the locale-aware URL plugin), normalises your language config, attaches a per-page locale object (with translationKey, lang, isDefaultLang), builds two translations collections, and registers three i18n filters for cross-language lookups in templates. The translation map gets written into a runtime store; the head module reads it once the cascade closes to build hreflang alternates.

A translation key is the identifier you set on a page (translationKey: 'about' in front matter) so Baseline knows the English /about/ and the French /fr/a-propos/ are the same page in different languages. Hreflang is the HTML link relation that tells search engines about those alternates.

Active when

All three of these must be set, otherwise the module exits early without registering anything:

  • options.multilingual: true
  • settings.defaultLanguage (a non-empty string)
  • settings.languages (a non-empty object or array)

There is no inference. Setting only defaultLanguage and languages will not activate the module without the explicit multilingual: true.

Lifecycle

  • Build-time. Adds I18nPlugin, registers the i18n filters, registers a computed page.locale, normalises the language config through normalizeLanguages.
  • Cascade-time. The translations and translationsMap collection builders walk every page with a translationKey, build the per-key map, and write it to the translation-map store (the runtime store other modules read).

How it works

  1. Normalise the language config. normalizeLanguages accepts an object map or an array of strings, lowercases the keys, drops invalid entries (logged when verbose: true), and returns the normalised object.
  2. Activate I18nPlugin. Adds Eleventy's built-in plugin with the resolved defaultLanguage and errorMode: 'allow-fallback'.
  3. Compute page.locale. Registered as eleventyComputed.page.locale. Resolves lang against data.lang, data.language, or defaultLanguage, then sets isDefaultLang accordingly.
  4. Build collections. Both collections walk the same loop, build the translationsMap once, and write it to the translation-map store. Pages without a translationKey are skipped silently. Pages whose lang is outside the allowed set are logged.
  5. Register filters. i18nTranslationsFor, i18nTranslationIn, i18nDefaultTranslation.

Defaults

  • errorMode for I18nPlugin: 'allow-fallback'. Pages without a translation in the requested language fall back to the default-language version rather than 404.
  • Language resolution per page (in order): data.lang, then data.language, then settings.defaultLanguage.
  • Allowed-languages set. Built from settings.languages keys. Pages whose lang is not in this set are logged and skipped during collection building.

defaultLanguage does not fall back to 'en' automatically. If absent, the module stays inactive (see Active when).

Settings

The multilang module reads two parts of the settings argument. Full shape on Site settings.

Key Type Used for
settings.defaultLanguage string Default-language code. Pages in this language render at unprefixed URLs; others live under /<lang>/.
settings.languages object | string[] Map of language codes to per-language overrides, or a flat array of codes. Arrays are normalised to objects with empty entries.

Options

Option Type Default Meaning
multilingual boolean false Activate the module. Required, alongside settings.

Computed page.locale

Every page receives a computed locale object on page:

export default {
	page: {
		translationKey: 'about', // from data.translationKey, or undefined
		lang: 'en', // resolved + lowercased
		isDefaultLang: true // lang === defaultLanguage
	}
};

The head module reads this for hreflang. The sitemap module reads lang for per-language partitioning.


Collections

Two collections, both keyed by translationKey:

  • translations is a flat list. Each entry is a safe copy of a page with locale attached.
  • translationsMap is a nested map: translationsMap[translationKey][lang]. Each leaf carries { title, url, lang, isDefaultLang, data }.

The map is also written to the translation-map store so transform-time consumers (the head module) can read it without going through collections.

Filters

Three filters for cross-language lookups in templates. Full reference on Filters.

  • i18nTranslationsFor(page, collections.translations): every translation sibling of the current page.
  • i18nTranslationIn(page, collections.translations, lang): the specific-language variant, or null.
  • i18nDefaultTranslation(page, collections.translations): the default-language variant, or null.

Tips

  • Every localised page needs both translationKey and lang in its front matter. Without translationKey, the page does not appear in translation collections; without lang (or a default), the resolver falls back and may misclassify the page.
  • Keep the default-language page present for every translation key. It powers the x-default alternate and the head module's fallback resolution.
  • With multilang active, the sitemap module automatically emits per-language sitemaps plus an index. See sitemap.
  • The "Unknown lang ..." log line is your signal that a page declared a language not in settings.languages. Add it to the languages map or fix the page's front matter.

Rendering alternate links

The head module emits hreflang automatically when this module is active and the page has a translationKey. If you want to render alternates yourself (or override the default markup), translationsMap is the source:

{% set t = collections.translationsMap[translationKey] %}
{% if t %}
	{% for lang, entry in t %}
		<link rel="alternate" hreflang="{{ entry.lang }}" href="{{ entry.url }}">
		{% if entry.isDefaultLang %}
			<link rel="alternate" hreflang="x-default" href="{{ entry.url }}">
		{% endif %}
	{% endfor %}
{% endif %}

Peer deps

None. Uses Eleventy's built-in I18nPlugin.

See also

Previous: head

Next: navigator