Internationalization
Alloy’s i18n system is opt-in. Add a languages key to your config and the pipeline builds each language as an independent content tree with shared layouts. No languages key means a single-language site with zero overhead.
Content Structure
Each language gets its own top-level directory under content/:
Content pages are written in their language. They do not use translation lookups – each file contains its own content.
Output
Each language outputs under its own prefix:
Root Language
The default language (lowest weight) can output at the site root instead of under a prefix:
With root: true, English pages output at _site/about/index.html while French pages output at _site/fr/about/index.html.
Language Data in Templates
The active language’s config populates site.language during the build. Templates access language data like any other site data:
Available properties:
| Property | Description |
|---|---|
site.language.code |
Language key ("en", "fr") |
site.language.strings |
Translation strings map |
site.title |
Overridden by languages.{lang}.title |
Translation Strings
The strings map in each language config provides UI chrome translations. Access them in shared layouts:
strings is optional. You can declare languages for content routing and output paths without using the strings map at all.
Translation Linking
Pages are matched across languages by their relative path within the language tree. content/en/about.md and content/fr/about.md are the same page in different languages – no explicit front matter linking needed.
Templates access a page’s translations:
page.translations is an array of the same page in other languages, matched by relative path. Each entry has url, lang, and title. If no counterpart exists in a language, that language is absent from the array.
Hreflang Tags
Use translations to generate <link> tags for SEO:
Locale-Aware Permalinks
Permalink patterns in _data.yaml work per-language. Each language tree can have its own URL structure:
The output prefix is applied automatically:
- English (root):
/blog/2026/04/my-post/ - French:
/fr/blog/2026/04/my-post/
Shared Layouts
Layouts are shared across all languages. One layouts/ directory serves every language. The strings map handles UI chrome differences:
Collections and Taxonomies
Collections and taxonomies are per-language. collections.blog for the English build contains only English blog posts. Taxonomy pages are generated per-language:
External Data with i18n
For CMS-driven multilingual sites, use source plugins to fetch content per language. The languages config provides the language context, and virtual pages are generated per-language via onPagesReady:
Build Behavior
The build iterates over declared languages. Each language is a normal build with:
- A different content tree (
content/{lang}/) - A different
site.languagevalue - A different output prefix (
_site/{lang}/)
Same pipeline, same stages, different data. Languages build sequentially – each is an independent content tree with shared layouts.
Related
- Static Files and Passthrough – files shared across languages
- Lifecycle Events – hooks that fire per language batch
- Collections – per-language section collections