Statac

Languages

Set the site's language, add translations, and publish in one language or several.

The site’s language

In statac.yaml:

language: fr

language sets the site’s language, as a code such as en, en-GB or fr. Statac writes dates in that language’s words, digits and order, and changes quotation marks in the content to the language’s. Templates can read the code as page.language. If a site sets no language, Statac treats it as English (en).

Statac has translations for eight languages:

Language Code
Arabic ar
English en
French fr
German de
Japanese ja
Korean ko
Persian fa
Spanish es

For each, Statac takes the months and weekdays, the words for morning and afternoon, the quotation marks, the writing direction, the digits and the order of a date from the Unicode CLDR. Anything else comes from the site’s own file, apart from a few English lines that Statac writes itself.

For a code with a region, Statac writes dates the way the Unicode CLDR writes them in that region: fr-CA writes 4 mars 2026, 09 h 05 whereas fr writes 4 mars 2026, 09:05, and de-AT writes Jänner for January. If the CLDR writes a region as a broader form of its language, Statac uses that form’s translations: en-GB, en-AU, en-NZ and en-IN write 4 March 2026, as en-001 (English outside the United States) does, and es-CU writes dates as es-419 (Spanish in Latin America) does. Statac writes any other region as its language: en-US as en.

If a code names its language’s usual script, Statac reads it without the script: en-Latn-GB writes dates as en-GB does. If a code names a script the language isn’t usually written in, such as en-Dsrt (English in the Deseret alphabet) or ja-Latn, Statac treats it as another language and uses none of its translations for the language, because the Unicode CLDR has none for it.

For any other language, Statac writes dates in figures, 2026-03-04, and leaves quotation marks as you typed them, until you give it the translations. The writing direction needs no translations: Statac takes it from the Unicode CLDR, so a site in Hebrew or Urdu runs right to left from the start. If a code names its script, such as pa-Arab, the direction follows the script.

Translations for another language

In languages/sw.yaml:

months: [Januari, Februari, Machi, Aprili, Mei, Juni,
  Julai, Agosti, Septemba, Oktoba, Novemba, Desemba]
weekdays: [Jumatatu, Jumanne, Jumatano, Alhamisi, Ijumaa, Jumamosi, Jumapili]

A file in languages/ named after a language code holds translations for that language. Write one for a language Statac doesn’t know, or to change some of the translations Statac has. With this file, a site in Swahili can set date_format: 31 Desemba 2025. Every look shows a date in such a language as the day, the month and the year: 4 Machi 2026.

Every line is optional. Each line in the file replaces Statac’s, and Statac keeps its own for any line the file leaves out. The file can change the translations, but not the order each look puts them in.

  • A line with nothing after its colon removes Statac’s line. For example, digits: on its own in languages/fa.yaml keeps Persian dates in the digits 0 to 9.
  • A file for a region adds to Statac’s translations for that region: languages/fr-CA.yaml adds to Statac’s fr-CA.
  • If there is no file for a page’s exact code, Statac looks for one in the order the Unicode CLDR gives: the code without its language’s usual script (en-Latn-GB reads languages/en-GB.yaml), a region’s broader form (en-GB reads languages/en-001.yaml, es-MX reads languages/es-419.yaml), then the code without its last part (languages/en.yaml). It looks for a code with another script without the script first, then with it: zh-Hant-TW reads zh-TW.yaml, then zh-Hant.yaml, then zh.yaml. Statac reads only the first file it finds.
  • A language Statac has translations for, written in another script, such as en-Shaw or ja-Latn, reads only its own file, never the language’s.
  • If none of the site’s languages reads a file, Statac notes it as an unused language file.
