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 flat per-page language fields (lang, locale, translationKey, 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 the computed per-page language fields, normalises the language config through normalizeLanguageMap.
  • 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. normalizeLanguageMap 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 the per-page language fields. Four independent eleventyComputed.page.* registrations: lang (the short code), locale (the BCP 47 string), translationKey, and isDefaultLang.
  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 the language derived from data.locale, 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.

Per-page language fields

Every page receives four flat language fields on page, resolved during the cascade:

export default {
	page: {
		lang: 'en', // short code, resolved and lowercased
		locale: 'en-US', // BCP 47 string, from `data.locale` or the language's `locale`
		translationKey: 'about', // from `data.translationKey`, or undefined
		isDefaultLang: true // lang === defaultLanguage
	}
};

page.locale is the BCP 47 string itself. The head module reads these fields 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 the flat lang, locale, translationKey, and isDefaultLang fields 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.

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