Internationalization

Many businesses operate on a global scale and in order to be successful and tell a compelling story it is necessary to speak the language of your customers. Storyblok supports content in multiple languages and integrates with various tools to manage translations. These integrations support you in making your content available internationally.

hint:

Storyblok doesn’t charge extra for internationalization of your content. We know that a project can grow quite fast and we don’t want to punish customers for tapping into new markets.

There are two main options for managing multi-language and multi-country content in Storyblok. Different use cases require different approaches which is why we offer field level translation and folder level translation.

  • Field level translation is a good choice if the structure of your content in a different language is the same as in your default language.
  • Folder level translation is preferable when the content for different languages is managed by separate teams or your project is structured differently for each market.

Keep in mind that this decision depends on your content strategy and should be made after considering all the advantages and disadvantages of the solution.

hint:

The decision to use Folder or Field Level Translation is only influenced by your content structure and not the size of your team.

Field Level Translation

Storyblok can store multi-language content on the field level. This means that you only need one content tree and you don’t have to create a stand-alone folder for each country. Each translatable field will be stored in the content tree as a separate stand-alone property with a suffix of the language it belongs to.

As long as your site content is very similar from one language to another, this is the best option. You will have fewer stories to manage and the system takes care of serving the correct language automatically.

Setup

In order to set up field level translation you need to add a new language under the tab “Languages” in the settings of your space {1} and define which fields are translatable in the schema of each component.

Screenshot of Settings tab showing Languages

Once the field is translatable, you should see a language switch like in the following screenshot.

Screenshot of visual editor; 1) language switch

Enable translatable fields

To change fields to be translatable, the checkbox {1} in the schema definition needs to be enabled.

Screenshot of visual editor with schema definition (‘Edit field’) drawer open; 1) checkbox translatable

Once the field is enabled for translations, switching the language in the UI allows you to add a translation for each previously activated field separately. Fields that are not enabled for translation are included but use the default language in every story.

Screenshot of visual editor with translatable content; 1) Checkbox Translate, 2) Copy to field button, 3) Google Translate button, 4) additional translation options

To translate the content of the field, activate the checkbox and add your content. Click on the small arrow {4} to unveil additional options in order to make editing translations easier. When the menu is open the text in default language is shown to support the translation. It is possible to copy the text from the default language to the current language by clicking on the copy item {2}. The translate item {3} will open Google Translate with pre-filled content of the default language.

Translatable slugs

When using field level translations there is often the requirement to translate the slug of a story as well. You do not need to switch to folder level translation if this comes up in your project. With the translatable slugs app it is possible to define slugs for folders and stories in different languages.

Visit the Storyblok App Store to install Translatable Slugs in your space.

Screenshot of visual editor with translatable slug app installed; 1) language drop-down

Once the App is installed, a language selector {1} appears next to the slug input field. It is now possible to change the slug for each language. If a slug for a specific language is not defined, then the system uses the slug of the default language.

hint:

To generate a navigation in the context of a web application or direct search engines to index alternate versions of your stories additional links are added to the links endpoint of Content Delivery API..

Custom Fallback Language

This feature allows you to define a custom fallback language if you use field level translations using the parameter fallback_lang.

Example: The following API call will use the Portuguese version as fallback for Brazilian Portuguese and and if that one doesn’t exist it will use the default language defined in the space settings.

GET https://api.storyblok.com//v1/cdn/stories?fallback_lang=pt&starts_with=pt-br/*

Export and Import of Translations

Export and Import functionality can be added to your space by installing a new app from the Storyblok App Store. For most use-cases it is probably best to install both apps.

Visit the page of these apps and press the “Install” button. Once the apps are installed you can start exporting content from your favorite translation tool. The following steps show how it could look to have a generic workflow for field level translations with an external tool.

Screenshot of config tab; 1) Export, 2) Import

  1. Install the “Import” and “Export” app.
  2. Define your languages in the space settings (e.g.: English = default, German = de, Italian = it).
  3. Begin by filling in content in the default language.
  4. Enable translatable for fields you want to translate.
  5. Select the language you want to translate to (German or Italian) from the menu bar.
  6. Go to the config tab of the content item and click export {1}.
  7. Use the exported JSON or XML file in the translation tool of your choice to translate the default language (e.g.: English) to your target language (e.g.: German).
  8. Import the now translated language using the import button {2} with the target language selected from the menu bar. Keep in mind that newly imported content will overwrite your existing content.
hint:

Read this article to see a real world example on how to use Storyblok with a translation tool: https://www.storyblok.com/docs/editor-guides/translate-with-lingohub

3rd Party Integrations

Translating content with a 3rd party application still involves some manual steps at the moment. We already have a few prototypes implemented which will automate the full process but they are not ready for a full self-service-experience yet. If you have a special use-case that needs to be considered while we implement the integration or you would like to be considered as a beta customer, get in touch with our support team and we’ll figure out together how to proceed.

API and Data Structure

You can access the localized version of the content by adding the language code to your API requests. The following examples show how the language code (de in this case) can be used in the URL.

Get a single story

This request will fetch a single story from the content delivery API.

Default version:

GET https://api.storyblok.com/v1/cdn/stories/home

Localized version:

GET api.storyblok.com/v1/cdn/stories/de/home

Get all stories from one folder

Use this request to fetch all stories from a single folder (e.g.: news). The result is paginated with returning 25 stories per default. Use the per_page parameter to increase this number to up to 100 stories.

Default version:

GET https://api.storyblok.com/v1/cdn/stories?starts_with=news`

Localized version:

GET https://api.storyblok.com/v1/cdn/stories?starts_with=de/news

