Stand with Ukraine 🇺🇦
Eleventy
The possum is Eleventy’s mascot

Eleventy Documentation

Menu

Internationalization (I18n) Coming soon in v2.0.0 Jump to heading

Required reading: the Eleventy docs page on Internationalization (I18n) provides crucial context on project organization and URL strategies for multi-language projects. Please review it before continuing on here.
Expand for contents

Utilities to manage pages and linking between localized content on Eleventy projects.

Note that this plugin specifically helps you manage links between content but does not localize that content’s strings, numbers, dates, etc. You’ll likely want to pick a third-party library for this! A few popular choices include eleventy-plugin-i18n, rosetta, i18next, y18n, intl-messageformat, and LinguiJS.

Installation Jump to heading

The Internationalization (i18n) plugin is bundled with Eleventy and does not require separate installation. Available in version 2.0.0-canary.13 or newer.

If you don’t yet have an Eleventy project, go through the Getting Started Guide first and come back here when you’re done!

Add to your configuration file Jump to heading

Filename .eleventy.js
const { EleventyI18nPlugin } = require("@11ty/eleventy");

module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(EleventyI18nPlugin, {
// any valid BCP 47-compatible language tag is supported
defaultLanguage: "", // Required, this site uses "en"
});
};
Expand to see the full list of advanced options
const { EleventyEdgePlugin } = require("@11ty/eleventy");

module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(EleventyEdgePlugin, {
// any valid BCP 47-compatible language tag is supported
defaultLanguage: "", // Required, this site uses "en"

// Rename the default universal filter names
filters: {
// transform a URL with the current page’s locale code
url: "locale_url",

// find the other localized content for a specific input file
links: "locale_links",
},

// When to throw errors for missing localized content files
errorMode: "strict", // throw an error if content is missing at /en/slug
// errorMode: "allow-fallback", // only throw an error when the content is missing at both /en/slug and /slug
// errorMode: "never", // don’t throw errors for missing content
});
};

Usage Jump to heading

This plugin provides two universal filters (Nunjucks, Liquid, Handlebars, 11ty.js) and one addition to the page variable.

page.lang Jump to heading

Coming soon in v2.0.0-canary.14 Adding the i18n plugin to your project will make page.lang available to your templates. This represents the language tag for the current page template, and will default to the value you’ve passed to the plugin via defaultLanguage above.

Check out the rest of the data available on the page object.

locale_url Filter Jump to heading

Accepts any arbitrary URL string and transforms it using the current page’s locale. Works as expected if the URL already contains a language code. This is most useful in any shared code used by internationalized content (layouts, partials, includes, etc).

View this example in: Liquid Nunjucks 11ty.js Handlebars
Filename /en/index.njk
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/en/blog/">Blog</a> -->
Filename /es/index.njk
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
Filename /en/index.liquid
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/en/blog/">Blog</a> -->
Filename /es/index.liquid
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
Filename /en/index.hbs
<a href="{{ locale_url "/blog/" }}">Blog</a>
<!-- <a href="/en/blog/">Blog</a> -->
Filename /es/index.hbs
<a href="{{ locale_url "/blog/" }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
Filename /en/index.11ty.js
module.exports = function(data) {
return `<a href="${this.locale_url("/blog/")}">Blog</a>`;
// returns <a href="/en/blog/">Blog</a>
}
Filename /es/index.11ty.js
module.exports = function(data) {
return `<a href="${this.locale_url("/blog/")}">Blog</a>`;
// returns <a href="/es/blog/">Blog</a>
}

