With the vast number of languages spoken by users worldwide in different regions, an application's ability to serve content to users in their locales gradually becomes a needed functionality for better user engagement and retention.
Through the i18n plugin, Strapi supports the development of this functionality by providing application admins the ability to store data in different locales and serve them to users within the respective locales.
Within this article, we would explore more about the i18n plugin.
If you are interested in trying out the result of this tutorial before going through the article, we have it here for you to try out.
This article aims to help you understand what Internationalization is and how you can implement it in a Strapi application. This article is broken down into the two sections below;
To keep this article focused on its primary goal, we would not create a new Strapi application; and instead, we would use this Strapi eCommerce template.
The demo application would be an eCommerce store containing localized content for courses to be sold. Using the i18n plugin, the eCommerce application would serve localized content from two locales to a front-end application built with Gatsby.
This article will help you understand what Internationalization is and how you can implement it in a new or existing Strapi application.
If you are new to Strapi, Strapi is easy to learn and feature-rich headless content management system (CMS) with support for PostgreSQL, MongoDB, SQLite, MySQL, and MariaDB.
To run the two demo applications within this tutorial, you need to have node v12 installed on your local machine and understand React.js as we build out the front-end application.
So far, you have seen the word Internationalization reoccur all over this article. — Let's pause at this point to understand what this reoccurring word really means.
From the W3C definition, Internationalization is said to be "the design and development of a product, application or document content that enables easy localization for various target audiences that vary in culture, region, or language."
You would often find Internationalization shortened to i18n, with "i" and "n" representing the first and last letters. In contrast, the number 18 represents the total number of letters when "i" and "n" are subtracted.
While Internationalization refers to designing a product for various target audiences, localization can be narrowed to refer to the adaptation of a product, application, or document content to meet the language, cultural and other requirements of a specific target market (a locale).
One important thing to note is that the application of Internationalization and localization, as defined above, varies across different products as each product has its various target audience across other localities with differing cultural requirements.
You would find the word locale used in various parts of this article. A locale refers to the language with which the content is being created with.
For developers managing the content of their application using Strapi, the recently released i18n plugin provides admins with the means of localising the content served to their end-users.
Backend Setup
For this tutorial, we'll use the Ecommerce template developed by Remi and connect a Gatsby to it for fetching the pre-stored data.
Note: For this tutorial, we will use yarn as the package manager. You can, however, use npm if preferred.
Create an empty project directory to store your project. This folder would serve as the root folder, as you would also create the front-end project in a subdirectory.
Next, use the command below to create a strapi application with the Ecommerce template;
1yarn create strapi-app i18n --template https://github.com/strapi/strapi-template-ecommerce
After a successful installation, the CLI would automatically start the Strapi application. It would help if you opened the admin interface from your web browser at http://localhost:1337/admin to create the admin user for your Strapi application.
The template you just cloned contains the following;
A project that has already populated data within the collection types above.
Feel free to modify the existing data. However, they provide a good starting point for building this project.
Next, install the GraphQL plugin to add GraphQL support to this Strapi Application.
1yarn strapi install graphql
After installing the plugin above, you can access your project's GraphiQL playground at [*http://localhost:1337/graphql*](http://localhost:1337/graphql)
to write queries and mutations for working with the existing data.
Strapi Internationalization ( i18n ) Plugin
The Internationalization plugin gives Strapi application admins the feature to create, manage and distribute localized content across different languages, also known as locales.
Rather than automatically translate delivered content, the i18n plugin allows developers to fetch the right content based on the user's locale.
Note The i18n plugin ships by default with Strapi applications running on version 3.6.0 or higher. Please refer to the installation section of the plugin documentation for installing the i18n plugin on lower versions.
By default, the i18n plugin has en
( English ) as the default locale. However, more locales and their respective content can be added either using the Admin panel or the Strapi Content API.
We would add one more locale to the categories content-type en locale shipped with the cloned eCommerce template for this guide.
Adding A New Locale
Using the Admin panel, navigate to the Settings page of your Strapi application, then click on the Internationalization option within the Global Settings.
Locate and click the "Add a Locale" button at the top right corner to open the locale modal. Using the locales dropdown, select a new locale, select a display name, or leave the default locale name.
For this guide, we would use the fr locale with a default display name of French as shown below;
Enabling Content-Type localization
By default, the content types within the cloned eCommerce template do not have localization enabled. The following steps outline how to enable localization for existing content types;
From the Content-Builder page of your Strapi application, select the content type to be localized, then click the edit pencil icon of the chosen content type to open the edit icon.
Note: You need to start the Strapi develop server using the *npx strapi create*
command to enable life reloads on your Strapi application
From the edit modal, click the Advanced Settings tab, then enable localization using the checkbox at the bottom of the modal.
Adding Locale Content
As stated earlier, the i18n plugin does not automatically translate content stored with Strapi into different locales; instead, it gives admins the feature to store and deliver multiple locale entries.
We would only create one new product for this guide and link it to an existing category to work with the i18n plugin. This should be enough to give you a working idea when implementing Internationalization with the i18n plugin in your application.
fr
( French ) locale.Remember to click the Save button to save this new category!
Now, you would link this product with the previously created category.
fr
locale that was created.Remember to save the new update before exiting the category page!
The Strapi application has been fully set up with the i18n plugin, as we have created a new French ( fr ) locale, added a new category in this locale, and linked a product to the category.
You can now fetch localized content from your Strapi application using the Strapi GraphQL content API.
Frontend Application
Gatsby Setup
Launch the Gatsby installer to create a Gatsby application with the name i18n-frontend from your terminal using the command below;
1npm init gatsby
After the installation process is completed, you run the development server of the newly created Gatsby application to be sure the installation was successful.
1cd i18n-frontend
Application Styling
Create a components
folder within the src
directory and create a layout.js
file containing the code below to create a wrapper using React-helmet to wrap each page with styles from Bootstrap using a CDN link.
1// ./src/components/layout.js
2
3import React from "react";
4import { Helmet } from "react-helmet";
5
6const Layout = ({ children }) => (
7 <div>
8 <Helmet defer={false}>
9 <link
10 rel="stylesheet"
11 href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
12 integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
13 crossorigin="anonymous"
14 />
15 </Helmet>
16 {children}
17 </div>
18);
19
20export default Layout;
Create a styles.js
file in the src
directory. This stylesheet is where you would keep all CSS-in-js styles created using Styles-components.
1// ./src/styles.js
2
3import styled from "styled-components";
4
5export const Cards = styled.ul`
6 display: grid;
7 padding-top: 1rem;
8 grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
9 grid-gap: 2rem 1rem;
10 place-items: center;
11`;
12
13export const Item = styled.div`
14 height: 370px;
15 width: 300px;
16 border-radius: 7px;
17 box-shadow: 0 2px 3px #c0c0c0;
18 background: #fff;
19 color: #1d1b84;
20 text-align: center;
21 div {
22 padding: 0.1rem 0.5rem;
23 h4 {
24 text-align: center;
25 font-weight: 600;
26 }
27 }
28`;
29
30export const Button = styled.button`
31 background: #1d1b84;
32 border: 1px solid #1d1b84;
33 color: #fff;
34 border-radius: 3px;
35 padding: 0.3rem 1rem;
36`;
37
38export const Image = styled.img`
39 height: 250px;
40 width: 250px;
41 object-fit: contain;
42`;
43
44export const Flex = styled.div`
45 display: flex;
46 width: 100%;
47 align-items: center;
48 flex-direction: ${(props) => props.direction};
49 justify-content: ${(props) => props.justify};
50`;
51
52export const Banner = styled.div`
53 padding: 2rem 3rem;
54 text-align: center;
55 h4 {
56 font-weight: 600;
57 }
58`;
59
60export const CategoryCtn = styled.div`
61 width: 20rem;
62 padding: .5rem 1rem;
63 background: #fff;
64 margin: 1rem;
65 border-radius: 5px;
66 box-shadow: 0 2px 3px #c0c0c0;
67`
68
69export const Container = styled.div`
70 background: #ebf4fd;
71 height: calc(100vh - 110px);
72`;
The styles exported in the file above would be used when creating other components.
Install the following extra dependencies needed for this Gatsby application;
1yarn add gatsby-source-graphql moment react-icons styled-components babel-plugin-styled-components
Modify the gatsby-config.js
file with the code below to register the plugins installed above.
1// gatsby-config.js
2
3 module.exports = {
4 siteMetadata: {
5 title: "i18n-frontend",
6 },
7 plugins: [
8 "gatsby-plugin-react-helmet",
9 {
10 resolve: "gatsby-plugin-manifest",
11 options: {
12 icon: "src/images/icon.png",
13 },
14 },
15 { resolve: `gatsby-plugin-styled-components`},
16 {
17 resolve: "gatsby-source-graphql",
18 options: {
19 // Arbitrary name for the remote schema Query type
20 typeName: "STRAPI",
21 // Field for remote schema. You'll use this in your Gatsby query
22 fieldName: "strapi",
23 url: `${process.env.GATSBY_STRAPI_GRAPHQL_ENDPOINT}/graphql`,
24 },
25 },
26 ],
27};
In the configuration file above, you have registered styled components for styling the application.
Also, using the gatsby-source-graphql plugin, you configured the Gatsby application to connect with your Strapi Application using its GraphQL API endpoint stored in environment variables.
You probably don't have the GATSBY_STRAPI_GRAPHQL_ENDPOINT
environment variable yet.
Create a *.env*
file with the following field in the root folder of your Gatsby application.
1GATSBY_STRAPI_GRAPHQL_ENDPOINT="http://localhost:1337"
Next, stop and start the Gatsby develop server
for the remote Strapi schema to be merged into the Gatsby application schema.
Using GraphiQL editor for your Gatsby application at http://localhost:8000/___graphql
, test the GraphQL Query in the image below that you would make from the gatsby application to retrieve data from your Strapi Application.
In the GraphiQL playground above, you can observe the result of the getData
query executed to retrieve data from your running Strapi application. Also, the GraphiQL playground shows the Strapi fields that have been merged in the Gatsby application schema.
Now that the Strapi Schema has been merged with your Gatsby application schema, you can perform GraphQL operations directly from the application.
Create a gatsby-node.js
file in the root folder of the gatsby application and paste the code snippet below into the file to fetch data from the Strapi application and create dynamic pages with the fetched data.
1// ./gatsby-node.js
2
3const path = require("path");
4
5exports.createPages = async ({ graphql, actions }) => {
6 const { createPage } = actions;
7
8 const productsQuery = await graphql(`
9 query getData {
10 strapi {
11 categories(locale: "all") {
12 locale
13 id
14 name
15 slug
16 localizations {
17 locale
18 }
19 products {
20 locale
21 title
22 description
23 price
24 slug
25 image {
26 provider
27 width
28 }
29 }
30 }
31 }
32 }
33 `);
34
35 // Template to create dynamic pages from.
36 const productsTemplate = path.resolve(`src/pages/products.js`);
37
38 productsQuery.data.strapi.categories.forEach(
39 ({ title, id, products, price, slug, name, localizations, locale }) =>
40 {
41 if (localizations.length > 0) {
42 localizations.forEach((data) => {
43 data.locale
44 return createPage({
45 path: `product/${id}/${data.locale.toLowerCase()}`,
46 component: productsTemplate,
47 context: {
48 slug, name, title,
49 id, price, products, locale : data.locale,
50 localizations
51 },
52 })
53 })
54 }
55
56 return createPage({
57 path: `product/${id}/${locale}`,
58 component: productsTemplate,
59 context: { slug, name, title, id, price, products, locale, localizations }
60 })
61 }
62 );
63};
64
You would observe the following operations below performed to retrieve data from the Strapi application from the file above.
getData
query is executed with a locale
parameter to fetch all
categories content-type in the Strapi application.product.js
component as a template, passing the locale
and id
of the category into the component as props.Note: The path option specifies the URL
of each of the dynamic pages. In our use case, each dynamic page created is differentiated using the unique category id
generated by Strapi.
When an iteration is made, we also check if the category has localized content in it. The Strapi Content API would return an empty array if a collection type has localization enabled but has no localized content.
If a collection type has localized content in our use case, a new dynamic page is created to display content in that locale.
Before you restart the gatsby, develop a server to use the new changes in the gatsby-node.js
file. But, first, let us create the product
template and a home page to display all categories.
Create a products.js
file within the src/pages
directory and add the React component in the code snippet below;
1// src/pages/products.js
2
3import React, { useState } from "react";
4import { Cards, Item, Button, Image, Flex, Banner, Container } from "./styles";
5import {graphql, Link, navigate} from 'gatsby'
6
7import Header from "../components/header";
8import Footer from "../components/footer";
9import Layout from "../components/layout";
10
11const shrinkText = (text, length) => {
12 let txt = text.split(" ");
13
14 if (txt.length < 7) {
15 return text;
16 }
17
18 return [ ...txt.splice(0, length), '...' ].join(" ")
19};
20
21const Index = ({ pageContext, data }) => {
22 const { name, localizations, id } = pageContext;
23 const { categories } = data.strapi
24
25 return (
26 <Layout>
27 <Header />
28 <Container>
29 <Banner>
30 <br />
31 <h4 style={{ textAlign: "center" }}> {name} </h4>
32
33 <p> Display products in:
34 {categories[0].localizations.map(({ locale}) =>
35 <Link to={`/product/${id}/${locale.toLowerCase()}`} >
36 <span style={{padding : "0 .5rem"}} > {locale} </span>
37 </Link>
38 )}
39 </p>
40 </Banner>
41 <hr />
42
43 <Cards>
44 {categories.map(({products}) =>
45 products.map(({ id, title, price, description }) => (
46 <Item key={id}>
47 <div>
48 <Image
49 src={
50 "https://res.cloudinary.com/dkfptto8m/image/upload/v1620468926/shirts.jpg"
51 }
52 />
53 <h5> {title} </h5>
54 <p style={{opacity : ".8"}} >{shrinkText(description, 4)} </p>
55
56 <Flex direction="row" justify="space-between">
57 <div>
58 <p style={{ textAlign: "left" }}> ${price} </p>
59 </div>
60
61 <div>
62 <Button>Buy Now</Button>
63 </div>
64 </Flex>
65 </div>
66 </Item>
67 )))}
68 </Cards>
69 <Footer />
70 </Container>
71 </Layout>
72 );
73};
74
75export const query = graphql`
76 query fetchLocaleData($locale: String) {
77 strapi {
78 categories(locale: $locale) {
79 localizations {
80 locale
81 }
82 products {
83 title
84 description
85 price
86 }
87 }
88 }
89 }
90`
91
92export default Index;
Going through the code block above, which makes up the product component, you would observe the following;
Although the page is created dynamically using the createPages API, the component makes an extra graphQL query to fetch localised content using the locale value passed in from the createPages API when the page is created dynamically.
All available locales are displayed at the top banner giving users the feature to click on the links to view the page content in another locale.
With your Gatsby server running, navigate to the http://localhost:8000/product/1/en, it should display the product page with an id
of 1, having "back" as the category name as shown below;
The image above shows all products within the "Backend" category served in the English ( en ) locale. The top banner contains links to other pages containing products for the same category but in a different locale.
For example, clicking the fr
link would display products in the French locale as shown below;
Above, we can see the recently created product with the backend category displayed in the fr
locale.
Finally, let's create a home page that loads all categories with links to their respective products.
Create a home.js
file within the src/pages
directory and add the React component in the code snippet below;
1// src/pages/home.js
2
3import React from "react";
4import { graphql, useStaticQuery, Link } from "gatsby";
5import { FiCalendar, FiBook } from "react-icons/fi";
6import moment from "moment";
7
8import {
9 Container,
10 Flex,
11 Banner,
12 Cards,
13 CategoryCtn,
14} from "./styles";
15import Header from "../components/header";
16import Footer from "../components/footer";
17import Layout from "../components/layout";
18
19const Index = ({ pageContext }) => {
20 const {strapi} = useStaticQuery(graphql`
21 query fetchAllCourses {
22 strapi {
23 categories {
24 id
25 name
26 created_at
27 slug
28 locale
29 products { id }
30 }
31 }
32 }
33 `);
34
35 return (
36 <Layout>
37 <Header />
38 <Container>
39 <Banner>
40 <h4> STRAPI COURSE STORE </h4>
41 <p> A Course Store With Support For Internationalization </p>
42 </Banner>
43 <hr />
44
45 <Cards >
46 {strapi.categories.map(({ id, name, products, created_at, locale }) => (
47 <CategoryCtn key={id}>
48 <div>
49 <Link to={`/product/${id}/${locale}`}>
50 <h5> {name} </h5>
51 </Link>
52 <Flex
53 direction="row"
54 style={{
55 opacity: ".8",
56 }}
57 >
58 <div
59 style={{
60 marginRight: ".3rem",
61 }}
62 >
63 <FiCalendar size={19} />
64 </div>
65
66 <div>Added {moment(created_at).format("dddd mm yyyy")}</div>
67 </Flex>
68 <hr />
69 <br />
70
71 <Flex direction="row">
72 <div
73 style={{
74 marginRight: ".3rem",
75 display: "flex",
76 justifyContent: "center",
77 alignItems: "center",
78 }}
79 >
80 <FiBook size={19} />
81 </div>
82
83 <div
84 style={{
85 display: "flex",
86 justifyContent: "center",
87 alignItems: "center",
88 }}
89 >
90 {products.length} Courses Available
91 </div>
92 </Flex>
93 </div>
94 </CategoryCtn>
95 ))}
96 </Cards>
97 <Footer />
98 </Container>
99 </Layout>
100 );
101};
102
103export default Index;
Save the new file and navigate your Gatsby application homepage to view categories fetched from your Strapi application.
At this point, clicking on any of the categories shown in the image above would navigate you to the product page showing the list of products within that category.
Huge congrats to you. By going through this article, you have learned about Internationalization and localization and how the i18n plugin helps you achieve localization in your Strapi application regarding stored content.
First, we started by explaining what the terms localization and Internationalization meant.
Secondly, we cloned an existing Strapi application using the eCommerce template. Then, we created a new locale and enabled localization on the current content types from the admin panel, after which we inserted a new product in a French ( fr ) locale.
Lastly, we created a new Gatsby application to fetch localized data from the Strapi application.
This article is a guest post by Nwani Victory. He wrote this blog post through the Write for the Community program.
Victory works as a Frontend Engineer and also as an advocate for Cloud Engineering through written articles on Cloud Services as a Technical Author.