Storyblok Raises $80M Series C - Read News

Skip to main content

Manage Multilingual Content in Storyblok and SvelteKit

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

  • Home
  • Tutorials
  • Manage Multilingual Content in Storyblok and SvelteKit

On May 13th, 2024, Storyblok started gradually rolling out a new design for its Visual Editor. Therefore, the Visual Editor product screenshots depicted in this resource may not match what you encounter in the Storyblok App. For more information and a detailed reference, please consult this FAQ on the new Visual Editor design.

Let’s see how to add and manage multiple languages on our website. We will add internationalization to our blog and develop a simple language switcher in our header. For the backend, we will learn how to manage multiple languages in our Storyblok space and how to get the translations from our codebase.

Before starting with the practical part, it is important to know that Storyblok has three different approaches to implementing internationalization. In this tutorial, we will be using Field-Level translation. Depending on your requirements it will make sense to use one instead of the other. You can read all about the different approaches in our Internationalization guide.

Live demo:

If you’re in a hurry, have a look at our code from the SvelteKit Ultimate Tutorial GitHub Repository.


This tutorial is part 6 of the Ultimate Tutorial Series for SvelteKit. We recommend that you follow the previous tutorials before starting this one.


We will use the code from the previous tutorial as a starting point. You can find it here.

Adding a language in Storyblok

First, let's add a new language to our Storyblok space. Go to Settings {1} and click on Internationalization {2}. Here, you will find the configuration for field-level translation.


Although you can select any language you want, for the purpose of this tutorial we will use Spanish as the second language.

Let's select the Spanish language from the drop-down {3} and hit the Add button {4}. Once the Spanish language is added, save the changes by clicking on the Save button {5}.

Adding a language in the Storyblok space

Adding a language in the Storyblok space

If we now go to the Content section and open any Story, we will see a language drop-down in the action bar {1}.

Language drop-down in the Storyblok space action bar

Switching the language from the drop-down won't work because we haven't translated anything yet.

Since we’re using the field-level translation approach, we need to make the fields translatable in our component schema in order to allow the translation of our content. Let's edit the title field of the article Content Type and mark it as (Translatable) {1}. Hit the Save & Back to Fields button after changing the field {2}.

Mark block fields as translatable

If we change the language now, we will get a 500 error in SvelteKit. This is because we have not generated these new routes.

Looking now at the translatable field, you will notice a change in the UI. You will see that the field is non-editable and contains the default language's content {1}. If we want to translate it, we must click on the Translate checkbox {2}.

Use of the "Translate" checkbox per field in the content area

By activating it, we will be able to edit the field and add the content in Spanish. But not only that. As we can see in the screenshot below, when we activate the translate option, an arrow button {1} appears. If we expand it, we will see the default language content {2}, a button to add the default language content in the new language {3}, and the option to go to Google Translate {4}.

Features when field translation is enabled per field

Let's hit the Publish button with the translated content and configure our code in the frontend to make it work.

Implementing i18n in the SvelteKit project

Let’s first generate all the URLs for our SvelteKit site so we don’t get any more errors. Let’s open routes/[…slug]/+page.js to update the load function to account for all new path options: 

      /** @type {import('./$types').PageLoad} */

