Skip to content
Code on GitHub (opens in a new tab)

Documentation · Guides

Several languages

A site in two or more languages with one file per language, with its selector, its menus and its dates.

Version 0.16

For a site to speak two languages, say so in sitio.yml (idiomas means “languages”, codigo is the language code and nombre its name). This example is an English site with a Spanish version:

idiomas:
  - codigo: en
    nombre: English
  - codigo: es
    nombre: Español

And write each page once per language. The language goes in the name of the file:

contenido/
  index.md                →  /
  index.es.md             →  /es/
  contact.md              →  /contact/
  contact.es.md           →  /es/contact/
  blog/
    a-post.md             →  only in English

That is all. You do not have to declare that contact.md and contact.es.md are the same page: they are because they have the same name.

The default language

The first one in the list is the default. Its pages carry no prefix, and its file needs no code: contact.md is in English. That is why a site that already exists can become bilingual without renaming anything, just by adding the files of the other language. The pages of the other languages carry theirs in front (/es/), also when you give them an address of their own:

# contenido/contact.es.md
url: /contacto/

That page comes out at /es/contacto/. You only write what you translate.

Every language needs a page that is its root, like index.es.md for /es/: if it does not exist, the language selector would lead to an address that is not there, and Ehundu warns you.

What is not translated does not exist

The normal case is not a duplicated site, but a company that translates the home page, the about page and the contact page, and keeps the blog only in English. With one file per language that happens by itself: a-post.md exists in English and nothing is generated in Spanish. Ehundu never publishes the text of one language under the address of another, because it would be duplicated content, and it does not warn you about what is missing a translation.

For the same reason, collections are limited to the language of the page that uses them: a blog listing in Spanish does not pull English posts. If you want another language, you ask for it with a second argument:

{% for articulo in coleccion('blog', 'en') %}

The language selector

The engine does not write selectors: that is up to the template. You have what you need. pagina.idioma is the code of the page; pagina.traducciones (“translations”) are its published versions, itself included; and sitio.idiomas is the list of languages with their codigo, their nombre and the url of their root.

<html lang="{{ pagina.idioma }}">

{% for idioma in sitio.idiomas %}
  {% set traduccion = pagina.traducciones[idioma.codigo] %}
  <a href="{{ traduccion ? traduccion.url : idioma.url }}" hreflang="{{ idioma.codigo }}">{{ idioma.nombre }}</a>
{% endfor %}

If the current page is not translated, the link leads to the home page of that language, so it never points to an address that does not exist. And for search engines, the hreflang of the translations that do exist:

{% for codigo, traduccion in pagina.traducciones %}
  <link rel="alternate" hreflang="{{ codigo }}" href="{{ sitio.url }}{{ traduccion.url }}">
{% endfor %}

Data follows the same rule as pages, with one difference: a file without a code is common to all languages.

datos/menus.yml       common to all
datos/menus.en.yml    English
datos/menus.es.yml    Spanish

In a page, datos.menus is the common file with the one for its language on top. If the blog only exists in English, the Spanish menu has one entry fewer, and there is nothing else to do.

Dates

fecha() writes the days and the months in the language of the page. Ehundu brings Spanish, Basque, English and German:

{{ pagina.fecha|fecha }}

The same date comes out as 18 March 2025 in English, 18 de marzo de 2025 in Spanish and 2025eko martxoaren 18a in Basque.

What is not translated in a page (the main image, the order) can go in a .yml file with the same name, such as contact.yml, so that it is not repeated in every language. The detail of all this is in multilingual sites (in Spanish).