Multi-language sites
One site, several languages.
Since 0.10.0, one vagejs site can have pages in several languages. Your main language stays where it is, and each other language gets a folder.
For a step-by-step walk-through, see how to build a multilingual static website. For an overview, see multilingual sites with vagejs.
Turn it on
In vage.config.ts, lang is your main language. List the others under i18n:
export default defineConfig({
site: "https://example.com",
lang: "en",
i18n: { locales: ["fr", "de"] },
});Use language codes like fr, de or pt-BR. Don't repeat your main language in locales.
Where translated pages go
Put each language's pages in a folder named after it. A page with the same file name is the translation:
| File | Address | Translation of |
|---|---|---|
src/pages/about.md |
/about/ |
(main language) |
src/pages/fr/about.md |
/fr/about/ |
about.md |
src/pages/de/ueber-uns.md |
/de/ueber-uns/ |
set with translationOf |
To give a translation its own address in that language, add translationOf with the address of the main-language page:
---
title: Über uns
description: Wer wir sind.
translationOf: /about/
---You don't have to translate every page. Pages without a translation simply exist in one language.
If you only translate some pages, list them under i18n.pages (vagejs 0.15.1). vage check then asks for translations of those pages only, instead of warning about every other page:
i18n: {
locales: ["fr", "de"],
pages: ["/", "/about/", "/docs/*"], // "/docs/*" is the whole section, /docs/ itself included
},What vagejs adds
- The right
langon every page. French pages get<html lang="fr">, so screen readers use the right voice and search engines know the language. - hreflang links. Each page links to its translations with
<link rel="alternate" hreflang="...">, plus anx-defaultlink to the main-language page. Google uses these to show visitors the version in their language. Pages set tonoindex, or whosecanonicalpoints to another page, are left out. - Feeds in the right language. A feed for
/fr/blog/says it's French, and a feed for your main language leaves out pages in other languages.
In your layouts
Layouts get two new values:
page.lang: the page's language. Use it for<html lang="${page.lang}">.page.translations: every language version of the page, withlang,routeandcurrent(true for the page you're on). Use it for a language switcher.
const switcher = page.translations.length > 1
? html`<nav aria-label="Language">${page.translations.map((t) =>
t.current ? html`<span aria-current="page">${t.lang}</span>` : html`<a href="${t.route}" hreflang="${t.lang}">${t.lang}</a>`,
)}</nav>`
: "";To list only one language's pages, such as French blog posts, use collection(pages, { under: "/fr/blog/", lang: "fr" }). For menus in each language, add one menu per language in your settings, such as main and main-fr, and pick it in the layout with menus[page.lang === "fr" ? "main-fr" : "main"].
What does vage check look for on a multi-language site?
| Finding | Rule | Severity |
|---|---|---|
A layout writes a <html lang> that doesn't match the page's folder |
html-lang |
Error |
translationOf points to a page that doesn't exist in the main language |
translation |
Error |
translationOf on a page in the main language |
translation |
Error |
| Two pages claim to be the same translation | translation |
Error |
A main-language page has no version in one of your languages (with i18n.pages, only the pages listed) |
translation-missing |
Warning |
An i18n.pages entry matches no published page |
translation |
Warning |
Missing translations are warnings, so the site still builds while you translate. vage check --strict treats them as errors.
Not included yet
- Tag pages list every page with that tag, in any language.
llms.txtand the sitemap list every language together, not one file per language. hreflang links in each page are enough for Google.- vagejs doesn't send visitors to a language based on their browser. A static site can't do that reliably, so offer a language switcher instead.