export async function load({ params, parent, url }) {
  let languages = ['en', 'es']
  let language = ''
  const { storyblokApi } = await parent();
  let slug = params.slug;
  let path = 'cdn/stories/';

  if (slug) {
    let urlParts = slug.split('/')
    if (languages.includes(urlParts[0])){
      language = urlParts[0]
      path += urlParts.join('/')
    else {
      path += slug;
  else {
    language = url.searchParams.get('_storyblok_lang');
    path += 'home'

  const resolveRelations = ['popular-articles.articles']
  const dataStory = await storyblokApi.get(path, {
    version: 'draft',
    resolve_relations: resolveRelations, 
    language: language
  return {
    language: language


Okay, we did quite a bit of refactoring, so let's understand what's happening. In our load function, we are setting an array including our languages, in this case ‘es’ for Spanish and ‘en’ for our default, English. As you will see, in our example, the languages are hardcoded for the sake of simplicity, but of course you could take a dynamic approach here. We are also initializing an empty string as our language variable. In part 2 of the Ultimate Tutorial we learned how to dynamically get the current slug via the params.slug method and passed it on as a parameter to the StoryblokAPI function. Now, with the different languages, we needed to add a bit of logic to make sure the path is returned correctly. 

To do so, we are first taking apart our slug, to be able to check whether a valid language is included. If it is, we store the value in our language variable. Then, we use the .shift() method to take the first part of our urlParts off, as the language information is now stored in the language variable. Finally, we put the urlParts back together. 

In the case that there is no slug, we are checking the url for the _storyblok_lang parameter and store the value in the language variable. 

The language will be passed along in the Storyblok API and returned. 

Translating the AllArticles Component

If we take a look at our Blog home story, it does not work properly. 

Translated version of an article page

We can see the title in Spanish but all the article cards are still showing in English. Let's fix this issue by passing the language to all the components using SvelteKit Stores. 

Go to +page.svelte and set the context to our language: 

    import { setContext } from 'svelte';
    import { writable } from 'svelte/store';

    export let data;

    const lang = writable();
    setContext('lang', lang);

In the AllArticles.svelte we now need to import getContext from Svelte, set a new variable lang with our context and set the language dynamically in the storyblokAPI

	import ArticleCard from './ArticleCard.svelte';
	import { onMount } from 'svelte';
	import { useStoryblokApi } from '@storyblok/svelte';
	import { getContext } from 'svelte';

	const lang = getContext('lang');

	export let blok;
	let articles = [];
	onMount(async () => {
		const storyblokApi = useStoryblokApi();
		const { data } = await storyblokApi.get('cdn/stories', {
			version: 'draft',
			starts_with: 'blog',
			is_startpage: false, 
			language: $lang
		articles = data.stories;

Looking at the Blog home story again, we can see all the article cards showing the correct data. 

Translated article cards

Adding a Language Switcher

To be able to switch the language also from the frontend, let’s add a language switcher to our Header.svelte.

      + <script>
+	import { getContext } from "svelte";
+	const languages = ['en', 'es']
+	let currentLang = getContext('lang');
+ </script>

<header class="w-full h-24 bg-[#f7f6fd]">
	<div class="container h-full mx-auto flex items-center justify-between">
		<a href="/home">
			<h1 class="text-[#50b0ae] text-3xl font-bold">Storyblok SvelteKit</h1>
				<ul class="flex space-x-8 text-lg font-bold">
					<li class="hover:text-[#50b0ae]">
						<a href="/about"> About</a>
					<li class="hover:text-[#50b0ae]">
						<a href="/blog">Blog</a>
+					{#each languages as lang}
+						<li>
+							<a data-sveltekit-reload class:active={$currentLang === lang}
+							href={`/${lang}/home`} class="hover:text-[#50b0ae]" >{lang}</a>
+						</li>
+					{/each}

+ <style>
+	nav {
+		color: #50b0ae;
+	}
+ </style>

Here, in our Header.svelte we are retrieving the current language from our Store and adding our language options as link elements to our header. When the language value in the store changes, we reload our page. We also check which language is active to give it addtional styling.

Wrapping Up

Congratulations, you are now able to build a full-blown multilingual SvelteKit website using Storyblok. In this tutorial, you saw how to add and manage multiple languages in your SvelteKit and Storyblok website with Storyblok's field-level translation. We also added a basic language switcher on the navigation bar of the website, so that your project is ready to go to production and serve as a base for future projects.

Next part:

The next part, exploring how to create a preview environment for your SvelteKit application, will be published soon. Stay tuned!


Josefine Schaefer

Josefine Schaefer

Josefine is a frontend engineer with a passion for JavaScript, web accessibility and community-building. Before entering the tech industry, she worked as a communications specialist - nowadays, she combines these skills with her curiosity for technology and works as a Developer Relations Engineer at Storyblok.