Line What it sets
months, months_short The month names, January first, as they appear in a date.
months_alone, months_short_alone The month names as written without a day, for a language that writes them differently then.
weekdays, weekdays_short The days of the week, Monday first.
days The days of the month as words, 1st to 31st, which Statac uses when a format contains 31st. Statac has them only in English.
am_pm The words for morning and afternoon: [AM, PM] in English.
digits The ten digits, zero first. Statac writes dates and a footnote’s number with them, and the digits filter writes any other number with them.
quotes The outer pair of quotation marks, then the inner pair: quotes: « » “ ”. Without it, Statac leaves quotation marks in the content straight. See punctuation.
inside_quotes A space that Statac adds inside the outer pair, such as inside_quotes: " " for a space a line can’t break at.
no_break_before, no_break_after Punctuation marks. A space you type before (or after) one of them becomes a space a line can’t break at: no_break_before: "; : ! ?". Statac doesn’t add a space where you didn’t type one.
direction rtl for a language written right to left, ltr for one written left to right. Statac takes a language’s direction from the Unicode CLDR, so write this line only to change it. Templates can read it as page.direction, and the arrow from a footnote back to the text changes from ↩ to ↪.
back_to_the_text What a screen reader says for a footnote’s link back to the text. Statac writes it as the link’s aria-label, and passes it to a site’s own snippets/markdown/footnotes.html as label.
callouts The names of the five kinds of callout: note, tip, important, warning and caution, in that order. A site’s own snippets/markdown/callout.html receives the name as title.

Statac has back_to_the_text and callouts in English only. For any other language, they come from the site’s own file, which statac new writes with both lines in English for you to translate.

On a site with no snippet for callouts, Statac’s own callout shows no name in any language.

The words your pages show

Statac writes no templates into a site, so the words your templates show readers, such as “Skip to content”, are your own. On a site in several languages, keep their translations in a data file, under each language’s code. In data/words.yaml:

en:
  skip: Skip to content
fr:
  skip: Aller au contenu

Then read the translations for the page’s language with a set line:

{% set words = site.data.words[page.language] %}
<a href="#content">{{ words.skip }}</a>

Each template that shows any of these words needs its own set line: a set line in base.html doesn’t reach a template that extends it.

A site in several languages

In statac.yaml, the main language first:

language: [ar, en]
content/
  index.md
  post-1.md
  en/
    index.md
    post-1.md
    notes/
      post-2.md

A folder at the top of content/ holds one language’s pages if its name is that language’s code, written exactly as language writes it. Here, every page under content/en/ is in English. Every page outside the language folders is in the main language, which here is Arabic.

  • A page can’t set its language in its front matter. Its place in content/ decides it.
  • The main language may have a folder of its own too, such as content/ar/. Pages outside every language folder are still in the main language, so a page at / can welcome readers in every language.

Lists and feeds in each language

Arabic, at the top English, in content/en/
Home page / /en/
post-1.md /post-1/ /en/post-1/
Tag Rust, with tags.pages: true /tags/rust/ /en/tags/rust/
Feed, with feed: true /feed.xml /en/feed.xml

Each language has a top: its folder, or content/ if the main language has no folder. The top is that language’s own list. It contains every post in the language, and it is site.posts on the language’s pages. A page in one language never appears in another language’s lists or feeds.

  • Statac makes a home page for a top with no index.md only if the site has templates/home.html. A folder inside a top has a list page if it has an index.md, as lists describes.
  • feed: true in statac.yaml asks for a feed for each language’s own list. A folder’s index.md asks for the folder’s feed, as feeds describes.
  • Tags and series belong to each language separately: Rust on an English page and Rust on an Arabic page are two tags.
  • A post’s previous and next posts, a list’s pagination, related(page), a posts: line and each: site.posts all stay within the page’s language. in("blog") looks for the folder beneath the language’s top.
  • Statac builds a feed in its list’s language, so the feed needs a title for that language: title or en.title.
  • If the main language has a folder, content/ itself is not any language’s list, and it has no feed unless its page asks for one. On its pages, site refers to the main language.

URLs in each language

In statac.yaml:

pages.permalink: /{year}/{slug}/

With this setting, Statac publishes an Arabic post at the top at /2026/post-1/, and an English one at /en/2026/post-1/. It reads every URL pattern from the language’s top: permalink: on a page or in a cascade, tags.permalink, series.permalink, and an each: page’s pattern. So permalink: /about/ on content/en/about.md publishes it at /en/about/.