If the link argument already has a valid language code, it will be swapped. The following all return /en/blog/ when rendered in /en/* templates (or /es/blog/ in /es/* templates):

It’s important to note that this filter checks for the existence of the file in the target locale. If the specific content file in the target locale does not exist, an error will be thrown! You can change this behavior using the plugin’s errorMode option (see advanced usage above).

It’s unlikely that you’ll need to but you can override the root locale with a second argument:

View this example in: Liquid Nunjucks 11ty.js Handlebars
Filename /en/index.njk
<a href="{{ "/blog/" | locale_url("es") }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
Filename /en/index.liquid
<a href="{{ "/blog/" | locale_url: "es" }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
Filename /en/index.hbs
<a href="{{ locale_url "/blog/" "es" }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
Filename /en/index.11ty.js
module.exports = function(data) {
return `<a href="${this.locale_url("/blog/", "es")}">Blog</a>`;
// returns <a href="/es/blog/">Blog</a>
}

Returns an array of the relevant alternative content for a specified URL (or, defaults to the current page). The original page passed to the filter is not included in the results. Each array entry is an object with url, code, and (localized) label properties, for example:

[
{"url":"/es/blog/","code":"es","label":"Español"}
]

“This page also available in:” Example Jump to heading

View this example in: Liquid Nunjucks 11ty.js Handlebars

Renders as:

This page is also available in <a href="/es/blog/" lang="es" hreflang="es">Español</a>

<link rel="alternate"> Example Jump to heading

Here’s another example in a layout file.

The href attributes here must be fully qualified (include the full domain with the protocol). Read more on the Google Search Central documentation.

The top level lang data property used here is most commonly set by you in the data cascade. For example: /en/en.json with {"lang": "en"} and /es/es.json with {"lang": "es"}.
View this example in: Liquid Nunjucks 11ty.js Handlebars
Filename _includes/mylayout.njk
{# `{{lang}}` must be set by you in the data cascade, see above note #}
<!doctype html>
<html lang="{{lang}}">
<head>
<link rel="alternate" hreflang="{{lang}}" href="{{page.url}}">
{% for link in page.url | locale_links %}
<link rel="alternate" hreflang="{{link.lang}}" href="https://www.11ty.dev{{link.url}}">
{% endfor %}
Filename _includes/mylayout.njk
<!doctype html>
{% comment %} `{{lang}}` must be set by you in the data cascade, see above note {% endcomment %}
<html lang="{{lang}}">
<head>
<link rel="alternate" hreflang="{{lang}}" href="{{page.url}}">
{% assign links = page.url | locale_links %}
{%- for link in links %}
<link rel="alternate" hreflang="{{link.lang}}" href="https://www.11ty.dev{{link.url}}">
{%- endfor -%}
Filename /_includes/mylayout.11ty.js
module.exports = function(data) {
let links = this.locale_links(data.page.url);
// side note: url argument is optional for current page

// `${data.lang}` must be set by you in the data cascade, see above note
return `
<!doctype html>
<html lang="
${data.lang}">
<head>
<link rel="alternate" hreflang="
${data.lang}" href="{{data.page.url}}">
${links.map(link => {
return ` <link rel="alternate" hreflang="${link.lang}" href="https://www.11ty.dev${link.url}">`
}).join("\n")}

`
;
}

This example has not yet been added—you can swap to another template language above! Or maybe you want to contribute it? Edit this page

Using with get*CollectionItem filters Jump to heading

The getPreviousCollectionItem, getNextCollectionItem and getCollectionItem filters all provide a mechanism to retrieve a specific collection item from a collection.

The i18n plugin modifies the behavior of these filters to prefer a collection item in the current page language’s without requiring any changes to your project.

For example, assume that English (en) is the default language for your project. Assume we’ve configured all of the blog posts in /en/blog/*.md to have the post tag, placing them into a post collection. Now you want to provide alternative localized versions of this blog post, so you create the following files:

Using the above filters on these localized templates will automatically prefer /en/blog/my-blog-post.md as the root collection item when navigating the collection. This allows you to do things like:

Syntax Nunjucks
{%- set nextPost = collections.post | getNextCollectionItem %}
{%- if nextPost %}<a href="{{ nextPost.url | locale_url }}">Next post</a>{% endif %}

This will prefer a localized version of the next post’s URL (Spanish pages will prefer linking to other pages in Spanish, when available). If a localized version does not exist, it will fall back to the default language instead.


Other pages in Plugins: