Multiple languages
On this page
A Kilim site can be in several languages. Each page is a file, and its translations are files next to it with the language in their name. Declare the languages in the config:
import { defineConfig } from "@kilim-dev/core";
export default defineConfig({
url: "https://example.com",
i18n: { defaultLocale: "en", locales: ["en", "tr"] },
});locales lists every language as a BCP 47 tag, such as en, tr or pt-BR, and includes defaultLocale.
A file without a language suffix is in the default language and keeps its usual URL. .<locale> before .md makes it a translation, served under /<locale>/:
| File | Language | URL |
|---|---|---|
content/index.md | en | / |
content/index.tr.md | tr | /tr/ |
content/posts/hello.md | en | /posts/hello/ |
content/posts/hello.tr.md | tr | /tr/posts/hello/ |
content/404.tr.md | tr | /tr/404.html |
The prefix also applies to permalink patterns, slug and trailingSlash. A frontmatter permalink is used as written, without a prefix. kilim preview and kilim dev answer a missing URL with the nearest 404.html, so /tr/nothing/ gets the Turkish one.
A page does not need every language: a post written only in Turkish is fine.
hello.md and hello.tr.md are versions of one page because their paths match without the suffix. When the names differ, such as about.md and hakkinda.tr.md, give the files the same translationKey. Its value is any string; the path of the default language file is a natural choice:
---
title: Hakkında
translationKey: about
---Like other frontmatter, translationKey has to be in the collection schema: translationKey: z.string().optional(). Two files of one language with the same key are an error.
page.localeis the page's language, andpage.translationsits other versions with theirlocale,urlandtitle.site.i18nhas the default language and every language with itscode, itsnamein itself ("Türkçe") and theurlof its home page.site.collectionsholds the entries in the page's language only.- Pages link their versions with
hreflangalternates and anx-defaultfor the default language, andsitemap.xmllists them asxhtml:linkalternates. Both need configurl. - List pages and feeds are written per language:
/blog/and/blog/feed.xmlfor the default language,/tr/blog/and/tr/blog/feed.xmlonce there are Turkish posts. kilim checkdoes not count translations sharing a title as duplicates.
The default theme sets <html lang>, shows a language switcher in the header (linking to the page's translation, or to that language's home page when there is none), and writes its interface text and dates in the page's language. It has English and Turkish text; other languages get English until you set labels.
List page titles take one title per language:
collections: {
posts: { list: { title: { en: "Blog", tr: "Yazılar" } } },
},The theme's own lists, such as posts, already have English and Turkish titles.
Theme settings in settings/<theme>.yaml apply to every language. settings/<theme>.<locale>.yaml changes them for one language: each top-level setting it has replaces the default one, and the rest stay. A Turkish menu for the default theme:
# settings/default.tr.yaml
menu:
- label: Yazılar
url: /tr/posts/
- label: Hakkında
url: /tr/hakkinda/The default language has no file of its own: its settings are settings/<theme>.yaml. In kilim dev, the admin edits each language's settings, showing what it takes from the default.
The core writes a little text into pages itself: the title of later list pages and the messages of the search box. It has English and Turkish; other languages fall back to English. Change them per language with i18n.messages:
i18n: {
defaultLocale: "en",
locales: ["en", "de"],
messages: {
de: {
page: "{title} (Seite {page})",
searchUnavailable: "Die Suche ist gerade nicht verfügbar.",
searchNoResults: "Keine Ergebnisse für „{query}“.",
},
},
},| Message | English | Placeholders |
|---|---|---|
page | {title} (page {page}) | {title}, {page} |
searchUnavailable | Search is not available right now. | |
searchNoResults | No results for “{query}”. | {query} |
search()writes one index per language (/_kilim/search.tr.json), and each page's search box looks only in its own language.llmsTxt()writes/<locale>/llms.txtfor the other languages.kilim new posts "Merhaba" --locale trcreatescontent/posts/merhaba.tr.md, with atranslationKeycomment to fill in when the schema has one.--of hello --locale trcreatescontent/posts/hello.tr.mdinstead, the translation ofhello.md, with itstranslationKeywhen it has one.- In the admin, the versions of a page share a row, and a missing language can be added as a draft copy to translate.