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 inlanguages/fa.yamlkeeps Persian dates in the digits0to9. - A file for a region adds to Statac’s translations for that region:
languages/fr-CA.yamladds to Statac’sfr-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-GBreadslanguages/en-GB.yaml), a region’s broader form (en-GBreadslanguages/en-001.yaml,es-MXreadslanguages/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-TWreadszh-TW.yaml, thenzh-Hant.yaml, thenzh.yaml. Statac reads only the first file it finds. - A language Statac has translations for, written in another script,
such as
en-Shaworja-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/ 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/ 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.mdonly if the site hastemplates/home.html. A folder inside a top has a list page if it has anindex.md, as lists describes. feed: trueinstatac.yamlasks for a feed for each language’s own list. A folder’sindex.mdasks for the folder’s feed, as feeds describes.- Tags and series belong to each language separately:
Ruston an English page andRuston an Arabic page are two tags. - A post’s previous and next posts, a list’s pagination,
related(page), aposts:line andeach: site.postsall 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
titlefor that language:titleoren.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,siterefers 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/ and content/, 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/, 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 ofstatac.yaml
Something unclear or wrong? Open an issue.