Skip to content

Internationalization

Sveltia CMS comes with first-class support for internationalization (i18n), allowing you to manage content in multiple languages seamlessly. This guide covers the configuration options and best practices for setting up i18n in your Sveltia CMS projects.

Note for Netlify/Decap CMS users

All i18n issues in Netlify/Decap CMS have been resolved in Sveltia CMS, except for issues related to a few unimplemented features. The limitations mentioned in their documentation do not apply to Sveltia CMS.

Why i18n is at the core of Sveltia CMS

Sveltia CMS was originally built for @kyoshino’s Japanese clients, who needed a CMS that could handle multilingual content efficiently. The maintainer is a former long-time localizer for Mozilla and lives in the most diverse city in the world where 150+ languages are spoken.

As a result, i18n support is deeply integrated into Sveltia CMS from the ground up, making it a powerful choice for projects that require multilingual content management.

Configuration

To use i18n support in your Sveltia CMS project, you need to define the i18n option in the top-level, collection-level, and field-level configurations. For a file collection, file-level configuration is also required.

The below example demonstrates how to set up i18n at all levels for an entry collection:

yaml
i18n:
  structure: multiple_folders
  locales: [en, de, fr]

collections:
  - name: posts
    label: Blog Posts
    folder: /content/posts
    i18n: true
    fields:
      - name: title
        label: Title
        widget: string
        i18n: true
      - name: date
        label: Date
        widget: datetime
        i18n: duplicate
      - name: body
        label: Body
        widget: richtext
        i18n: true
toml
[i18n]
structure = "multiple_folders"
locales = ["en", "de", "fr"]

