⚠️Draft Content

Storyblok Raises $80M Series C - Read News

What’s the True Total Price of Enterprise CMS? Find out here.

Skip to main content

How to swap i18n content in Storyblok using their field type translations feature

Try Storyblok

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

Did you create a beautiful website with a lot of content and then someone came along with a request to change the default language? If the answer is yes, then don't worry, it's pretty easy to swap translated content using the Management API of Storyblok.

Detailed Case Description

We have experienced this situation at Wondrous LTD. As we set up the project, we already knew that the page would have to be multilingual and would require at least 2 language versions of content to go live. Storyblok offers you 4 different solutions for internationalization - Single tree and field-level translation, folder-level translation, multiple trees, and mixed approach. In this case, we choose option 1 - Single tree and field level translation. This means that you have one default language version and you can add as many additional language versions as you want through settings. We added German as a secondary language and set up the whole NuxtJS project with English as the default language in mind.

A while after it was published, they asked if it would be possible to make the German translation default and English secondary. After that, an editor would see “Default” and “English” listed in the dropdown.

Basically, you have two options how to do it:

- You can do it manually and copy & paste all content

- You can use the Management API of Storyblok and write a short script, which will do it for you.

I believe you would choose the second option.

I am going to show you how to do it in NodeJS, but you could also do it in Ruby, Python, PHP, Java, Swift, or C#. See the Storyblok documentation for more info.

Pseudo Code

  • Get schemas of all your components and for each component save which fields are translatable
  • Loop through all your stories
  • Recursively find all translatable fields in stories content
  • Move content of fields to default language and create new translation fields for original content
    •  Create new field “headline__i18n__en” and fill in value of “headline”
    • "headline" value replace with value of “headline__i18n__de”
  • Update original story with new content

Setup script

Install storyblok-js-client and get your personal access token from your account settings - this is not a space token, but an account token. Then, get the id of the space where you will want to perform a swap of content and finally define the new default language and where you want to move the old default content.

  • fromLang define postfix of i18n field - eg. headline__de__
  • toLang define postfix of new field, where the default content will be save - eg. headline__en__
        
      // Get schemas of all components
Storyblok.get(`spaces/${spaceId}/components`, {})
.then(response => {
  // Find translatable fields on each component
  response.data.components.map(component => addTransKeyFromComponent(component))
  // After get of all translatable fields start content swapping
  swapContentOnStories(1) // start contant swapping
}).catch(error => {
  console.log(error)
})

// Finds translatable fields in components and save them into i18nComponentsFields
function addTransKeyFromComponent(component) {
  const compName = component.name
  const compSchema = component.schema
  const compKeys = Object.keys(compSchema)
  let i18nKeys = []
  compKeys.map( key => {
    if (compSchema[key].translatable) {
      i18nKeys.push(key)
    }
  })
  i18nComponentsFields[compName] = i18nKeys
  console.log(`✅   got all i18n fields of ${compName}`)
}
    

Get all stories

Now we know which content of the fields needs to be swapped. So we need to get all stories. The function Storyblok.get(spaces/${spaceId}/stories, {}) returns a paged response of all stories. We get all the pages and, for each story, we will request the content of the story using story.id.

        
      function swapContentOnStories(page=1) {
  Storyblok.get(`spaces/${spaceId}/stories`, {
    page // starting with first page
  })
  .then(response => {
    response.data.stories.map(story => getStoryContent(story.id))
    if (response.total - response.perPage * page > 0) {
      // recursively get all stories from Storyblok - response is paged!!
      swapContentOnStories(page + 1) 
    }
  }).catch(error => {
    console.log(error)
  })
}
    

Get story content

Here we will get content of the story by calling the swapContentOfStory function to swap i18n content. After we swap content, we have to update/push the new content of the story to Storyblok using the function updateStory.

        
      function getStoryContent(storyId) {
  Storyblok.get(`spaces/${spaceId}/stories/${storyId}`, {})
  .then(response => {
    swapContentOfStory(response.data.story.content) // swap content
    console.log(`✅   swapped content on ${response.data.story.name}`)
    updateStory(storyId, response.data.story) // update story in Storyblok
  }).catch(error => {
    console.log(error)
  })
}
    

Swap content

First we need to know which fields of the current content are translatable. For that, we will use the name of the component (content.component) and the previously created object i18mComponentsFields, which contains all translatable fields sorted per component.

Now we look at the keys of the current content if it contains a match with the translatable field of the component. We will perform a swap of content for these fields (lines 6-8).   

If the key is not translatable and it is an Array, we will recursively call one more time this function swapContentOfStory. For all other cases, we will do nothing because these are the fields which are not translatable (like _uid etc.).

        
      function swapContentOfStory(content) {
  const i18nContentKeys = i18nComponentsFields[content.component]
  const contentKeys = Object.keys(content)
  contentKeys.map(key => {
    if(i18nContentKeys && i18nContentKeys.includes(key)) {
      content[`${key}__i18n__${toLang}`] = content[key]
      // if no translation leave the default
      content[key] = content[`${key}__i18n__${fromLang}`] ? content[`${key}__i18n__${fromLang}`] : content[key]
    } else if (Array.isArray(content[key])) {
      content[key].map(component => swapContentOfStory(component))
    } else {
      // Do nothing - not i18n field
    }
  })
}
    

Update story

The last step of the script is an update of the story with new content. For that, you need to have an id of the story and new content and call this function.

        
      function updateStory(storyId, updatedStory) {
  Storyblok.put(`spaces/${spaceId}/stories/${storyId}`, {
    "story": updatedStory,
    "force_update": 1
  }).then(response => {
    console.log(`⬆️   successfully updated - [${response.data.story.name}] `)
  }).catch(error => {
    console.log(error)
  })
}
    

Switching Default Language in Storyblok

At this moment your story in Storyblok will look like the code below. But we are not done yet, you still have to go to the language setting of the space in Storyblok (Space → Settings → Internationalization) and remove the German language and add the English language.

By the way, don't worry about adding/removing languages as this action will not remove content from your stories. It will correctly set up your visual editor of Storyblok and delivery API of Storyblok.

That's it - you can now swap your content as often as you want. And don't worry at all, you will always have backups and the version tree in Storyblok. 

Author

Samuel Snopko

Samuel Snopko

Samuel is the Head of Developer Relations at Storyblok with a passion for JAMStack and beautiful web. Co-creator of DX Meetup Basel and co-organizer of Frontend Meetup Freiburg. Recently fell in love with Vue.js, Nuxt.js, and Storyblok.