Get all translated stories at the root level

To get all stories on the root level it is necessary to add the * operator after the language.

GET https://api.storyblok.com/v1/cdn/stories?starts_with=de/*
warn:

The * operator only works in conjunction with languages on the root level. This is a feature to allow folders with the same name as the language.

hint:

You can read more about how to use the Content Delivery API in our API reference.

Get a single story (GraphQL)

This request will fetch a single story (e.g.: story with the id 9248263 ) in a certain language (e.g.: de-DE) from the GraphQL API.

ArticleItem(id: "9248263", language: "de-DE") {
    content {
        template
        content
        component
        title
        trip_state
    }
    lang
}

Get all translated stories at the root level (GraphQL)

Similar to the REST API above the starts_with directive is used to filter articles in a certain language (de-DE) and the * operator to indicate that we are looking for the language and not a folder.

ArticleItems(starts_with: "de-DE/*") {
    total
    items {
        content {
            title
        }
        id
        lang
    }
}
hint:

It is not possible to retrieve all language fields of one story using the Content Delivery API. You are able to

Get all languages

For certain implementations it is necessary to know which and how many languages are needed in advance. You can retrieve that information from the language_codes property in the space information. Find out more about the space object in Content Delivery API docs.

Example

GET https://api.storyblok.com/v1/cdn/spaces/me

Example response

{
  "space": {
    "id": 123,
    "name": "Storyblok.com",
    "domain": "https://www.storyblok.com/",
    "version": 1544117388,
    "language_codes": ["de"]
  }
}

Update a story

Internationalization is also a topic when updating a story. There are two different ways of handling the update for translated content. First, you can pass a lang parameter with your request defining the target language.

Example:

PUT mapi.storyblok.com/v1/spaces/<space-id>/stories/<story-id>
{
    "story": {
        "name": "Story Name",
        ...
    },
    ...
    "lang": "de"
}

Second, if you want to update multiple languages at once, it is possible to provide the values for the translations within the same content object by appending __i18n__ followed by the language code.

required:

Make sure to activate the component field option translatable to make field level translations work.

Example:

POST mapi.storyblok.com/v1/spaces/<space-id>/stories
{
    "story": {
        "name": "My First Article",
        "slug": "first-post",
        "content": {
            "component": "post",
            "headline": "This is awesome!",
            "headline__i18n__de": "Das ist toll!"
        }
    },
    "publish": 1
}
hint:

A great real world example on how to use the __i18n__ functionality is swapping the default language of a complete space. Read more about it in this article: https://www.storyblok.com/tp/how-to-swap-i18n-content-in-storyblok-using-their-field-type-translations-feature

Folder Level Translation

Folder level translation uses separate content trees for each language. One content tree for each language. For example one for English, one for German and so on. With this structure some of the content is duplicated but each localization can be completely customized on the level of structure and order of the components of the story.

Folder level translation is a great choice when your content tree or the structure of your stories is different in every language. Another reason to opt for folder level translation is having separate teams or special needs for different markets.

Setup

To start with folder level translation you need to create folders {1} at the root level of your content repository. These folders {2} represent the languages, country versions of your content or any other level of variations.

Screenshot of content area; 1) add folder button, 2) list of folders

hint:

Folder level translation can be used not only for languages but also to differentiate regions or markets.

Dimensions App

We recommend installing the Dimensions app to create links between stories in different languages. These links are needed to find alternate versions of a story. In the context of a web application, HTML tags, HTTP headers or sitemaps can be used to inform search engines about these alternate versions (hreflang). After installing this app there will be a tree icon {1} at the top right corner of the content editor.

Screenshot of “Dimensions” app; 1) dimensions app button 2) Clone to all, 3) Merge to all, 4) Overwrite, 5) Merge, 6) Create clone

The drop-down menu {1} shows a list of folders and the connected alternative versions. The screenshot shows different actions that are available now. See the table below for an explanation.

ActionDescription
Clone to all {2}Duplicate the current story into all available folders.
Merge to all {3}Merge the changes into all connected stories.
Overwrite {4} Overwrite the existing story in the folder with the current story.
Merge {5} Merge the changes to the selected story.
Create clone {6}Use the current story to generate a duplicate in the selected folder

Alternative version

Screenshot of visual editor with Entry configuration open; 1) Alternative versions

It’s easiest to link the different story using the Dimensions app. If necessary, alternative versions can be linked manually as well. For an existing content element open the entry configuration in the visual editor. At the bottom of this section you can select an alternative version {1} of the current story.

Screenshot of content area with copy dialog open; 1) “Is alternative version of …”

If you copy a story or a folder you can directly link the newly created element with the existing one by activating the “Is alternative version of the duplicate” checkbox {1} in the copy dialog.

Defining alternative versions is necessary to link between different translations of your content. When looking at how these links work with the API we need to look at two properties. group_id is a unique ID that is used to cross reference all stories that belong together. The group_id is then used to populate the alternates property which was added to the response for your convenience. alternates contains an array of UUIDs for stories which have the same group_id as the current story. With these two properties you should have all the information you need to build an application that is translated using folder level translation.

Example:

GET api.storyblok.com/v1/cdn/stories/(:full_slug|:id|:uuid)
{
    "story": {
        "name": "Story Name",
        // group id defines the referenced alternates
        "group_id": "fb33b858-277f-4690-81fb-e0a080bd39ac",
        // resolved alternates by group_id
        "alternates": [],
    }
    ...
}
IMPORTANT:

It is strongly advised that you do not mix folder and field level translations. Using both options together can have unwanted side effects and will add a lot of unnecessary complexity to your project.