Docs menu

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:

TypeScript
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:

TypeScript
i18n: {
  locales: ["fr", "de"],
  pages: ["/", "/about/", "/docs/*"], // "/docs/*" is the whole section, /docs/ itself included
},

What vagejs adds

  • The right lang on 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 an x-default link to the main-language page. Google uses these to show visitors the version in their language. Pages set to noindex, or whose canonical points 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, with lang, route and current (true for the page you're on). Use it for a language switcher.
TypeScript
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.txt and 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.

Start with one command.
You'll have a site running in a minute.

npm create vagejs@latest my-site

Read the docsUse it with your AI assistant