Guide

Internationalisation (i18n)

i18n is opt-in. Existing sites, including sites using the legacy compact languages option, keep their current behaviour until i18n.enabled is set.

Configuration

languages:
  - code: pl
    locale: pl-PL
    name: Polski
    timezone: Europe/Warsaw
  - code: en
    locale: en-GB
    name: English
    timezone: Europe/London
default_language: pl
i18n:
  enabled: true
  prefix_default_language: false
  translations_dir: i18n
  dictionary_fallback: true
  missing_translation: warn # error, warn, fallback, empty
  invalid_language: fail    # fail, warn
  duplicate_translation: fail # fail, warn
  fallback_languages:
    pl: [en]
    en: []

The compact form remains valid:

languages: [pl, en]
default_language: pl
language_timezones:
  pl: Europe/Warsaw

With prefix_default_language: false, Polish /o-nas/ and English /en/about/ are generated. Setting it to true produces /pl/o-nas/ and /en/about/.

Content translations

Connect translated files with a stable key; translated slugs may differ:

title: About
slug: about
lang: en
translation_key: about
status: publish

When lang is absent it resolves to default_language. If translation_key is absent, SSG derives it deterministically from the source filename. Duplicate (translation_key, lang) pairs, unknown languages, fallback cycles, invalid timezones and output collisions are validated before rendering.

Templates receive .Page.Lang, .Page.Locale, .Page.TranslationKey, .Page.Translations, .Site.Language, .Site.Languages, .Site.DefaultLanguage, .Site.LanguagePages and .Site.LanguagePosts.

{{range .Page.Translations}}
  <a href="{{.URL}}" hreflang="{{.Lang}}" {{if .IsCurrent}}aria-current="page">{{.Lang}}</a>

Helpers: hasTranslation, translationURL, languageURL, localizeDate and t. Existing helpers such as formatDatePL remain available.

Translation dictionaries

Place pl.yaml, en.yaml or JSON equivalents in translations_dir:

navigation:
  home: Strona główna
post:
  reading_time: " minut"

Use {{t "navigation.home"}} or {{t "post.reading_time" (dict "count" .ReadingTime)}}. Values are returned as ordinary strings and remain subject to html/template escaping. Only named placeholders are substituted; catalog values are never executed as templates.

Generated output

Pages, aliases, home pages, pagination, JSON records, Atom feeds and search indexes follow the configured prefix rule. Sitemap page entries contain XHTML language alternates and x-default; opt-in SEO output includes Open Graph locale metadata and JSON-LD inLanguage.

rewrite_md_links is language-aware: a link like installation.md resolves to the active language's translation of that document. An explicit language-suffixed link (installation.en.md) keeps the author's choice. When the active-language translation does not exist, the content_fallback chain (fallback_languages → default language) applies only when i18n.content_fallback: true; otherwise the link is left untouched and a warning names the link and language (once per pair).

Deferred features

Planned follow-ups, not yet implemented: language-scoped taxonomy pages (category/tag/author/series listings are still cross-language), a language selector and t labels inside the built-in themes (the output <html lang> is already corrected at render time), localized month names in localizeDate, and plural rules.

Migration

Add the i18n section only when ready. Start by assigning lang and a shared translation_key to variants, then add dictionaries and a language selector. Run a clean build after enabling i18n so obsolete unprefixed artifacts do not remain in the output directory.