Bengal supports full internationalization (i18n) with content routing, UI translations, and SEO-friendly hreflang tags.
Quick Start
1. Configure Languages
# config/_default/i18n.yaml
i18n:
strategy: "prefix" # URL prefix strategy (/en/, /fr/)
default_language: "en"
default_in_subdir: false # Default lang at root, others in subdirs
languages:
- code: "en"
name: "English"
weight: 1
- code: "fr"
name: "Français"
weight: 2
- code: "es"
name: "Español"
weight: 3
2. Organize Content by Language
content/
├── en/
│ ├── _index.md
│ └── docs/
│ ├── _index.md
│ └── getting-started.md
├── fr/
│ ├── _index.md
│ └── docs/
│ ├── _index.md
│ └── getting-started.md
└── es/
├── _index.md
└── docs/
└── getting-started.md
3. Add UI Translations
# i18n/en.yaml
nav:
home: "Home"
docs: "Documentation"
search: "Search"
footer:
copyright: "© 2026 My Project"
content:
read_more: "Read more"
minutes_read: "{minutes} min read"
# i18n/fr.yaml
nav:
home: "Accueil"
docs: "Documentation"
search: "Rechercher"
footer:
copyright: "© 2026 Mon Projet"
content:
read_more: "Lire la suite"
minutes_read: "{minutes} min de lecture"
4. Build and Verify
bengal build
Output:
public/
├── index.html # English (default at root)
├── docs/
│ └── getting-started/
├── fr/
│ ├── index.html
│ └── docs/
│ └── getting-started/
└── es/
├── index.html
└── docs/
└── getting-started/
Configuration Reference
Strategy Options
| Strategy | Description | URL Pattern |
|---|---|---|
"none" |
Single language (default) | /docs/page/ |
"prefix" |
Language in URL path | /en/docs/page/,/fr/docs/page/ |
Full Configuration
i18n:
# URL strategy
strategy: "prefix"
# Default language code
default_language: "en"
# Put default language in subdir too? (false = root)
default_in_subdir: false
# Language list with metadata
languages:
- code: "en"
name: "English"
hreflang: "en" # For SEO (usually same as code)
weight: 1 # Sort order in language switchers
- code: "fr"
name: "Français"
hreflang: "fr"
weight: 2
Template Functions
t() — Translate UI Strings
{# Basic translation #}
{{ t('nav.home') }}
{# With parameters #}
{{ t('content.minutes_read', {'minutes': page.reading_time}) }}
{# Force specific language #}
{{ t('nav.home', {}, 'fr') }}
{# With default fallback text #}
{{ t('custom.key', {}, None, 'Fallback text') }}
Signature:t(key, params={}, lang=None, default=None)
If a translation key is missing, Bengal automatically falls back to the default language. If still not found, returns the provideddefaultor the key itself.
current_lang() — Get Current Language
{% let lang = current_lang() %}
<html lang="{{ lang }}">
{% if current_lang() == 'fr' %}
{# French-specific content #}
{% end %}
languages() — List All Languages
{# Language switcher #}
<nav class="language-switcher">
{% for lang in languages() %}
<a href="/{{ lang.code }}/"
{% if lang.code == current_lang() %}class="active"{% end %}>
{{ lang.name }}
</a>
{% end %}
</nav>
Returns: List of language objects with:
code— Language code (e.g.,"en")name— Display name (e.g.,"English")hreflang— SEO attributeweight— Sort order
alternate_links() — Generate hreflang Tags
{# In <head> for SEO #}
{% for alt in alternate_links(page) %}
<link rel="alternate" hreflang="{{ alt.hreflang }}" href="{{ alt.href }}">
{% end %}
Output:
<link rel="alternate" hreflang="en" href="/docs/getting-started/">
<link rel="alternate" hreflang="fr" href="/fr/docs/getting-started/">
<link rel="alternate" hreflang="x-default" href="/docs/getting-started/">
locale_date() — Localized Dates
{# Format with locale-aware output #}
{{ locale_date(page.date, 'medium') }}
{# → "Dec 19, 2025" (English) or "19 déc. 2025" (French) #}
{{ locale_date(page.date, 'long') }}
{# → "December 19, 2025" or "19 décembre 2025" #}
{# Force specific locale #}
{{ locale_date(page.date, 'medium', lang='fr') }}
Format options:short,medium,long
Tip
For full date formatting, install Babel:pip install babel
Content Linking Across Languages
Link Translations with translation_key
Pages with the sametranslation_keyare linked as translations:
# content/en/docs/getting-started.md
---
title: Getting Started
translation_key: getting-started
---
# content/fr/docs/getting-started.md
---
title: Démarrage
translation_key: getting-started
---
Bengal usestranslation_keyto:
- Generate
alternate_links()for SEO - Enable language switchers to link to the same page in other languages
Per-Page Language Override
Override the directory-based language:
---
title: Page in French
lang: fr
---
Translation File Formats
Bengal supports multiple formats for translation files:
i18n/
├── en.yaml # YAML (recommended)
├── fr.yaml
├── es.json # JSON also works
└── de.toml # TOML also works
Nested Keys
# i18n/en.yaml
nav:
home: "Home"
docs:
title: "Documentation"
description: "Learn how to use..."
errors:
404:
title: "Page Not Found"
message: "The page you're looking for doesn't exist."
Access with dot notation:
{{ t('nav.docs.title') }}
{{ t('errors.404.message') }}
Integration with Other Features
Menus
Useget_menu_lang()to get language-aware menu items:
{% for item in get_menu_lang('main', current_lang()) %}
<a href="{{ item.url }}">{{ item.name }}</a>
{% end %}
For the default menu without language awareness:
{% for item in get_menu('main') %}
<a href="{{ item.url }}">{{ item.name }}</a>
{% end %}
RSS Feeds
RSS feeds are generated per-language when usingprefixstrategy:
/rss.xml— Default language/fr/rss.xml— French/es/rss.xml— Spanish
Search
Search indexes are built per-language for accurate results:
/index.json— Default language/fr/index.json— French
Example: Complete Language Switcher
{# templates/partials/language-switcher.html #}
<div class="language-switcher">
<button aria-label="Select language">
{{ current_lang() | upper }}
</button>
<ul class="dropdown">
{% for lang in languages() %}
{% let is_current = lang.code == current_lang() %}
<li>
<a href="/{{ lang.code }}/"
{% if is_current %}aria-current="true"{% end %}
lang="{{ lang.code }}">
{{ lang.name }}
</a>
</li>
{% end %}
</ul>
</div>
Troubleshooting
Translations Not Loading
- Check file exists:
i18n/<lang>.yaml - Verify YAML syntax is valid
- Check key path matches:
t('nav.home')needsnav.home:in file
Wrong Language Displayed
- Verify
langfrontmatter or directory structure - Check
i18n.strategyis set to"prefix" - Ensure content is in correct language directory
Missing hreflang Tags
- Add
translation_keyto linked pages - Verify pages exist in both languages
- Check
alternate_links(page)is in<head>