[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
i18n = true

[[collections.fields]]
name = "title"
label = "Title"
widget = "string"
i18n = true

[[collections.fields]]
name = "date"
label = "Date"
widget = "datetime"
i18n = "duplicate"

[[collections.fields]]
name = "body"
label = "Body"
widget = "richtext"
i18n = true
json
{
  "i18n": {
    "structure": "multiple_folders",
    "locales": ["en", "de", "fr"]
  },
  "collections": [
    {
      "name": "posts",
      "label": "Blog Posts",
      "folder": "/content/posts",
      "i18n": true,
      "fields": [
        {
          "name": "title",
          "label": "Title",
          "widget": "string",
          "i18n": true
        },
        {
          "name": "date",
          "label": "Date",
          "widget": "datetime",
          "i18n": "duplicate"
        },
        {
          "name": "body",
          "label": "Body",
          "widget": "richtext",
          "i18n": true
        }
      ]
    }
  ]
}
js
{
  i18n: {
    structure: "multiple_folders",
    locales: ["en", "de", "fr"],
  },
  collections: [
    {
      name: "posts",
      label: "Blog Posts",
      folder: "/content/posts",
      i18n: true,
      fields: [
        {
          name: "title",
          label: "Title",
          widget: "string",
          i18n: true,
        },
        {
          name: "date",
          label: "Date",
          widget: "datetime",
          i18n: "duplicate",
        },
        {
          name: "body",
          label: "Body",
          widget: "richtext",
          i18n: true,
        },
      ],
    },
  ],
}

Read on for detailed explanations of each configuration level.

Top-Level Configuration

Let’s start by defining the i18n option at the top-level of your configuration. The i18n option accepts the following properties:

yaml
i18n:
  structure: multiple_folders
  locales: [en, de, fr]
toml
[i18n]
structure = "multiple_folders"
locales = ["en", "de", "fr"]
json
{
  "i18n": {
    "structure": "multiple_folders",
    "locales": ["en", "de", "fr"]
  }
}
js
{
  i18n: {
    structure: "multiple_folders",
    locales: ["en", "de", "fr"],
  },
}
  • structure: Defines how localized content is stored. See below for available structures.
  • locales: An array of locale codes representing the supported languages.
  • default_locale: (optional) Explicitly sets the default locale. If not provided, the first locale in the locales array is used.
  • omit_default_locale_from_file_path: (optional) When using a structure that creates separate files for each locale (e.g., multiple_files), setting this option to true will omit the locale code from the filename for the default locale. This is useful for frameworks like Zola that expect the default locale to have a specific filename.
  • omit_default_locale_from_preview_path: (optional) When set to true, the preview URL for the default locale will not include the locale code. This is useful for frameworks that expect the default locale to be served at the root path.

Deprecation Notice

The omit_default_locale_from_filename option is deprecated and will be removed in Sveltia CMS v1.0.0. Use omit_default_locale_from_file_path instead, which works with all multiple files/folders structures, not just the multiple_files structure.

Collection-Level Configuration

You can opt into i18n for a specific collection by setting the i18n option to true. This will use the top-level i18n configuration for that collection.

yaml
collections:
  - name: posts
    i18n: true
toml
[[collections]]
name = "posts"
i18n = true
json
{
  "collections": [
    {
      "name": "posts",
      "i18n": true
    }
  ]
}
js
{
  collections: [
    {
      name: "posts",
      i18n: true,
    },
  ],
}

To override the global i18n settings for a specific collection, set the i18n option to an object with the desired configuration.

yaml
collections:
  - name: posts
    i18n:
      structure: multiple_files
      locales: [en, de, fr, ja]
toml
[[collections]]
name = "posts"

[collections.i18n]
structure = "multiple_files"
locales = ["en", "de", "fr", "ja"]
json
{
  "collections": [
    {
      "name": "posts",
      "i18n": {
        "structure": "multiple_files",
        "locales": ["en", "de", "fr", "ja"]
      }
    }
  ]
}
js
{
  collections: [
    {
      name: "posts",
      i18n: {
        structure: "multiple_files",
        locales: ["en", "de", "fr", "ja"],
      },
    },
  ],
}

Note that the structure option at the collection level only applies to entry collections. For file collections, see the File Collections section below.

File-Level Configuration

Each file in a file collection can also have the i18n option set to true to enable localization for that specific file. As with collections, the file will use the top-level i18n configuration.

yaml
collections:
  - name: pages
    label: Pages
    i18n: true
    files:
      - name: about
        label: About Page
        file: content/about.md
        i18n: true
toml
[[collections]]
name = "pages"
label = "Pages"
i18n = true

[[collections.files]]
name = "about"
label = "About Page"
file = "content/about.md"
i18n = true
json
{
  "collections": [
    {
      "name": "pages",
      "label": "Pages",
      "i18n": true,
      "files": [
        {
          "name": "about",
          "label": "About Page",
          "file": "content/about.md",
          "i18n": true
        }
      ]
    }
  ]
}
js
{
  collections: [
    {
      name: "pages",
      label: "Pages",
      i18n: true,
      files: [
        {
          name: "about",
          label: "About Page",
          file: "content/about.md",
          i18n: true,
        },
      ],
    },
  ],
}

If you want to override the global i18n settings for a specific file, set the i18n option to an object with the desired configuration. The following example enables localization only for English and French for the about file:

yaml
collections:
  - name: pages
    label: Pages
    i18n: true
    files:
      - name: about
        label: About Page
        file: content/about.md
        i18n:
          locales: [en, fr]
toml
[[collections]]
name = "pages"
label = "Pages"
i18n = true

[[collections.files]]
name = "about"
label = "About Page"
file = "content/about.md"

[collections.files.i18n]
locales = ["en", "fr"]
json
{
  "collections": [
    {
      "name": "pages",
      "label": "Pages",
      "i18n": true,
      "files": [
        {
          "name": "about",
          "label": "About Page",
          "file": "content/about.md",
          "i18n": {
            "locales": ["en", "fr"]
          }
        }
      ]
    }
  ]
}
js
{
  collections: [
    {
      name: "pages",
      label: "Pages",
      i18n: true,
      files: [
        {
          name: "about",
          label: "About Page",
          file: "content/about.md",
          i18n: {
            locales: ["en", "fr"],
          },
        },
      ],
    },
  ],
}

Note that the structure option does not apply to file collections. See the File Collections section below for more details.

Field-Level Configuration

Each field can be localized individually by setting the i18n option. The i18n option for fields accepts the following values:

  • true
    • The field can be edited separately for each locale.
    • The values are stored separately for each locale.
  • false (default)
    • The field can only be edited in the default locale.
    • The field is hidden in non-default locales.
    • Only the default locale’s value is stored.
  • duplicate
    • The field can only be edited in the default locale.
    • The field is read-only in non-default locales, displaying the default locale’s value.
    • The same value is stored for all locales.

Legacy option values

For backward compatibility with Netlify/Decap CMS, Sveltia CMS also supports translate and none values for the field-level i18n option, which are equivalent to true and false, respectively.

Here’s how the i18n option behaves for different field types:

  • The Hidden field type does not have a visible UI representation, but you’ll still need to set the i18n option to true or duplicate to store values in non-default locales.
  • The complex List field type:
    • true makes all subfields editable separately for each locale.
    • duplicate makes all subfields duplicate. When the user adds, reorders, or removes list items in the default locale, the same changes are reflected in other locales. Subfields can be configured with their own i18n options.
  • The Object field type:
    • true makes all subfields editable separately for each locale.
    • duplicate makes all subfields duplicate. Subfields can be configured with their own i18n options.

Here’s an example of how to configure fields with different i18n options:

yaml
fields:
  - name: title
    label: Title
    widget: string
    i18n: true
  - name: date
    label: Date
    widget: datetime
    i18n: duplicate
toml
[[fields]]
label = "Title"
name = "title"
widget = "string"
i18n = true

[[fields]]
label = "Date"
name = "date"
widget = "datetime"
i18n = "duplicate"
json
{
  "fields": [
    {
      "label": "Title",
      "name": "title",
      "widget": "string",
      "i18n": true
    },
    {
      "label": "Date",
      "name": "date",
      "widget": "datetime",
      "i18n": "duplicate"
    }
  ]
}
js
{
  fields: [
    {
      label: "Title",
      name: "title",
      widget: "string",
      i18n: true,
    },
    {
      label: "Date",
      name: "date",
      widget: "datetime",
      i18n: "duplicate",
    },
  ],
}

When the i18n options is true, all locale are required to have a value for the field. if you want to make the field required only for specific locales, you can use an array value for the required option, which defaults to true.

In the following example, the summary field is required for English and German locales only:

yaml
fields:
  - name: summary
    label: Summary
    widget: string
    i18n: true
    required: [en, de]
toml
[[fields]]
label = "Summary"
name = "summary"
widget = "string"
i18n = true
required = ["en", "de"]
json
{
  "fields": [
    {
      "label": "Summary",
      "name": "summary",
      "widget": "string",
      "i18n": true,
      "required": ["en", "de"]
    }
  ]
}
js
{
  fields: [
    {
      label: "Summary",
      name: "summary",
      widget: "string",
      i18n: true,
      required: ["en", "de"],
    },
  ],
}

Managing Content Structure

Sveltia CMS supports four different structures:

  • single_file: All locales are stored in a single file.
  • 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

yaml
i18n:
  structure: single_file
  locales: [en, de, fr]
toml
[i18n]
structure = "single_file"
locales = ["en", "de", "fr"]
json
{
  "i18n": {
    "structure": "single_file",
    "locales": ["en", "de", "fr"]
  }
}
js
{
  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.md

And the file contains all locales in a single file, with locale keys as top-level properties:

yaml
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.

Multiple Files

yaml
i18n:
  structure: multiple_files
  locales: [en, de, fr]
toml
[i18n]
structure = "multiple_files"
locales = ["en", "de", "fr"]
json
{
  "i18n": {
    "structure": "multiple_files",
    "locales": ["en", "de", "fr"]
  }
}
js
{
  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:

yaml
/<folder>/<path>.<locale>.<extension>

The file paths for each locale would look like this:

yaml
.
└─ content/
   └─ pages/
      ├─ about.de.md  # German
      ├─ about.en.md  # English (default locale)
      └─ about.fr.md  # French

Omitting 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:

yaml
/<folder>/<path>.<extension> # default locale
/<folder>/<path>.<locale>.<extension> # other locales

For 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:

yaml
.
└─ content/
   └─ pages/
      ├─ about.md     # English (default locale)
      ├─ about.de.md  # German
      └─ about.fr.md  # French

Multiple Folders

yaml
i18n:
  structure: multiple_folders
  locales: [en, de, fr]
toml
[i18n]
structure = "multiple_folders"
locales = ["en", "de", "fr"]
json
{
  "i18n": {
    "structure": "multiple_folders",
    "locales": ["en", "de", "fr"]
  }
}
js
{
  i18n: {
    structure: "multiple_folders",
    locales: ["en", "de", "fr"],
  },
}

The multiple_folders structure creates separate folders for each locale, containing the localized files:

yaml
/<folder>/<locale>/<path>.<extension>

The file paths for each locale would look like this:

yaml
.
└─ content/
   └─ pages/
      ├─ de/
      │  └─ about.md  # German
      ├─ en/
      │  └─ about.md  # English (default locale)
      └─ fr/
         └─ about.md  # French

Omitting 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:

yaml
/<folder>/<path>.<extension> # default locale
/<folder>/<locale>/<path>.<extension> # other locales

For 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:

yaml
.
└─ content/
   └─ pages/
      ├─ about.md     # English (default locale)
      ├─ de/
      │  └─ about.md  # German
      └─ fr/
         └─ about.md  # French

Multiple Root Folders

yaml
i18n:
  structure: multiple_root_folders
  locales: [en, de, fr]
toml
[i18n]
structure = "multiple_root_folders"
locales = ["en", "de", "fr"]
json
{
  "i18n": {
    "structure": "multiple_root_folders",
    "locales": ["en", "de", "fr"]
  }
}
js
{
  i18n: {
    structure: "multiple_root_folders",
    locales: ["en", "de", "fr"],
  },
}

The multiple_root_folders structure creates separate root folders for each locale, containing all collections:

yaml
/<locale>/<folder>/<path>.<extension>

The file paths for each locale would look like this:

yaml
.
├─ de/
│  └─ pages/
│     └─ about.md  # German
├─ en/
│  └─ pages/
│     └─ about.md  # English (default locale)
└─ fr/
   └─ pages/
      └─ about.md  # French

Omitting 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:

yaml
/<folder>/<path>.<extension> # default locale
/<locale>/<folder>/<path>.<extension> # other locales

For 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:

yaml
.
├─ pages/
│  └─ about.md     # English (default locale)
├─ de/
│  └─ pages/
│     └─ about.md  # German
└─ fr/
   └─ pages/
      └─ about.md  # French

File Collections

The structure i18n option only applies to entry collections. For file collections, 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:

yaml
collections:
  - name: pages
    label: Pages
    files:
      - name: contact
        label: Contact Page
        file: content/contact.md # equivalent to single_file
      - name: about
        label: About Page
        file: content/about.{{locale}}.md # multiple_files
      - name: products
        label: Products Page
        file: content/{{locale}}/products.md # multiple_folders
      - name: settings
        label: Site Settings
        file: '{{locale}}/settings.yaml' # multiple_root_folders
toml
[[collections]]
name = "pages"
label = "Pages"

[[collections.files]]
name = "contact"
label = "Contact Page"
file = "content/contact.md"

[[collections.files]]
name = "about"
label = "About Page"
file = "content/about.{{locale}}.md"

[[collections.files]]
name = "products"
label = "Products Page"
file = "content/{{locale}}/products.md"

[[collections.files]]
name = "settings"
label = "Site Settings"
file = "{{locale}}/settings.yaml"
json
{
  "collections": [
    {
      "name": "pages",
      "label": "Pages",
      "files": [
        {
          "name": "contact",
          "label": "Contact Page",
          "file": "content/contact.md"
        },
        {
          "name": "about",
          "label": "About Page",
          "file": "content/about.{{locale}}.md"
        },
        {
          "name": "products",
          "label": "Products Page",
          "file": "content/{{locale}}/products.md"
        },
        {
          "name": "settings",
          "label": "Site Settings",
          "file": "{{locale}}/settings.yaml"
        }
      ]
    }
  ]
}
js
{
  collections: [
    {
      name: "pages",
      label: "Pages",
      files: [
        {
          name: "contact",
          label: "Contact Page",
          file: "content/contact.md",
        },
        {
          name: "about",
          label: "About Page",
          file: "content/about.{{locale}}.md",
        },
        {
          name: "products",
          label: "Products Page",
          file: "content/{{locale}}/products.md",
        },
        {
          name: "settings",
          label: "Site Settings",
          file: "{{locale}}/settings.yaml",
        },
      ],
    },
  ],
}

The resulting file structure would be:

yaml
.
├─ 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   # French

Omitting 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:

yaml
.
├─ 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   # French

Disabling Non-Default Locale Content

Developers can specify locales to be enabled by default when users create a new entry draft, using the initial_locales i18n option, which accepts a locale list, default (default locale only) or all (all locales).

The default locale is always enabled, even if it’s excluded from initial_locales, while other locales can be enabled or disabled by users in the Content Editor through the three-dot menu in the top right corner, if this i18n option is defined.

The following example disables German by default, but users can manually enable it if needed. Users can also disable French, which is enabled by default.

yaml
i18n:
  structure: multiple_files
  locales: [en, fr, de]
  default_locale: en
  initial_locales: [en, fr]
toml
[i18n]
structure = "multiple_files"
locales = ["en", "fr", "de"]
default_locale = "en"
initial_locales = ["en", "fr"]
json
{
  "i18n": {
    "structure": "multiple_files",
    "locales": ["en", "fr", "de"],
    "default_locale": "en",
    "initial_locales": ["en", "fr"]
  }
}
js
{
  i18n: {
    structure: "multiple_files",
    locales: ["en", "fr", "de"],
    default_locale: "en",
    initial_locales: ["en", "fr"],
  },
}

Deprecation Notice

The save_all_locales option has been deprecated in favor of the more flexible initial_locales option and will be removed in Sveltia CMS v1.0.0. If you are upgrading from an older version, update your configuration accordingly: save_all_locales: false is equivalent to initial_locales: all, while save_all_locales: true is equivalent to initial_locales being omitted (all locales enabled by default).

Managing Entry Slugs with I18n

When i18n is enabled for an entry collection, managing entry slugs (file names) across different locales can be challenging. Sveltia CMS offers two solutions: localized entry slugs and UUID-based slugs.

Localizing Entry Slugs

In Sveltia CMS, it’s possible to localize entry slugs (filenames) if the i18n structure is multiple_files or multiple_folders. All you need is the localize filter for slug template tags:

yaml
i18n:
  structure: multiple_folders
  locales: [en, fr]

slug:
  encoding: ascii
  clean_accents: true

collections:
  - name: posts
    label: Blog posts
    folder: /content/posts
    slug: '{{title | localize}}'
    format: yaml
    i18n: true
    fields:
      - name: title
        label: Title
        widget: string
        i18n: true
toml
[i18n]
structure = "multiple_folders"
locales = ["en", "fr"]
[slug]
encoding = "ascii"
clean_accents = true
[[collections]]
name = "posts"
label = "Blog posts"
folder = "/content/posts"
slug = "{{title | localize}}"
format = "yaml"
i18n = true
[[collections.fields]]
name = "title"
label = "Title"
widget = "string"
i18n = true
json
{
  "i18n": {
    "structure": "multiple_folders",
    "locales": ["en", "fr"]
  },
  "slug": {
    "encoding": "ascii",
    "clean_accents": true
  },
  "collections": [
    {
      "name": "posts",
      "label": "Blog posts",
      "folder": "/content/posts",
      "slug": "{{title | localize}}",
      "format": "yaml",
      "i18n": true,
      "fields": [
        {
          "name": "title",
          "label": "Title",
          "widget": "string",
          "i18n": true
        }
      ]
    }
  ]
}
js
{
  i18n: {
    structure: "multiple_folders",
    locales: ["en", "fr"],
  },
  slug: {
    encoding: "ascii",
    clean_accents: true,
  },
  collections: [
    {
      name: "posts",
      label: "Blog posts",
      folder: "/content/posts",
      slug: "{{title | localize}}",
      format: "yaml",
      i18n: true,
      fields: [
        {
          name: "title",
          label: "Title",
          widget: "string",
          i18n: true,
        },
      ],
    },
  ],
}

With this configuration, an entry is saved with localized filenames, while the default locale’s slug is stored in each file as an extra translationKey property, which is used in Hugo’s multilingual support. Sveltia CMS and Hugo read this property to link localized files.

For example, if you create a blog post with the title "My trip to New York" in English and "Mon voyage à New York" in French, the following files will be created:

  • content/posts/en/my-trip-to-new-york.yaml
    yaml
    translationKey: my-trip-to-new-york
    title: My trip to New York
  • content/posts/fr/mon-voyage-a-new-york.yaml
    yaml
    translationKey: my-trip-to-new-york
    title: Mon voyage à New York

You can customize the property name and value for a different framework or i18n library by adding the canonical_slug option to your top-level or collection-level i18n configuration. The example below is for @astrolicious/i18n, which requires a locale prefix in the value (discussion):

yaml
i18n:
  canonical_slug:
    key: defaultLocaleVersion # default: translationKey
    value: 'en/{{slug}}' # default: {{slug}}
toml
[i18n]
canonical_slug = { key = "defaultLocaleVersion", value = "en/{{slug}}" }
json
{
  "i18n": {
    "canonical_slug": {
      "key": "defaultLocaleVersion",
      "value": "en/{{slug}}"
    }
  }
}
js
{
  i18n: {
    canonical_slug: {
      key: "defaultLocaleVersion",
      value: "en/{{slug}}",
    },
  },
}

For Jekyll, you may want to use the ref property:

yaml
i18n:
  canonical_slug:
    key: ref
toml
[i18n]
canonical_slug = { key = "ref" }
json
{
  "i18n": {
    "canonical_slug": {
      "key": "ref"
    }
  }
}
js
{
  i18n: {
    canonical_slug: {
      key: "ref",
    },
  },
}

Making Slugs Editable

To make slugs editable for each locale, you can set the slug option to {{fields._slug | localize}} in your collection configuration:

yaml
collections:
  - name: posts
    label: Blog Posts
    folder: /content/posts
    slug: "{{fields._slug | localize}}"
toml
[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
slug = "{{fields._slug | localize}}"
json
{
  "collections": [
    {
      "name": "posts",
      "label": "Blog Posts",
      "folder": "/content/posts",
      "slug": "{{fields._slug | localize}}"
    }
  ]
}
js
{
  collections: [
    {
      name: "posts",
      label: "Blog Posts",
      folder: "/content/posts",
      slug: "{{fields._slug | localize}}",
    },
  ],
}

Using Random UUIDs for Slugs

Entry titles and other fields containing non-Latin characters, such as Japanese or Chinese, may not be suitable for generating slugs. To address this issue, Sveltia CMS lets you use random UUIDs as slugs for entries in i18n collections.

This can be achieved by setting the slug option with the {{uuid}}, {{uuid_short}} or {{uuid_shorter}} template tag in your collection configuration:

yaml
collections:
  - name: posts
    label: Blog Posts
    folder: /content/posts
    slug: "{{uuid_short}}"
toml
[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
slug = "{{uuid_short}}"
json
{
  "collections": [
    {
      "name": "posts",
      "label": "Blog Posts",
      "folder": "/content/posts",
      "slug": "{{uuid_short}}"
    }
  ]
}
js
{
  collections: [
    {
      name: "posts",
      label: "Blog Posts",
      folder: "/content/posts",
      slug: "{{uuid_short}}",
    },
  ],
}

Managing Preview Paths with I18n

When i18n is enabled for an entry collection, you can manage preview paths for each locale using the preview_path option. This option supports the {{locale}} template tag, which will be replaced with the current locale in the preview URL. For example, if you set the preview_path to /{{locale}}/{{slug}}, the preview URL for an entry with the slug my-post in the fr locale would be /fr/my-post.

The omit_default_locale_from_preview_path option can be used to omit the locale code from the preview path for the default locale. For example, if en is the default locale and omit_default_locale_from_preview_path is set to true, the preview URL for the English version of the entry would be /my-post, while the preview URL for the French version would still be /fr/my-post.

Other I18n Features

Sveltia CMS embeds i18n support everywhere in the configuration and UI. Here are some additional features that enhance the multilingual content management experience.

Translating Content

Sveltia CMS provides a built-in mechanism for managing content translations. When you create or edit an entry in one locale, you can easily switch to another locale and provide the translated content. The CMS automatically links the translations together based on the i18n configuration.

Adding or updating translations can be done directly in the Content Editor. When viewing an entry, you can use the locale switcher in the top-right corner to switch between locales. If a translation for the selected locale does not exist, you will see an option to create a new translation.

When creating a new translation, Sveltia CMS will pre-fill the fields with the content from the default locale, allowing you to modify only the necessary parts for the translation. This streamlines the translation process and ensures consistency across different language versions of your content.

Content Editor Features

See the Content Editor I18n Support guide for details on how Sveltia CMS enhances the Content Editor experience for multilingual content management, including locale switching, validation, and previewing.

AI-Powered Translations

Sveltia CMS supports AI-powered translation integrations that can automatically translate content between supported languages. This feature can significantly speed up the localization process, especially for large amounts of content. You can enable AI translations in the CMS settings and choose your preferred translation service. See the Translation Services guide for more details.

Showcase

See our Showcase for examples of websites using Sveltia CMS with i18n support. Most of them come with a link to the source code, which can be a great resource for learning how to implement i18n in your own projects.

Released under the MIT License.