Table of Contents

v0.1.0-next.45

Two strands of work in one release. Half of it is Baseline claiming something about your site that was not so: an origin anchored to the wrong place, a breadcrumb trail pointing at itself, a debug page asking to be indexed. The other half is multilingual: a page can now list its own translations without going through a collection, and the strings in your templates get somewhere to live.

Four breaking changes, which is more than usual. The first one asks you to change a file before anything else works, so it goes first.


Breaking

  • Baseline no longer loads your .env file.

    Importing the plugin used to run dotenv as a side effect, which put your environment variables in place before your own files were evaluated. That is gone.

    This only affects you if you keep a .env file. If your variables come from npm scripts or your host, nothing changes. If you do keep one, install dotenv and import it at the top of whichever file reads the values, normally _data/settings.js:

    import 'dotenv/config';

    Keep that import in the same file as the read rather than in eleventy.config.js. A file that loads its own environment works whatever imported it, in whatever order; one that relies on someone else having done it first depends on the order of two import lines.

    • Symptom: a startup warning that settings.url is missing, and pages built without canonical, Open Graph or JSON-LD.
  • The three translation filters lost their i18n prefix.

    i18nTranslationsFor, i18nTranslationIn and i18nDefaultTranslation are now translationsFor, translationIn and defaultTranslation. Their arguments changed too, two entries down.

    The prefix was on the wrong system. Across Hugo, the Eleventy dictionary plugins and most of npm, i18n means string translation, and these three answer a different question: which pages are versions of each other. The name is now free for the thing it usually means, which arrives below as t.

  • translationsMap leaves are records, not pages.

    Each entry was { title, url, lang, isDefaultLang, data }, where data was the page's entire resolved cascade, copied per language per key and kept alive until the build finished. It is now { url, lang, label, title, description, isDefaultLang }, with label being that language's languageName.

    • If you read entry.data.something, either use title and description, or look the page up by url.
  • collections.translations is gone, and the three filters no longer take a collection.

    The flat list existed only to be passed into the filters. They read the translation map directly now, so the argument went with it:

    {{ page | translationsFor }}
    {{ page | translationIn("fr") }}
    {{ page | defaultTranslation }}

    This also frees the word translations for the string tables below. If you were using the collection to list pages by language, collections.all grouped by data.lang is the replacement, and it stays yours to shape.


Changed

  • Absolute URLs come from settings.url and nothing else.

    Baseline used to fall back to the environment when resolving the origin. If the two disagree now, settings wins and the sitemap follows it. Deploy previews are where that bites, since host variables tend to stay pointed at production.

  • settings.url has to be an absolute http(s) URL.

    A relative value like /, or one missing its scheme like localhost:8080, used to be truthy enough to pass every internal check and then reach the JSON-LD graph as relative @ids. It is ignored with a warning now, and an unusable value is treated the same as an absent one: no canonical, no Open Graph, no JSON-LD.

  • Breadcrumb labels come from the section's own index page.

    A crumb for /docs/ used to be built out of the folder name. It now reads as the page at that URL titles itself, so a folder called docs holding a page called "Documentation" gives you Documentation. Sections with no index page keep the folder name, sentence-cased: core-reference reads as "Core reference" rather than "Core Reference". Both the visible trail and the BreadcrumbList change together.

    Nothing to configure. Retitle a section index page and its crumb follows.

  • A site-wide share card is no longer a claim about each page.

    settings.seo.ogImage used to feed primaryImageOfPage as well as og:image, so one card said it was the image representing every page on the site. It now fills the social tags only. Set ogImage on a page and that page gets primaryImageOfPage and a sized ImageObject node as before.

    • Symptom: primaryImageOfPage and an ImageObject node disappearing from pages that never set their own image. An organisation logo or person photo is unaffected; those come from schema.
  • hreflang is built from the content graph.

    No output change. Head used to read a map assembled by a collection; it now reads the same relationships grouped from the graph, which is where they already were.


