Internationalization (I18n) Coming soon in v2.0.0 Jump to heading
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
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).
<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> -->
<a href="{{ locale_url "/blog/" }}">Blog</a>
<!-- <a href="/en/blog/">Blog</a> -->
<a href="{{ locale_url "/blog/" }}">Blog</a>
<!-- <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> -->
<a href="{{ locale_url "/blog/" "es" }}">Blog</a>
<!-- <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
Jump to heading
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
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 -%}
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
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.
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 -%}
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
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:
/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.