# Multi-language sites One site, several languages.

> How to build a multi-language site with vagejs. One folder per language, the right lang and hreflang tags on every page, and checks for missing translations.

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.

Updated 28 October 2026

For a step-by-step walk-through, see [how to build a multilingual static website](https://vagejs.com/guides/multilingual-static-site/). For an overview, see [multilingual sites with vagejs](https://vagejs.com/features/multi-language-sites/).

## 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`](https://vagejs.com/docs/checks/#html-lang) | Error |
| `translationOf` points to a page that doesn't exist in the main language | [`translation`](https://vagejs.com/docs/checks/#translation) | Error |
| `translationOf` on a page in the main language | [`translation`](https://vagejs.com/docs/checks/#translation) | Error |
| Two pages claim to be the same translation | [`translation`](https://vagejs.com/docs/checks/#translation) | Error |
| A main-language page has no version in one of your languages (with `i18n.pages`, only the pages listed) | [`translation-missing`](https://vagejs.com/docs/checks/#translation-missing) | Warning |
| An `i18n.pages` entry matches no published page | [`translation`](https://vagejs.com/docs/checks/#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.
