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.

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 build checks internal links, including every hreflang target, 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.