Added

  • page.translations, the current page's siblings in other languages.

    A list, on the page, with no collection to pass around. Each entry carries the url, the lang, a label from that language's languageName, that translation's own title and description, and isDefaultLang.

    {% for entry in page.translations %}
    	<a href="{{ entry.url }}" hreflang="{{ entry.lang }}">{{ entry.label }}</a>
    {% endfor %}

    The current page is left out, matching Eleventy's own locale_links and Hugo's .Translations. It is built from the content graph, so a page marked _internal, excluded from the graph, or not rendering to .html is absent here. The filters still see it.

  • t, for translating UI strings.

    The other half of the word. Strings live in _data/translations/<lang>.js, one file per language, which Eleventy auto-loads. Keys are dot-paths.

    {{ "nav.home" | t }}
    {{ "greeting" | t({ name: "Cris" }) }}
    {{ "items" | t({ count: 7 }) }}

    The language comes from the page, falling back to your default. {name} placeholders are interpolated, plural forms are selected by count through Intl.PluralRules, and a key missing everywhere renders as the key itself with a warning rather than as a blank.

    It is registered whether or not multilingual mode is on. A single-language site still benefits from one place for its labels.

  • homeLabel, per language.

    The first breadcrumb was hardcoded to "Home". Set homeLabel on a language entry and that language's trail uses it. Unset, you get "Home".

    languages: {
      fr: { locale: 'fr', languageName: 'Français', homeLabel: 'Accueil' }
    }
  • root is a reserved section segment.

    A page whose section is ['root'] sits at the site root: the segment contributes no crumb, so a top-level page gets a Home > Page trail without /root/ landing in its URL. It shipped undocumented in an earlier release. Worth knowing if you have a real root segment, because that crumb is skipped.


Fixed

The home page no longer emits a breadcrumb trail. It was a two-item BreadcrumbList with the same URL in both entries. Language-aware, so /fr/ and /nl/ match /.

The navigator page renders one <head> instead of two nested inside each other. Its template wrapped <baseline-head> in a <head> of its own, and the placeholder is the head rather than something that goes inside one. Its styles now arrive through the head module like any other.

Warnings print once. They were emitted by the content-graph pre-pass and again by the real build, because the pre-pass log gate covered info and not warn. Errors are still ungated, since a pre-pass that fails may be the only place it says so.

The navigator page is noindex, nofollow and out of the sitemap. It was shipping index, follow and a canonical, and the only way to stop it was turning the navigator off.

No more raw fatal: not a git repository in the build log. dateModified falls back to a file's last commit, so Baseline asks git for one, and outside a repository git writes that line straight to the terminal before throwing. Unprefixed, in the middle of Baseline's own output, it read as the build dying. The build was always fine and wrote every file; it is quiet now, and the date chain still degrades to datePublished as before.

_navigator.edges falls back to an empty array rather than an empty object. Before the content graph exists it was {}, so the shape changed between the pre-pass and the real build and anything treating it as an array saw an object on the first one.

Drafts render in dev again, and images transform on request again. The content-graph pre-pass runs a second Eleventy instance, and a nested instance overwrites ELEVENTY_RUN_MODE for the whole process. It was defaulting to build, so a --serve run stopped looking like one: draft: true pages vanished and the image shortcode stopped passing transformOnRequest. Dev only, production was always correct. True since the pre-pass landed, so next.42 through next.44.

A page in a non-default language could inherit the default language's locale. With languages written as an array (['en', 'nl']), no entry carries a locale of its own, and the resolver reached past the page's own language straight to the site default, so a Dutch page reported en. It now derives from the page's language first. Object-form config with explicit locales was never affected.

The translation collections did the same walk twice per build, once each, deep-copying every translated page both times and discarding half the result each way. They share one walk now.


Docs

The site got as much work as the plugin did this time.

The tutorial is rewritten end to end. Ten pages from an empty folder to a site in two languages, explaining each decision as it makes it rather than listing them afterwards. It ends with a draft post, a production build and a sitemap you can read.

The chapters are restructured. The tutorial now sits in front of the reference layer rather than replacing any of it, with the introduction staying its own chapter for anyone new to Eleventy.

A how-to for translating UI strings, covering t and where the string files live. Deployment URL checks moved into how-to as well, where it always belonged.

Release notes are one page per version now, listed at /release-notes/, rather than one page that grew.

The install lines no longer name eleventy-img. npm resolves the peer itself, and naming it was sending readers into an ERESOLVE they did not need to meet.

Previous: v0.1.0-next.44