Languages
Publishing in more than one language
Read this reference when a site publishes in more than one language, or when adding a language to one that does not. Everything below is template behaviour: a translated site needs content and data, not template changes.
How A Page Gets Its Language
Each language is published under its own URL prefix (home_url), and a page's
language is the language whose prefix its URL starts with; everything else is in
the default language. Keep each language's pages in a folder named like its
prefix. A page without a permalink publishes where its file sits, and a page
with no URL at all (permalink: false) takes its language from its folder:
src/pages/en/index.md -> /en/
src/pages/en/about.md -> /en/about/
src/pages/es/index.md -> /es/
src/pages/es/acerca.md -> /es/acerca/
In PagesCMS an editor adds a Spanish page by opening the es folder of the
Pages collection and creating it there; it lands under /es/ with no
permalink to set.
The default language may live at / or under its own prefix such as /en/.
With a prefixed default, redirect the bare root to it from the default home
page (redirect_from: /), and redirect any old unprefixed URLs the same way.
The Data Files
src/_data/languages.json lists every language the site publishes. Exactly one
entry has is_default: true. Copy the default entry's shape and translate every
label: the build fails on a language missing any of them, because each one is
read by a screen reader or shown on every page.
| Field | Used for |
|---|---|
code, hreflang, og_locale |
<html lang>, alternate links, social locale |
label |
The language's own name, in the language switcher |
home_url, home_label |
URL prefix and home page; breadcrumb root, logo link |
breadcrumb_label, skip_to_content_label, search_label |
Landmark and control names |
navigation_label, menu_label, close_menu_label, submenu_label |
The header menu and its buttons |
gallery_label, close_gallery_label, previous_image_label, next_image_label, gallery_images_label |
The image gallery dialog |
download_label, back_to_label |
Downloads block; guide "Back to" link |
load_more_label, no_results_label, result_count_one_label, result_count_other_label |
Search results; {count} stands for the number |
redirect_label, redirect_link_label |
Generated redirect pages |
src/_data/translations.json pairs the pages that say the same thing, keyed by
language code. Slugs may differ per language:
[
{ "en": "/en/", "es": "/es/" },
{ "en": "/en/privacy-policy/", "es": "/es/politica-de-privacidad/" }
]
Each pair produces reciprocal hreflang links, an x-default, and the
language switcher's target. A page without a pair switches to the other
language's home page.
Navigation
Give every page its menu entry in its own frontmatter. The header lists only the pages written in the current page's language, so each language orders and labels its own menu, and keys only need to be unique within a language:
eleventyNavigation:
key: ¿Cómo abro una cuenta?
order: 6
Nest pages with parent: <parent key>. A menu entry that links elsewhere is a
file in the language's folder with no page of its own:
name: Sitio principal
permalink: false
layout: false
eleventyNavigation:
key: Sitio principal
url: https://www.example.org/es/
order: 9
config.json language_switcher puts the switcher in the footer (default)
or the header; collapse_menu (mobile, always, never) decides where the
menu folds behind its toggle button.
Snippets
A snippet can have a version per language: src/snippets/<code>/<name>.md
replaces src/snippets/<name>.md on that language's pages, and a language
without its own version uses the shared one. The footer, the right-hand column,
and the snippet block all resolve snippets this way, so per-language footer
copy is two files, not template logic.
Writing And Checking Translations
- Convert each language from its own source. Real sites are not symmetric: footer links, contact routes, and even page counts differ between languages.
- Do not machine-translate publishable copy unless the user explicitly accepts that workflow and arranges human review.
npm run buildchecks internal links, including everyhreflangtarget, so add a translation pair once both pages exist.- In the built output, check each language's
<html lang>, menu, switcher target, footer, and breadcrumb trail on at least one page.