Statac reads a URL you write out in full from the top of the site: in links in the content, redirect_from and redirect_to. An English post that used to be at /en/2025/post-1/ needs that whole URL in redirect_from.

Settings for one language

In statac.yaml:

language: [ar, en]
title: Site 1
en.title: Site 2
en.date_format: Dec 31, 2025

You can set title and date_format for one language by writing its code, a dot and the setting on one line. The plain line applies to every language that has no line of its own.

Write a date_format for one language in that language’s words. Write a date_format for every language in any of the site’s languages, and each page shows its dates in its own language: date_format: 31 December 2025 shows 2026-10-04 as 4 October 2026 on English pages and as 4 أكتوبر 2026 on Arabic ones. The date filter reads its examples the same way.

Translations

With Arabic at the top of content/ and English in content/en/:

content/
  about.md
  blog/
    post-1.md
  en/
    about.md
    blog/
      post-1.md
Two pages in different languages are translations if For example
They have the same path below their languages’ tops content/about.md and content/en/about.md above, and the two blog/post-1.md
Their paths differ only in the file extension about.md and about.html
One is a folder’s index.md and the other is a file post-1/index.md, a post with its images beside it, and post-1.md
They have the same translation_key, which can be any text you choose content/about.md and content/en/about-us.md, each with translation_key: about
They have the same path below two folders whose own pages share a key content/ar/books/book-1.md and content/en/book-list/book-1.md, once books/index.md and book-list/index.md share one

Statac matches a page with a key to its translations by the key alone, and can match a page outside every language folder only by a key. The pages Statac makes have translations too: each language’s home page, and a tag or series page whose URL has the same name in the other language. A page of your own at a tag’s or a series’ URL, such as content/tags/rust.md at /tags/rust/, is that tag’s list page. It is the translation of the page Statac makes for the same tag or series in another language, and each names the other with hreflang. If another language has a page at the same path, such as content/en/tags/rust.md, that page is its translation instead.

A draft or a scheduled post isn’t a translation of any page until a build publishes it.

Each language in templates

Name What it contains
page.language The page’s language code, for the lang attribute.
page.direction ltr or rtl, the direction the page’s text runs, for the dir attribute.
page.translations The same page in the site’s other languages, in the order language lists them. Each has its url, title and language. Empty if there are none.
site.languages Each language’s code, the main one first. Looked up by a code, it returns that language’s home page: site.languages.en.url. If a site sets no language, it contains only en.
site.language, site.title The language and title for the page being built.
site.posts, site.folders, site.tags, site.series, site.feed The ones for the page’s language.
site.pages Every page, in every language. To pick one language’s pages, check page.language.
<html lang="{{ page.language }}" dir="{{ page.direction }}">

For search engines, a template can list in each page’s <head> every language the page is available in, including its own, and mark the main language’s page with x-default:

{% if page.translations and site.url is defined %}
{% set main = site.languages | first %}
<link rel="alternate" hreflang="{{ page.language }}" href="{{ page.url | absolute }}">
{% for other in page.translations %}
<link rel="alternate" hreflang="{{ other.language }}" href="{{ other.url | absolute }}">
{% endfor %}
{% for one in [page] + page.translations if one.language == main %}
<link rel="alternate" hreflang="x-default" href="{{ one.url | absolute }}">
{% endfor %}
{% endif %}

absolute needs the site’s url, so this writes the links only if the site has one.

A language switcher links to the page’s translation in each other language, or to that language’s home page if there is no translation:

<nav aria-label="Languages">
{% for code in site.languages if code != page.language %}
{% set same = page.translations | selectattr("language", "eq", code) | list %}
{% set there = same[0] if same else site.languages[code] %}
<a href="{{ there.url }}" hreflang="{{ code }}" lang="{{ code }}">{{ code }}</a>
{% endfor %}
</nav>

The sitemap can list translations too. See sitemaps.

Where next?

  • Dates: writing a date, and how dates look in each language
  • Lists, tags and series: the lists each language keeps, and their order
  • Settings: language, and every other line of statac.yaml

Something unclear or wrong? Open an issue.