- Stable
3.0.0
Toggle Menu
1.93s
22.90s
Internationalization (I18n) Added in v2.0.0
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
The Internationalization (i18n) plugin is bundled with Eleventy and does not require separate installation. Available in version v2.0.0
or newer.
If you don’t yet have an Eleventy project, go through the Get Started Guide first and come back here when you’re done!
Add to your configuration file
import { EleventyI18nPlugin } from "@11ty/eleventy";
export default function (eleventyConfig) {
eleventyConfig.addPlugin(EleventyI18nPlugin);
}
module.exports = async function (eleventyConfig) {
const { EleventyI18nPlugin } = await import("@11ty/eleventy");
eleventyConfig.addPlugin(EleventyI18nPlugin);
}
Expand to see the full list of advanced options
import { EleventyI18nPlugin } from "@11ty/eleventy";
export default function (eleventyConfig) {
eleventyConfig.addPlugin(EleventyI18nPlugin, {
// 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
});
}
module.exports = async function (eleventyConfig) {
const { EleventyI18nPlugin } = await import("@11ty/eleventy");
eleventyConfig.addPlugin(EleventyI18nPlugin, {
// 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
This plugin provides two universal filters (Nunjucks, Liquid, 11ty.js) and one addition to the page
variable.
page.lang
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
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).
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/en/blog/">Blog</a> -->
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/en/blog/">Blog</a> -->
<a href="{{ "/blog/" | locale_url }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
export default function (data) {
return `<a href="${this.locale_url("/blog/")}">Blog</a>`;
// returns <a href="/en/blog/">Blog</a>
};
export default function (data) {
return `<a href="${this.locale_url("/blog/")}">Blog</a>`;
// returns <a href="/es/blog/">Blog</a>
};
module.exports = function (data) {
return `<a href="${this.locale_url("/blog/")}">Blog</a>`;
// returns <a href="/en/blog/">Blog</a>
};
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):
{{ "/blog/" | locale_url }}
{{ "/en/blog/" | locale_url }}
{{ "/es/blog/" | locale_url }}
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:
<a href="{{ "/blog/" | locale_url("es") }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
<a href="{{ "/blog/" | locale_url: "es" }}">Blog</a>
<!-- <a href="/es/blog/">Blog</a> -->
export default function (data) {
return `<a href="${this.locale_url("/blog/", "es")}">Blog</a>`;
// returns <a href="/es/blog/">Blog</a>
};
module.exports = function (data) {
return `<a href="${this.locale_url("/blog/", "es")}">Blog</a>`;
// returns <a href="/es/blog/">Blog</a>
};
locale_links
Filter
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
, lang
, and (localized) label
properties, for example:
[{ "url": "/es/blog/", "lang": "es", "label": "Español" }]
“This page also available in:” Example
This page is also available in:
{% for link in page.url | locale_links %}
{%- if not loop.first %},{% endif %}
<a href="{{link.url}}" lang="{{link.lang}}" hreflang="{{link.lang}}">{{link.label}}</a>
{% endfor %}
This page is also available in:
{% assign links = page.url | locale_links %}
{%- for link in links %}
{%- unless forloop.first %},{% endunless %}
<a href="{{link.url}}" lang="{{link.lang}}" hreflang="{{link.lang}}">{{link.label}}</a>
{%- endfor -%}
export default function (data) {
let links = this.locale_links(data.page.url);
// Don’t forget to localize this text too
return `This page is also available in:
${links
.map((link) => {
return `<a href="${link.url}" lang="${link.lang}" hreflang="${link.lang}">${link.label}</a>`;
})
.join(", ")}`;
};
module.exports = function (data) {
let links = this.locale_links(data.page.url);
// Don’t forget to localize this text too
return `This page is also available in:
${links
.map((link) => {
return `<a href="${link.url}" lang="${link.lang}" hreflang="${link.lang}">${link.label}</a>`;
})
.join(", ")}`;
};
Renders as:
This page is also available in <a href="/es/blog/" lang="es" hreflang="es">Español</a>
<link rel="alternate">
Example
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.
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"}
.{# `{{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 %}
<!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 -%}
export default 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")}
`;
};
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")}
`;
};
Using with get*CollectionItem
filters
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:
/es/blog/my-blog-post.md
/ja/blog/my-blog-post.md
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:
{%- 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.