---
title: "Multi-Language Static Sites"
description: "Add translations to your seite static site with filename-based i18n: per-language URLs, RSS feeds, sitemaps, search indexes, and LLM discovery files."
weight: 7
---

seite's i18n system is filename-based and purely additive. Single-language sites need zero configuration. The static site generator works identically whether or not you enable translations.

## Overview

`seite` uses a filename-based translation system. It's fully backward compatible: single-language sites work identically with no configuration.

{{% callout(type="info") %}}
Single-language sites need zero i18n config. Everything works identically whether or not you add `[languages.*]` sections. i18n is purely additive.
{{% end %}}

## How It Works

Default language content uses plain filenames:

```
content/posts/hello-world.md     → /posts/hello-world
content/pages/about.md           → /about
```

Translations add a language suffix before the extension:

```
content/posts/hello-world.es.md  → /es/posts/hello-world
content/pages/about.es.md        → /es/about
```

Items with the same slug across languages are automatically linked as translations.

## Configuration

Enable multi-language support by adding language sections to [`seite.toml`](/docs/configuration):

```toml
[site]
language = "en"
title = "My Site"

[languages.es]
title = "Mi Sitio"
description = "Un sitio web est&aacute;tico"

[languages.fr]
title = "Mon Site"
description = "Un site web statique"
```

Each language can override `title` and `description`: the default language is set in `[site].language`.

## Creating Translations

Use the `--lang` flag with `seite new`:

```bash
seite new post "Hello World"             # English (default)
seite new post "Hola Mundo" --lang es    # Spanish translation
```

Or create files manually. The language suffix must match a configured language code:

```
about.md      → English (default)
about.es.md   → Spanish
about.fr.md   → French
about.xx.md   → Ignored (xx not configured)
```

{{% callout(type="tip") %}}
Files with a `.xx.md` suffix are safely ignored if `xx` isn't a configured language. You won't accidentally create broken translations from random filename patterns.
{{% end %}}

## Per-Language Output

When multi-language is enabled, `seite` generates per-language versions of:

| Output | Default language | Other languages |
|--------|-----------------|-----------------|
| Index page | `dist/index.html` | `dist/{lang}/index.html` |
| RSS feed | `dist/feed.xml` | `dist/{lang}/feed.xml` |
| LLM discovery | `dist/llms.txt` | `dist/{lang}/llms.txt` |
| Search index | `dist/search-index.json` | `dist/{lang}/search-index.json` |
| Sitemap | `dist/sitemap.xml` (all languages, with alternates) | |

## URL Structure

Non-default languages get a `/{lang}/` prefix:

```
/posts/hello-world          # English
/es/posts/hello-world       # Spanish
/fr/posts/hello-world       # French
```

## hreflang Tags

All bundled themes automatically emit `<link rel="alternate" hreflang="...">` tags when translations exist, helping search engines serve the right language.

## Language Switcher

Bundled themes include a language switcher UI when `translations` is non-empty. It shows links to all available translations of the current page.

## Template Variables

| Variable | Description |
|----------|-------------|
| `{{ lang }}` | Current page language code |
| `{{ translations }}` | Array of `{lang, url}` for available translations |
| `{{ site.language }}` | Configured default language (from `seite.toml`) |
| `{{ default_language }}` | Configured default language code |
| `{{ lang_prefix }}` | URL prefix for current language (empty for default, `"/es"` for others) |
| `{{ t }}` | UI translation strings (see [Templates & Themes](/docs/templates) docs) |

Use in templates:

```html
{% if translations %}
<nav class="lang-switcher">
  {% for t in translations %}
    {% if t.lang == lang %}
      <strong>{{ t.lang }}</strong>
    {% else %}
      <a href="{{ t.url }}">{{ t.lang }}</a>
    {% endif %}
  {% endfor %}
</nav>
{% endif %}
```

## Navigation and Footer Links

Data file links (`data.nav`, `data.footer`) are automatically prefixed with the current language in all bundled themes. Mark external links with `external: true` so they are not prefixed:

```yaml
# data/nav.yaml
- title: Blog
  url: /posts
- title: GitHub
  url: https://github.com/user/repo
  external: true
```

Internal links render as `{{ lang_prefix }}{{ item.url }}`. External links render with `target="_blank"`.

## UI String Translations

Bundled themes use `{{ t.key }}` for all interface text (search placeholder, pagination, 404 text, etc.). Override defaults per language by creating `data/i18n/{lang}.yaml`:

```yaml
# data/i18n/es.yaml
search_placeholder: "Buscar…"
skip_to_content: "Ir al contenido principal"
no_results: "Sin resultados"
newer: "Más recientes"
older: "Más antiguos"
```

See [Templates & Themes](/docs/templates) for the full list of available keys.

## Next Steps

- [Collections](/docs/collections): how translations work across posts, docs, and pages
- [Templates & Themes](/docs/templates): language switcher template code and theme blocks
- [Deployment](/docs/deployment): per-language output files and how they deploy
