I18n Content Structures
The structure i18n option defines how localized content is stored on disk. It is set in the top-level or collection-level configuration. Sveltia CMS supports five different structures:
single_file: All locales are stored in a single file.single_file_default_root: Likesingle_file, but the default locale’s fields are stored at the root level of the file (without a locale key), while non-default locales are nested under their locale key. This is designed to support Lume’s Multilanguage plugin.multiple_files: Each locale has its own file with the locale code in the filename.multiple_folders: Each locale has its own folder containing the localized files.multiple_root_folders: Each locale has its own root folder containing all collections.
Deprecation Notice
The multiple_folders_i18n_root structure is deprecated and will be removed in Sveltia CMS v1.0.0. Use multiple_root_folders instead, which has a more intuitive name and the same file structure.
Some frameworks and static site generators may have specific requirements for multilingual content organization. Choose the structure that best fits your project’s needs.
When using a structure other than single_file, Sveltia CMS automatically links localized entries and files based on their filenames or folder paths. From the Content Editor perspective, they appear as a single entry or file with multiple locales, regardless of the underlying file structure. There’s no need to manually link translations.
Entry Collections
The structure i18n option defines how localized content is stored for entry collections. Below are examples of each structure type.
Single File
i18n:
structure: single_file
locales: [en, de, fr][i18n]
structure = "single_file"
locales = ["en", "de", "fr"]{
"i18n": {
"structure": "single_file",
"locales": ["en", "de", "fr"]
}
}{
i18n: {
structure: "single_file",
locales: ["en", "de", "fr"],
},
}The single_file structure stores all locales in a single file:
/<folder>/<path>.<extension>The file path remains the same for all locales:
.
└─ content/
└─ pages/
└─ about.mdAnd the file contains all locales in a single file, with locale keys as top-level properties:
de:
title: Über uns
content: Dies ist die deutsche Version der Seite.
en:
title: About Us
content: This is the English version of the page.
fr:
title: À propos de nous
content: Ceci est la version française de la page.Single File (Default Root)
i18n:
structure: single_file_default_root
locales: [en, de, fr][i18n]
structure = "single_file_default_root"
locales = ["en", "de", "fr"]{
"i18n": {
"structure": "single_file_default_root",
"locales": ["en", "de", "fr"]
}
}{
i18n: {
structure: "single_file_default_root",
locales: ["en", "de", "fr"],
},
}The single_file_default_root structure stores all locales in a single file, just like single_file. The difference is that the default locale’s fields are written at the root level of the file, while non-default locales are nested under their locale key:
lang: [en, de, fr] # auto-generated list of all enabled locales
title: About Us
content: This is the English version of the page.
de:
title: Über uns # default locale (de) at root
content: Dies ist die deutsche Version der Seite.
fr:
title: À propos de nous
content: Ceci est la version française de la page.This format is compatible with Lume’s Multilanguage plugin, which resolves field values from the root level for the default locale and from the nested locale keys for other locales. The lang field is automatically maintained by Sveltia CMS and lists all currently enabled locales.
Limitation
Unlike the examples in Lume’s documentation, all non-default locales will have a complete set of fields, even if their values are the same as the default locale. Make sure to keep the non-default locale fields in sync with the default locale when making changes manually, otherwise you may end up with inconsistent content across locales.
Multiple Files
i18n:
structure: multiple_files
locales: [en, de, fr][i18n]
structure = "multiple_files"
locales = ["en", "de", "fr"]{
"i18n": {
"structure": "multiple_files",
"locales": ["en", "de", "fr"]
}
}{
i18n: {
structure: "multiple_files",
locales: ["en", "de", "fr"],
},
}The multiple_files structure creates separate files for each locale, with the locale code included in the filename:
/<folder>/<path>.<locale>.<extension>The file paths for each locale would look like this:
.
└─ content/
└─ pages/
├─ about.de.md # German
├─ about.en.md # English (default locale)
└─ about.fr.md # FrenchOmitting Default Locale from File Path
When the omit_default_locale_from_file_path option is set to true, the path depends on the locale being the default locale or not:
/<folder>/<path>.<extension> # default locale
/<folder>/<path>.<locale>.<extension> # other localesFor example, with en as the default locale, the English file would be named about.md, while the German and French files would retain the locale suffix:
.
└─ content/
└─ pages/
├─ about.md # English (default locale)
├─ about.de.md # German
└─ about.fr.md # FrenchMultiple Folders
i18n:
structure: multiple_folders
locales: [en, de, fr][i18n]
structure = "multiple_folders"
locales = ["en", "de", "fr"]{
"i18n": {
"structure": "multiple_folders",
"locales": ["en", "de", "fr"]
}
}{
i18n: {
structure: "multiple_folders",
locales: ["en", "de", "fr"],
},
}The multiple_folders structure creates separate folders for each locale, containing the localized files:
/<folder>/<locale>/<path>.<extension>The file paths for each locale would look like this:
.
└─ content/
└─ pages/
├─ de/
│ └─ about.md # German
├─ en/
│ └─ about.md # English (default locale)
└─ fr/
└─ about.md # FrenchOmitting Default Locale from File Path
When the omit_default_locale_from_file_path option is set to true, the path depends on the locale being the default locale or not:
/<folder>/<path>.<extension> # default locale
/<folder>/<locale>/<path>.<extension> # other localesFor example, with en as the default locale, the English file would be located at content/pages/about.md, while the German and French files would be located in their respective locale folders:
.
└─ content/
└─ pages/
├─ about.md # English (default locale)
├─ de/
│ └─ about.md # German
└─ fr/
└─ about.md # FrenchMultiple Root Folders
i18n:
structure: multiple_root_folders
locales: [en, de, fr][i18n]
structure = "multiple_root_folders"
locales = ["en", "de", "fr"]{
"i18n": {
"structure": "multiple_root_folders",
"locales": ["en", "de", "fr"]
}
}{
i18n: {
structure: "multiple_root_folders",
locales: ["en", "de", "fr"],
},
}The multiple_root_folders structure creates separate root folders for each locale, containing all collections:
/<locale>/<folder>/<path>.<extension>The file paths for each locale would look like this:
.
├─ de/
│ └─ pages/
│ └─ about.md # German
├─ en/
│ └─ pages/
│ └─ about.md # English (default locale)
└─ fr/
└─ pages/
└─ about.md # FrenchOmitting Default Locale from File Path
When the omit_default_locale_from_file_path option is set to true, the path depends on the locale being the default locale or not:
/<folder>/<path>.<extension> # default locale
/<locale>/<folder>/<path>.<extension> # other localesFor example, with en as the default locale, the English file would be located at pages/about.md, while the German and French files would be located in their respective locale root folders:
.
├─ pages/
│ └─ about.md # English (default locale)
├─ de/
│ └─ pages/
│ └─ about.md # German
└─ fr/
└─ pages/
└─ about.md # FrenchNested Collections
A nested collection organizes entries into a folder tree, which is a separate thing from the folders and file names i18n adds. The two combine: the locale is taken off the file path before the tree is worked out, so the same tree appears whichever structure you choose, and the localized files of one entry are linked as usual.
The examples below use a pages collection with a products/hardware entry. In the default subfolders mode, where each entry is an index file in a folder of its own:
# single_file
content/pages/products/hardware/_index.md
# multiple_files
content/pages/products/hardware/_index.en.md
content/pages/products/hardware/_index.de.md
# multiple_folders
content/pages/en/products/hardware/_index.md
content/pages/de/products/hardware/_index.md
# multiple_root_folders
en/content/pages/products/hardware/_index.md
de/content/pages/products/hardware/_index.mdAnd with subfolders: false, where each entry is a regular file that keeps its own name:
# single_file
content/pages/products/hardware.md
# multiple_files
content/pages/products/hardware.en.md
content/pages/products/hardware.de.md
# multiple_folders
content/pages/en/products/hardware.md
content/pages/de/products/hardware.md
# multiple_root_folders
en/content/pages/products/hardware.md
de/content/pages/products/hardware.mdIn every case the entry sits at products/hardware in the tree, and the sidebar shows one products folder rather than one per locale. The omit_default_locale_from_file_path option works as it does elsewhere, dropping the locale from the default locale’s path only.
A few things worth knowing:
- The
depthoption counts the path segments below the collection folder, and the locale is not one of them. A locale folder or file-name suffix therefore never uses up part of the depth budget. - Moving an entry with the path editor moves every locale’s file in the same commit. In the
subfoldersmode it takes the entry’s child entries along, in every locale. - Entry-relative media is stored once, not once per locale. With the
multiple_foldersandmultiple_root_foldersstructures, where each locale has a folder of its own, the file is saved in the default locale’s folder and every locale’s entry refers to it by the same relative path. The other structures keep each locale’s file in the folder holding the entry, so the media already sits beside all of them.
Localized Folder Names
The folder names can be localized as well, giving nested content fully localized permalinks. In the subfolders mode, a folder is named after the slug of the entry stored in it, so localizing the entry slugs with the localize filter localizes the whole folder chain above an entry along with the entry itself. With the multiple_folders structure, a products/hardware entry in English could then be stored as follows:
content/pages/en/products/hardware/_index.md
content/pages/de/produkte/hardware/_index.mdThe tree in the sidebar and the path editor still go by the default locale’s names, since that’s how the entries are identified, but the other locales’ path editor panes show the folder as it’s named in their own locale. The localized folder names are taken from the entries stored in the folders, including unpublished ones with the Editorial Workflow, so a section and its sub-pages can be created in one sitting. A folder whose entry lacks a locale keeps its default name in that locale.
When an entry is moved to a different folder, each locale’s file goes below the localized folder chain, and its child entries are moved along in every locale. Renaming the folder is done with the Slug Editor, which offers a folder name per locale when the slugs are localized.
This requires the multiple_files or multiple_folders structure, like localized slugs in general. With subfolders: false, entries are files rather than folders, so a folder takes its localized name from the entry it belongs to: its index file, as named with the meta.path.index_file option or conventionally index or _index, or else a file of the same name stored beside it, such as about.md next to the about folder in an Eleventy or Jekyll project. A folder with neither keeps the same name in every locale.
Because an entry in a nested collection is identified by its path within the collection folder rather than by a bare slug, that path is what the translationKey property holds there, e.g. products/hardware/_index, so two entries with the same slug in different folders stay apart. When an entry is moved, the property is updated in every file it takes along.
Page Bundles
A page bundle keeps an entry’s content file and its media together in one folder. Give a collection a path option ending in a fixed file name to store each entry as a leaf bundle, or use the subfolders mode of a nested collection to store each one as a branch bundle. Combined with entry-relative media, uploads land in the bundle instead of a shared media folder.
The examples below use a posts collection with path: '{{slug}}/index', media_folder: '' and public_folder: '', holding a my-first-post entry with a cover image.
The single_file and multiple_files structures keep every locale in the same bundle, so the media sits beside all of them. The latter is how Hugo’s own translation by file name is organized:
# single_file
content/posts/my-first-post/index.md # cover: image1.jpg, under each locale key
content/posts/my-first-post/image1.jpg
# multiple_files
content/posts/my-first-post/index.en.md # cover: image1.jpg
content/posts/my-first-post/index.de.md # cover: image1.jpg
content/posts/my-first-post/image1.jpgThe multiple_folders and multiple_root_folders structures give each locale a bundle of its own. The media is not copied into each one: it’s stored in the default locale’s bundle, and every locale’s entry refers to it by the same relative path:
# multiple_folders
content/posts/en/my-first-post/index.md # cover: image1.jpg
content/posts/de/my-first-post/index.md # cover: image1.jpg
content/posts/en/my-first-post/image1.jpg
# multiple_root_folders
en/content/posts/my-first-post/index.md # cover: image1.jpg
de/content/posts/my-first-post/index.md # cover: image1.jpg
en/content/posts/my-first-post/image1.jpgSveltia CMS resolves that path across locales, so the image appears in the content editor whichever locale you are editing. Hugo resolves it as well, because a page bundle inherits the resources of its translated pages — but only where the two are linked as translations, which Hugo determines from each language’s contentDir. If your framework has no equivalent mechanism, prefer single_file or multiple_files, which keep the media in the same bundle as every locale’s content file.
File Collections
The structure i18n option only applies to entry collections, except for the single_file_default_root structure as described below. Other structure option values are ignored for file collections, and the organization of localized files is determined by the file path option in the file collection configuration.
You can use the {{locale}} placeholder in the file path option to create separate files or folders for each locale. If the placeholder is not used, the structure defaults to single_file, meaning the same file is shared across all locales.
The following example demonstrates how to set up a file collection with different i18n structures using the {{locale}} placeholder:
collections:
- name: pages
label: Pages
i18n: true
files:
- name: contact
label: Contact Page
i18n: true
file: content/contact.md # equivalent to single_file
- name: about
label: About Page
i18n: true
file: content/about.{{locale}}.md # multiple_files
- name: products
label: Products Page
i18n: true
file: content/{{locale}}/products.md # multiple_folders
- name: settings
label: Site Settings
i18n: true
file: '{{locale}}/settings.yaml' # multiple_root_folders[[collections]]
name = "pages"
label = "Pages"
i18n = true
[[collections.files]]
name = "contact"
label = "Contact Page"
i18n = true
file = "content/contact.md"
[[collections.files]]
name = "about"
label = "About Page"
i18n = true
file = "content/about.{{locale}}.md"
[[collections.files]]
name = "products"
label = "Products Page"
i18n = true
file = "content/{{locale}}/products.md"
[[collections.files]]
name = "settings"
label = "Site Settings"
i18n = true
file = "{{locale}}/settings.yaml"{
"collections": [
{
"name": "pages",
"label": "Pages",
"i18n": true,
"files": [
{
"name": "contact",
"label": "Contact Page",
"i18n": true,
"file": "content/contact.md"
},
{
"name": "about",
"label": "About Page",
"i18n": true,
"file": "content/about.{{locale}}.md"
},
{
"name": "products",
"label": "Products Page",
"i18n": true,
"file": "content/{{locale}}/products.md"
},
{
"name": "settings",
"label": "Site Settings",
"i18n": true,
"file": "{{locale}}/settings.yaml"
}
]
}
]
}{
collections: [
{
name: "pages",
label: "Pages",
i18n: true,
files: [
{
name: "contact",
label: "Contact Page",
i18n: true,
file: "content/contact.md",
},
{
name: "about",
label: "About Page",
i18n: true,
file: "content/about.{{locale}}.md",
},
{
name: "products",
label: "Products Page",
i18n: true,
file: "content/{{locale}}/products.md",
},
{
name: "settings",
label: "Site Settings",
i18n: true,
file: "{{locale}}/settings.yaml",
},
],
},
],
}The resulting file structure would be:
.
├─ content/
│ ├─ contact.md # shared across all locales (single_file)
│ ├─ about.de.md # German
│ ├─ about.en.md # English (default locale)
│ ├─ about.fr.md # French
│ ├─ de/
│ │ └─ products.md # German
│ ├─ en/
│ │ └─ products.md # English (default locale)
│ └─ fr/
│ └─ products.md # French
├─ de/
│ └─ settings.yaml # German
├─ en/
│ └─ settings.yaml # English (default locale)
└─ fr/
└─ settings.yaml # FrenchOmitting Default Locale from File Path
As with entry collections, the omit_default_locale_from_file_path option can be used to omit the locale code from the file path for the default locale when using the {{locale}} placeholder in file collections. The above example would result in the following file structure if en is the default locale and omit_default_locale_from_file_path is set to true:
.
├─ content/
│ ├─ contact.md # shared across all locales (single_file)
│ ├─ about.md # English (default locale)
│ ├─ about.de.md # German
│ ├─ about.fr.md # French
│ ├─ products.md # English (default locale)
│ ├─ de/
│ │ └─ products.md # German
│ └─ fr/
│ └─ products.md # French
├─ settings.yaml # English (default locale)
├─ de/
│ └─ settings.yaml # German
└─ fr/
└─ settings.yaml # FrenchSingle File (Default Root) Structure for File Collections
The single_file_default_root structure can also be used for file collections. The example below shows how to configure a file collection with this structure:
i18n:
structure: single_file_default_root
locales: [en, de, fr]
collections:
- name: pages
label: Pages
files:
- name: about
label: About Page
file: content/about.md[i18n]
structure = "single_file_default_root"
locales = ["en", "de", "fr"]
[[collections]]
name = "pages"
label = "Pages"
[[collections.files]]
name = "about"
label = "About Page"
file = "content/about.md"{
"i18n": {
"structure": "single_file_default_root",
"locales": ["en", "de", "fr"]
},
"collections": [
{
"name": "pages",
"label": "Pages",
"files": [
{
"name": "about",
"label": "About Page",
"file": "content/about.md"
}
]
}
]
}{
i18n: {
structure: "single_file_default_root",
locales: ["en", "de", "fr"],
},
collections: [
{
name: "pages",
label: "Pages",
files: [
{
name: "about",
label: "About Page",
file: "content/about.md",
},
],
},
],
}