At first, we create our app in English. We face adding multilingual support when the project grows or needs to serve people from countries that have multiple official languages. How can you tackle this with React and headless CMS?
Ivan KiráľPublished on Sep 6, 2022
You started small – you have a headless CMS as your content hub and your React website as a single channel. As time passed, you found out that you needed more channels and languages to reach new markets and be more accessible. This article will help you set up your React site to be multilingual.
The application we will create will use React in combination with Typescript. I recommend using Create React App with a Typescript template as it sets up some of the dependencies automatically. If you want to do it manually, bear in mind we’ll use React hooks and, therefore, in React in at least version of 16.8. We will also need the following libraries to manage the React application successfully:
- Kontent.ai Delivery SDK – to deliver content from Kontent.ai to the application
- React Router – to navigate between routes
- React Intl – to keep information about localization and translate local text
- Universal Cookie – to store last language preference
- Kontent.ai Backup Manager - to import data into your Kontent.ai project
If you are already familiar with React Router, the new version comes with a few changes, such as that routes are selected based on the best match instead of being traversed in order. For a detailed change log, see React Router | Upgrading from v5.
Using headless content storage
In the example, we’ll fetch content from the headless CMS Kontent.ai. It should contain a couple of movies and also some other pages such as About us and Home. Our project will be a simple app consisting of a few pages – Home, About, Movies, and Movie detail. The URLs will be analogical except for the Home page whose URL will be simple "/" and Movie, which we will identify as
The URL structure will slightly change when we add another language. The most typical approach is to use language prefixes, so our URLs will then look like this:
/en/about-us. Before we continue, create a new project in Kontent.ai and note the project ID and Management API key from the project settings.
Next, import the following .zip file with the help of Kontent.ai Backup Manager using the following command:
After the import finishes, you should see the content items in your project.
Note: Acknowledge that your language prefixes should be the same as the localization codenames of your project.
Add a new language to content
Let’s start with adding a new language to our content. In Kontent.ai, head to the Project Settings and Localization. Add a new language and set fallback to the default language. I added a new language and called it
sk. I also have changed the name and codename of the default language to
en to keep language prefixes and codenames intact. Now you are ready to create new language variants of your content items. It’s common that some content items don’t have language variants in all languages. For those cases, Kontent.ai features language fallbacks that deliver language variants of other languages in a defined order. Find out more here.
Now we are ready to start working on our React application. Let’s see how our application will be structured:
Fetching content from the CMS
To obtain data from Kontent.ai, we will use the Kontent.ai Delivery SDK. For starters, we will initialize a client which will be used for querying data. Create a /utils folder, and inside it, create a
client.ts file with the code below. Remember to replace <YOUR_PROJECT_ID> with your actual project ID that you can find in Project settings in Kontent.ai.
Prepare our local content
Apart from fetching our content from Kontent.ai, for some texts, it’s more beneficial to store them locally. It is typical for the data you want to have always ready and don’t want to wait till they are fetched. One of the cases for this scenario is navigation link titles which we’ll keep in JSON files –
sk.json. Keep in mind that React Intl support only
key:value hierarchy, meaning you can’t use nested objects. Although, you might use a separator such as '_' to give your labels a taste of hierarchy:
Now the fun begins! Firstly, we will add support for routing in the
index.tsx which you can find at the top level of our files structure. As mentioned before, we support multilanguage routes using prefixes, therefore every route that has one of our supported languages in route in format
/en/... should navigate us to a component
LocalizedApp which is a semi-step that gets us to the application which will view our pages. The plain URL
/ will navigate us to the language prefix stored in cookies. If no cookie for language is set yet, then it will use the English language by default. Every other route will fall under the 404 page.
Initializing React Intl in LocalizedApp
Our next challenge is to tell components which language is currently being used. We will use the React Intl IntlProvider component for that. Let’s create a
/LocalizedApp.tsx component, which will wrap our app in IntlProvider. This allows every component of the app to access the necessary data – locale, defaultLocale, and messages. In the previous code file, we have already passed a lang variable to our new component LocalizedApp. We can now obtain it and pass it to IntlProvider also with messages from the JSON file and the default language option. In the
useEffect block, we will save our current language into a cookie to make it available for subsequent requests.
Routing and local translations of our app
Great job there! Now we can create links and routes to navigate users around. Let’s look at our simple navigation header first in
app.tsx. We get access to
formatMessage function via
useIntl() hook. Then we use this function to tell React Intl to translate our link titles. It automatically takes provided JSON file from IntlProvider and gives us the right text according to the specified id. Then we specify the routes that our app can navigate through. Notice that the Route for the Movie component consists of
/movies/:codename. The suffix tells us that there is a variable
codename, which can be then obtained via hook from the URL.
Fetching translated content from Kontent.ai
Now it’s time to fetch data from Kontent.ai and display it. We will first process the movie component. Let’s create a
/views folder and a
/views/Movie.tsx file inside it. Firstly, we define a Movie type according to our content type in Kontent.ai. We obtain our locale via
useIntl() and the codename of the movie via
useParams() hook, which lets us obtain data from the URL. Then, we prepare a function that uses our previously defined Kontent.ai delivery client to fetch a movie with the given codename. We also need to provide a language parameter, so that the CMS knows which language version we want. For that, we use
.languageParameter(locale). Then we call the function inside of
useEffect, so it is executed anytime the codename or locale gets changed. Lastly, we can easily render our data in the return function.
Note: Analogically, you can create all the other views.
To handle 404 errors, it might seem like a good idea to have one /404 page for all languages. In our example, we decided to go in a way that every language has its own route – e.g.,
/en/404. The reason is that it’s better for SEO as it is recommended to use different URLs for different languages.
To sum up...
We created a new sample app and installed the necessary dependencies for internationalization and content fetching. We adjusted the implementation to support multiple languages, used the Kontent.ai delivery SDK to fetch appropriate content, and finally added multiple 404 pages to handle non-existing pages. We have discussed how to make a basic react app with routing and multilanguage support using Kontent.ai. If you are more interested in Kontent.ai, you can check React Sample App that can show you more of the integration between React and Kontent.ai.