Building a News App Using React.js with i18n API in Strapi
Tags: strapi, i18n, react, api
Published: 2022-09-26 07:58:46

In this tutorial, you will build a News app using Strapi and [React.js](https://strapi.io/integrations/react-cms). You will use the [Strapi i18n plugin](https://strapi.io/features/internationalization) to translate the backend content from one language to another. Strapi  will be used to build the backend and provide the API endpoints, and [React.js](https://strapi.io/integrations/react-cms) we will use to build our frontend part.

## What is Strapi?
[Strapi is an open-source headless content management system based on Node.js](https://strapi.io/). Strapi provides us with a nice UI to build our APIs. We can build APIs in the form of collections. A collection is a web resource that has endpoints — GET, DELETE, POST, and PUT. These endpoints perform a separate action to the web resource.

Strapi provides the web resource endpoints without us writing any server code. The web resources content can be added to Strapi via its admin panel, also using the API.

For example, if we create a `movies` collection from the admin panel. Strapi will provide us with the following endpoints:

- `/movies` GET: This gets all the movies in the backend.
- `/movies/:id` GET: This gets a movie from the backend.
- `/movies/:id` PUT: This edits a movie.
- `/movies` POST: This adds a new movie to the backend.
- `/movies/:id` DELETE: This removes a movie from the backend.

So with just a collection, we get the above API endpoints. Strapi is self-hosted. It has an in-built server that serves the collections we create. Also, a Strapi project can be hosted in the cloud.

Strapi supports relational databases such as MySQL, SQLite, PostgreSQL, etc. With Strapi, we only have to build the frontend (mobile, web, or desktop) and use HTTP to fetch the data from the Strapi backend.

## The i18n Strapi Plugin
Content internationalization is very popular these days. You can build an app with content displayed in English, but there are chances that a Chinese or Spanish user. You wouldn't want the language to be a barrier that stops users from accessing your app. 

With content internationalization, you can translate the content of your application from one language to another. You can translate from English to Chinese, from English to Spanish, French to German, etc.

[The Strapi i18n plugin](https://strapi.io/features/internationalization) allows users to create different localized/language versions of their API content. It helps developers build different language versions of their projects by fetching the content based on the language/locale of the user.

## Prerequisites
Here are some things you should know, have, or do before continuing this tutorial.

### Knowledge Requirements
To follow along, you need to have some basic knowledge of:
- [Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript): A scripting language for web pages.
- [React](https://reactjs.org/): A Javascript library for building user interfaces.
- [Node.js](https://nodejs.dev/learn): A Javascript runtime environment for the backend.
- [Shell (Bash)](https://www.gnu.org/software/bash/manual/bash.html): Command language interpreter. 
- [Git](https://git-scm.com/book/en/v2): Distributed version control system.

### Software Requirements
In this tutorial, we will need to install some tools if not already installed:
- **Operating System** (any of the following are supported): 
    - Ubuntu >= 18.04 (LTS-Only). I used Ubuntu 20.04 LTS for this tutorial.
    - Debian >= 9.x
    - CentOS/RHEL >= 8
    - macOS Mojave or newer (ARM not supported)
    -  Windows 10 or 11
    - Docker - [docker repo](https://github.com/strapi/strapi-docker)

- **Node**: Both Strapi and Reactjs are Node-based tools and frameworks respectively. Supported versions are v14 or v16. Odd-number versions of Node are not supported (e.g. v13 or v15). To download the Nodejs binary, get it from the [Download | Nodejs](https://nodejs.org/) page. I used Node v16.14.2.
    
- **npm**: This is a Node package manager. This comes bundled with the Nodejs binary. I used npm v8.5.0.
    
- **yarn (Optional)**: An alternative to **npm**. Check [Installation | Yarn](https://classic.yarnpkg.com/en/docs/install) for installation instructions.
    
- **Postman**: An API platform to test our app’s API endpoints. [Download the desktop version](https://www.postman.com/downloads/) or [sign up for an account](https://identity.getpostman.com/signup) to use the web version.
    
- **Code Editor**: Any editor of your choice. I used [VSCode](https://code.visualstudio.com/).
    
- **Web Browser:** Any Chromium-based browser. I used Google Chrome.
    
- **Git**: For version control. Download Git from the [Git - Downloads](https://git-scm.com/downloads) page.
    
- **Git Bash:** (for Windows users). The tutorial uses Unix shell commands. Git Bash can run UNIX shell commands in Windows. Git Bash ships with your Git download.

## Hardware Requirements
- At least 1 CPU core (Highly recommended, at least 2).
- At least 2GB of RAM (Moderately recommended).
- Minimum required storage space recommended by your OS or 32 GB of free space.

## Set up Strapi Backend
The first thing we need to do is set up the Strapi backend. 

> **NOTE**: If you prefer testing the ready-made app, you can clone the [Github repo](https://github.com/Marktawa/newsapp).

### 1. Scaffold a Strapi Project
In your terminal, create a folder to contain the entire source code for this tutorial. Name it `newsapp`, move into it, and initialize git for source control.

```git
    $ mkdir newsapp
    $ cd newsapp
    /newsapp $ git init
```

Create a Strapi project named `newsapp-api`.
```bash
    /newsapp $ npx create-strapi-app newsapp-api --quickstart
    # or
    /newsapp $ yarn create strapi-app newsapp-api --quickstart
```

This will create a Strapi project in the `newsapp-api` folder. The `--quickstart` flag sets up your Strapi app with an [SQLite](https://www.sqlite.org/index.html) database. After executing the command, enter `y` if prompted to proceed with the installation. Wait for Strapi and all its dependencies to be installed in the `newsapp-api` folder.

After installation, your app should start automatically. Stop the server by pressing `Ctrl` plus `C` on your keyboard. Your project folder `newsapp` after installing Strapi should have a structure similar to this:

```
    /newsapp $ tree -L 2 -a
    .
    ├── newsapp-api
    │   ├── build
    │   ├── .cache
    │   ├── config
    │   ├── data
    │   ├── database
    │   ├── .editorconfig
    │   ├── .env
    │   ├── .env.example
    │   ├── .eslintignore
    │   ├── .eslintrc
    │   ├── favicon.ico
    │   ├── .gitignore
    │   ├── node_modules
    │   ├── package.json
    │   ├── public
    │   ├── README.md
    │   ├── src
    │   ├── .strapi-updater.json
    │   ├── .tmp
    │   └── yarn.lock
    ├── .git
    ├── LICENSE
    └── README.md
```

Move inside the Strapi install folder.
```bash
    /newsapp $ cd newsapp-api
```

### 2. Install Strapi i18n plugin
This Strapi i18n can be installed:
1. From the Terminal
2. From the [Marketplace](https://market.strapi.io/)
    
According to the [installation docs,](https://docs.strapi.io/developer-docs/latest/plugins/plugins-intro.html#installation), the Internationalization plugin is installed by default on all Strapi applications running on version 3.6.0 or higher. For lower versions, a migration is needed (see [Update Strapi version](https://docs.strapi.io/developer-docs/latest/update-migration-guides/update-version.html)), as well as a manual installation of the plugin.

This tutorial uses Strapi version 4.3.2 which ships with the i18n plugin by default.

### 3. Register an Admin User

Start the Strapi server
```bash
    /newsapp/newsapp-api $ npm run develop
    # OR
    /newsapp/newsapp-api $ yarn develop
```

> `npm run develop` starts your Strapi project development server in **watch mode.** In **watch mode** changes in your Strapi project will trigger a server restart.

Strapi will start its server at `http://localhost:1337`, and load its admin panel at http://localhost:1337/admin. 

![Strapi Admin Registration](https://lh3.googleusercontent.com/HYANaaGn0RcvLDbI0Hr21H-R52pHj44x_0r113trp13x8JOkeqDdtXF_RTlx0Dpw9drPXOjs_Rc9EI_y8x--BRFd2UEPARBFSufDQhMo80PaijCGuR-x614nP9eFbre7-UQkUGExNNSK9ajdjg)

>️ **NOTE:**
If you visit http://localhost:1337/admin and see “**Strapi: Warning: an error occurred while requesting the API**”.

Stop the server, `Ctrl` + `C`. Change the host IP address in `./newsapp/newsapp-api/config/server.js` from `0.0.0.0` to `127.0.0.1` . If you are stuck, check out [this gist](https://gist.github.com/Marktawa/ab7e95bc1bad822f40a1662abca672fe).

Fill in your details, and click on the **LET’S START** button. This makes Strapi register your details and shows the admin dashboard.

![Strapi Admin Dashboard](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1660227687118_image.png)

Now, we will begin creating our collections.

### 4. Build the Strapi Collections
We are building a news website, a single news item will have this model:

```
    newspost {
        title
        body
        writtenBy
        imageUrl
    }
```

- The `title` is the title of the news.
- The `body` is the content of the news.
- The `writtenBy` is the author of the news.
- The `imageUrl` is the header image of the news.
    
Strapi will provide us with these endpoints:
- `/newsposts` GET: This endpoint will retrieve all the news content.
- `/newsposts/:id` GET: This endpoint will retrieve a single news post based on the `:id`.
- `/newsposts` POST: This endpoint will create a new news item.
- `/newsposts/:id` DELETE: This endpoint will delete a particular news item.
- `/newsposts/:id` PUT: This endpoint will edit a particular news item based on the `:id`

On the admin page, click on the **Create your first Content type** button. This will take you to the **Content-Type Builder** plugin page. Click on **+ Create a collection type** to display the **Create a collection type** modal. Enter `newspost` for the **Display name** and leave the **API ID (Singular)** and **API ID (Plural)** as is.

![Create a collection type - Basic Settings](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1660341681714_create-a-collection-type-basic-settings.png)

We need to enable localization for this collection, so move to the top-right of the modal and click on the **Advanced Settings** tab. Scroll down and tick the **Enable localization for this Content-Type** checkbox. This will allow you to have content in different locales.

![Create a collection type - Enable localization](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1660343334907_create-a-collection-type-enable-localization.png)

Click **Continue** and a modal appears with field options for your collection type. Select **Text**.

![Select Text field for Newspost collection](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1660344228730_Select-add-text-field-to-collection-type.png)

In the **Add new Text field** modal, enter `title` for **Name,** leave **Type** as `Short text` and click on **+ Add another field**.

![Add new Text field named title](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1660345133484_add-new-text-field-for-title.png)

Add **Text** fields named `writtenBy` and `imageUrl`. The type should be **Short text.** Next, add a **Rich text** field named `body` and click **Finish.** This loads a view of the **Newspost** collection type with the four fields you just created. Click **Save** and when the server restarts **Newspost** will be listed under **COLLECTION TYPES** in the left sidebar.

![Newspost collection type](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1660346560148_newspost-collection-type.png)

When the server restarted, the database for your Strapi app was updated with the newspost collection type. New files in `./newsapp/newsapp-api/src/api` are created to provide the business logic for the `newspost` API.

```
    /newsapp/newsapp-api $ cd src
    /newsapp/newsapp-api/src $ tree
    .
    ├── admin
    │   ├── app.example.js
    │   └── webpack.config.example.js
    ├── api
    │   └── newspost
    │       ├── content-types
    │       │   └── newspost
    │       │       └── schema.json
    │       ├── controllers
    │       │   └── newspost.js
    │       ├── routes
    │       │   └── newspost.js
    │       └── services
    │           └── newspost.js
    ├── extensions
    └── index.js
```

### 5. Adding Languages
With localization enabled, the content will be seeded in the database for each language or locale. English is by default the selected language.

Click **Settings** on the sidebar. Under **GLOBAL SETTINGS**, click on **Internationalization.**

![Settings: Internationalization Plugin Menu](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661343685194_settings-internationalization-plugin-menu.png)

*English (en)* is the default locale/language. Let’s add the French locale. Click on the **+ Add a locale** button on the top-right. A modal shows up. Click on the **Locales** dropdown. All locales supported by Strapi will cascade down. Select *French (France) (fr-FR)*. Click on **✔️ Save**.

![Add French (France) (fr-FR) locale](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661344813709_add-french-locale.png)

See that the *French (France) (fr-FR)* locale is added to your list of locales for your **Newsposts** collection.

![List of locales](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661345182766_list-of-locales.png)

Our app now supports both English and French languages.

### 6. Seeding our database
Now, we will add content to our database. Click on **Content Manager** on the sidebar. Under **COLLECTION TYPES** select **newspost**.

Notice that any **newspost** entry you make will be *English (en)* locale by default. Since you have added the *French (France) (fr-FR)* locale, clicking on the dropdown box will display the *French (France) (fr-FR)* locale listed below the *English (en)* locale.

![Locale options in Content Manager for newspost](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661462758464_locale-options-in-content-manager-in-newspost.png)

Locales added in the Internationalization section will appear in the dropdown. You can select them to add content for that language. So, if you select the *French (France) (fr-FR)*, the content locale will then be for *French (France) (fr-FR)*.

But for now, let’s add news content for the *English (en)* locale. Click on **+ Create new entry** in the top right corner.

Enter the following data for your entry:

```
    title -> Nepal's Minister sworn into office
    imageUrl -> https://thehimalayantimes.com/uploads/imported_images/wp-content/uploads/2020/10/Ministers-Sworn-in.jpg
    writtenBy -> John Doe
    body -> Type anything here
```
Click on **Save**, then **Publish** to publish your article. You can add many more news items as you deem fit.

Let’s add two more:

```
    title -> Angular 12 released.
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Angular_full_color_logo.svg/1280px-Angular_full_color_logo.svg.png
    writtenBy -> M. Hry
    body -> Type anything here
```

Click on **Save** then **Publish**. Add this:
```
    title -> React v17 released
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/1920px-React-icon.svg.png
    writtenBy -> Dan Tierry
    body -> Type anything here
```
Click on **Save**, then **Publish**. We are done adding news posts for the *English (en)* locale. The list of your news posts should be similar to this:

![List of News posts](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661465339738_list-of-news-posts.png)

### 7. Add News Content for the French Locale
Click on the **English (en)** dropdown box and select **French (France) (fr-FR)**.
![Select French locale](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661800378965_select-french-locale.png)

The page will reload:
![News posts for French locale](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661802086873_newsposts-for-french-locale.png)

There is no data for *French (France) (fr-FR)* yet, but we can add them. Here, we will add the French translation of our English version.

For the first data on the French version, we will add this:

```
    title -> Le ministre du Népal a prêté serment
    imageUrl -> https://thehimalayantimes.com/uploads/imported_images/wp-content/uploads/2020/10/Ministers-Sworn-in.jpg
    writtenBy -> John Doe
    body -> Type_any_thing_here.
```
Click on **+ Create new entry** and add the above content. Click on **Save** and then on **Publish**. Now, do this for the second and third posts:

```
    title -> Angular 12 est sorti.
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Angular_full_color_logo.svg/1280px-Angular_full_color_logo.svg.png
    writtenBy -> M. Hry
    body -> Type_any_thing_here.
```
```
    title -> Sortie de React v17
    imageUrl -> https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/1920px-React-icon.svg.png
    writtenBy -> Dan Tierry
    body -> Type_any_thing_here.
```
News posts for the *French (France) fr-FR* locale are ready.

![List of French news posts](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661802286952_list-of-french-news-posts.png)

## 8. Open Access for the Newsposts
Now, only authenticated users can access our endpoints. Let’s make the endpoints to be accessible to the public.

To do that:
- Click on **Settings** on the sidebar.
- In the **USERS & PERMISSIONS PLUGIN** section, click on **Roles**.
- On the **Roles** page, click on **Public.**
![Select Public User Permission Roles](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661802923852_select-public-user-permissions.png)
- Scroll down to the **Permissions** section.
- Select **Newspost** and check the **Select all** checkbox. This makes all the endpoints accessible to the public and performs any [Create, Read, Update, Delete (CRUD)](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) actions on them.
![Open access for News posts](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661803405641_open-access-for-news-posts-raw.png)
- Now, scroll up to the top, and click on **Save**.

## Testing Our Strapi Backend Endpoints
Let's test the endpoints. Open Postman. If you don’t have it already, you can download it from [here](https://www.postman.com/downloads/).

### 1. Get All Newsposts
Create a new collection so we can organize our requests. Add a request to the collection. Set the Request type to **GET** in the input box type http://localhost:1337/api/newsposts and click on **Send.**

![Newsposts API endpoint view in Postman](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661809627601_newspost-api-endpoint-view-in-postman.png)

See that only the `Newsposts`  for the *English (en)* locale are returned because the default locale is set to *English (en)*. To get the French translation, we add the query, `?locale=fr-FR` to the URL and send. The `fr-FR` is the standard language code for the French language.

Type http://localhost:1337/api/newsposts?locale=fr-FR in the input box and click **Send**. The result will give the French locale news posts as seen below.

![French News posts API Endpoint](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661810290913_newspost-api-endpoint-french.png)

Notice that both newsposts in both English and French have unique ids. So if we fetch a News with its `id`, we don’t have to add the `locale=` param. It will fetch the content of the news with no effect on the locale.

### 2. Adding a News Item

To add a news item to a particular locale, we will have to pass the language code of the locale in the body payload. Change the request method to **POST.** Select **Body,** choose **raw** then add the following JSON data for the new news post.

```js
    {
        "data": {
            "title": "Nouvelles fonctionnalités de Vue js et changements de rupture - Présentation de Vue 3",
            "body": "Avec la sortie d'une nouvelle version majeure du noyau, toutes les autres parties du cadre devaient progresser ensemble. Nous devions également fournir une voie de migration pour les utilisateurs de Vue 2. Il s'agissait d'une entreprise massive pour une équipe dirigée par la communauté comme Vue. Lorsque le noyau de Vue 3 était prêt, la plupart des autres parties du framework étaient soit en version bêta, soit en attente de mise à jour. Nous avons décidé d'aller de l'avant et de publier le noyau afin que les premiers utilisateurs, les auteurs de bibliothèques et les frameworks de niveau supérieur puissent commencer à construire avec lui pendant que nous travaillions sur le reste du framework.\n\n Dans le même temps, nous avons conservé Vue 2 comme valeur par défaut pour la documentation et les installations npm. En effet, nous savions que pour de nombreux utilisateurs, Vue 2 offrait toujours une expérience plus cohérente et plus complète jusqu'à ce que d'autres parties de Vue 3 soient affinées.",
            "writtenBy": "CJ",
            "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1920px-Vue.js_Logo_2.svg.png",
            "locale": "fr-FR"
        }
    }
```
Click **Send** and wait for a response from the server. If successful, you should get the following response:

```js
    {
        "data": {
            "id": 8,
            "attributes": {
                "title": "Nouvelles fonctionnalités de Vue js et changements de rupture - Présentation de Vue 3",
                "body": "Avec la sortie d'une nouvelle version majeure du noyau, toutes les autres parties du cadre devaient progresser ensemble. Nous devions également fournir une voie de migration pour les utilisateurs de Vue 2. Il s'agissait d'une entreprise massive pour une équipe dirigée par la communauté comme Vue. Lorsque le noyau de Vue 3 était prêt, la plupart des autres parties du framework étaient soit en version bêta, soit en attente de mise à jour. Nous avons décidé d'aller de l'avant et de publier le noyau afin que les premiers utilisateurs, les auteurs de bibliothèques et les frameworks de niveau supérieur puissent commencer à construire avec lui pendant que nous travaillions sur le reste du framework.\n\n Dans le même temps, nous avons conservé Vue 2 comme valeur par défaut pour la documentation et les installations npm. En effet, nous savions que pour de nombreux utilisateurs, Vue 2 offrait toujours une expérience plus cohérente et plus complète jusqu'à ce que d'autres parties de Vue 3 soient affinées.",
                "writtenBy": "CJ",
                "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1920px-Vue.js_Logo_2.svg.png",
                "createdAt": "2022-08-30T19:39:30.199Z",
                "updatedAt": "2022-08-30T19:39:30.199Z",
                "publishedAt": "2022-08-30T19:39:30.194Z",
                "locale": "fr-FR"
            }
        },
        "meta": {}
    }
```

![POST Request Payload](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661889772634_post-request-payload-in-postman.png)

The final confirmation for the successful news post addition is it is visible in the list of French locale news posts. Navigate to the *French (France) fr-FR* locale list of news posts in the **Content Manager** and you will see it. See we set `fr-FR` to the `locale` prop so Strapi knows this goes into the French locale.

![Newly added news post in French list](https://paper-attachments.dropbox.com/s_1397259AE4EA905EA394D788E024F54EE4192AD1703CEE73F161107BB332DADF_1661890225801_new-newspost-in-strapi-dashboard.png)

We are done with our Strapi backend; let’s build the frontend to see how the two meet.

## Set up React App Frontend
Let's move to the next stage, which is setting up the React app frontend. 

### 1. Scaffold React App
Now, you will build the frontend of your app. 
Change the directory to the root of your project folder `newsapp`:
```bash
    /newsapp/newsapp-api $ cd ..
```
First, make sure you have the `create-react-app` tool installed on your machine. If not, you can install it globally by running this command:
```bash
    /newsapp $ npm i create-react-app -g
```
Test the installation by running the command:
```bash
    /newsapp $ create-react-app --version
```
From here, scaffold the React project. Create a `newsapp-strapi` React project by running the following command:
```bash
    /newsapp $ create-react-app newsapp-strapi
```
Move into the folder:
```bash
    /newsapp $ cd newsapp-strapi
```
Install the `axios` module, which you will use for querying Strapi endpoints:
```bash
    /newsapp/newsapp-strapi $ npm i axios
```
Next, you will need routes in our app. Install the `react-router-dom`:
```bash
    /newsapp/newsapp-strapi $ npm i react-router-dom
```
Start the server:
```bash
    /newsapp/newsapp-strapi $ npm run start
```
Go to your browser and navigate to http://localhost:3000 to see the React frontend for your app.

### 2. Build the Components
**LET'S BEGIN WITH THE SETUP.**
Your news app will have two routes:

- `/news`: This route will render all the news in our app.
- `/newsview/:id`: This route will render a particular news item. The `:id` will be the `id` attribute of the news item.
    

Your final app will look like this:

![News app Home page](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620168466465_image.png)

![Add News post modal](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620168501577_image.png)

This displays a single news post.

![Full view of Single News post](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620168537998_image.png)

Let’s break our app into components.

- **Header**: This will hold the header section of your app.
- **NewsList**: This component will be a page component. It will be rendered when you navigate to the `/news` route. It will display the list of news. It is a smart component.
- **NewsCard**: This component will display an overview of a news item. It will be rendered by the `NewsList` component.
- **NewsView**: This component is a page component, it displays the full details of a news item. It is the news page, where users will read a piece of particular news.
- **AddNewsDialog**: This is a dialog component, it is where news is added to your app.
![Header, NewsList and NewsCard Component](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620169377093_image.png)

![AddNewsDialog Component](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620168758742_image.png)

![NewsView Component](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620169575726_image.png)

The page components will be in the `pages` folder while the presentational components will be in the `components` folder.

Create two folders `pages` and `components` inside the `src` folder.
```bash
    /newsapp/newsapp-strapi $ mkdir src/pages && mkdir src/components
```
Create `Header`, `AddNewsDialog`, and `NewsCard` components inside the `components` folder. Move inside the `components` folder:

```bash
    /newsapp/newsapp-strapi $ cd src/components
```
Create a folder named `AddNewsDialog` and create an `index.js` file inside it.
```bash
    /newsapp/newsapp-strapi/src/components $ mkdir AddNewsDialog && touch AddNewsDialog/index.js
```
Create a folder named `Header`. Add `index.js` and `Header.css` to the `Header` folder.
```bash
    /newsapp/newsapp-strapi/src/components $ mkdir Header && touch Header/index.js && touch Header/Header.css
```
Do the same for the `NewsCard` component.

```bash
    /newsapp/newsapp-strapi/src/components $ mkdir NewsCard && touch NewsCard/index.js && touch NewsCard/NewsCard.css
```
Create the page components. Switch to the `pages` folder.
```bash
    /newsapp/newsapp-strapi/src/components $ cd /newsapp/newsapp-strapi/src/pages
```
Add the NewsList component.
```bash
    /newsapp/newsapp-strapi/src/pages $ mkdir NewsList && touch NewsList/index.js && touch NewsList/NewsList.css
```
Add the NewsView component.
```bash
    /newsapp/newsapp-strapi/src/pages $ mkdir NewsView && touch NewsView/index.js && touch NewsView/NewsView.css
```
Open `App.js` in the `src` folder. Clear the content and paste the below code:

```js
    /* /newsapp/newsapp-strapi/src/App.js */
    
    import "./App.css";
    import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
    import Header from "./components/Header";
    import NewsList from "./pages/NewsList";
    import NewsView from "./pages/NewsView";
    
    function App() {
      return (
        <>
          <Header />
          <div className="container">
            <main className="main">
              <BrowserRouter>
                <Switch>
                  <Route path="/news">
                    <NewsList />
                  </Route>
                  <Route path="/newsview/:id">
                    <NewsView />
                  </Route>
                  <Route exact path="/">
                    <Redirect to="/news" />
                  </Route>
                  <Route path="*">
                    <NewsList />
                  </Route>{" "}
                </Switch>
              </BrowserRouter>
            </main>
          </div>
        </>
      );
    }
    export default App;
```
We set up two routes: `/news` and `newsview/:id`. The first route will render the `NewsList` component while the last route will render the `NewsView` component.

We used `React Router` to set up routing here. The `BrowserRouter` component initializes the routing system, the `Switch` component wraps the dynamic routes, and the `Route` configures the specific routes and wraps the component the route that will render. Anything else outside the `BrowserRouter` component will render on every page. See that we render the `Header` component outside it, so the header appears on all our pages.

Next, clear the code in `App.css` and save the file.

**HEADER COMPONENT**

Add the below code to `index.js`  in the `Header` component folder.

```js
    // /newsapp/newsapp-strapi/src/components/Header/index.js
    
    import "./Header.css";
    
    export default function Header() {
      return (
        <section className="header">
          <div className="headerName">News24</div>
        </section>
      );
    }
```

Just a simple UI that displays the text “News24”. Add some styling to your header, using the `Header.css` file:

```css
    /* /newsapp/newsapp-strapi/src/components/Header/Header.css */
    
    .header {
      height: 54px;
      background-color: black;
      color: white;
      display: flex;
      align-items: center;
      padding: 10px;
      font-family: sans-serif;
      /*width: 100%;*/
      padding-left: 27%;
    }
    .headerName {
      font-size: 1.8em;
    }
```

**NEWSLIST COMPONENT**

Insert the following code in the `index.js` file for the `NewsList` component:
```js
    // /newsapp/newsapp-strapi/src/pages/NewsList/index.js
    
    import "./NewsList.css";
    import NewsCard from "./../../components/NewsCard";
    import { useEffect, useState } from "react";
    import axios from "axios";
    import AddNewsDialog from "../../components/AddNewsDialog";
    
    export default function NewsList() {
        const [newsList, setNewsList] = useState([]);
        const [locale, setLocale] = useState("en");
        const [showModal, setShowModal] = useState(false);
    
        useEffect(() => {
            async function fetchNews() {
                const data = await axios.get(
                    "http://localhost:1337/api/newsposts?_locale=" + locale
                );
                setNewsList([...data?.data]);
            }
            fetchNews();
        }, [locale]);
    
        function setLang() {
            setLocale(window.locales.value);
        }
    
        function showAddNewsDialog() {
            setShowModal(!showModal);
        }
    
        return (
            <div className="newslist">
                <div className="newslistbreadcrumb">
                    <div className="newslisttitle">
                        <h3>World News</h3>
                    </div>
                    <div style={{ display: "flex", alignItems: "center" }}>
                        <div style={{ marginRight: "4px" }}>
                            <button onClick={showAddNewsDialog}>Add News</button>
                        </div>
                        <div>
                            <select name="locales" id="locales" onChange={setLang}>
                                <option value="en">English</option>
                                <option value="fr-FR">French</option>
                            </select>
                        </div>
                    </div>
                </div>
                <div>
                    {newsList?.map((newsItem, i) => (
                        <NewsCard newsItem={newsItem} key={i} />
                    ))}
                </div>
                {showModal ? <AddNewsDialog closeModal={showAddNewsDialog} /> : null}
            </div>
        );
    }
```

Here we set up three states. The first holds the list of news. The second holds the current locale/language. It is by default set to English `en`. The last holds the modal visibility state.

The `useEffect()` function loads the news whenever the component mounts from the `http://localhost:1337/api/newsposts?_locale="` endpoint. The current locale is added to it based on the selected `locale`. This enables of internationalization of the news app. You read news in both English and French. 

The result sets the `newsList` state. This causes the component to render and the UI displays the news in a long list. We added a `locale` dependency to the `useEffect` so the callback runs when the `locale` state changes.

The functions `setLang` and `showAddNewsDialog` sets the selected locale to the `locale` state and toggles the `showModal` state respectively.

**Let’s analyze the UI code.**

See that we added an `Add News` button that when clicked the `AddNewsDialog` modal shows up. We also added a language select element, this is where we can select the language the news will be rendered in. We have "English" and "French", this is so because we have internationalization on our backend for "English" and "French". When any of the options is clicked, the language version of the app is loaded.

Add the styling. Open `NewsList.css` and paste the below code:

```css
    /* /newsapp/newsapp-strapi/src/pages/NewsList/NewsList.css */
    
    .newslistbreadcrumb {
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 1px solid darkgray;
    }
    .newslisttitle {
      margin-left: 12px;
    }
    .newslisttitle h3 {
      color: rgb(107 107 107);
      font-weight: 500;
    }
```

**NEWWSVIEW COMPONENT**
This page component renders the news selected from the `NewsList` component. Insert the following code to the `index.js` file for the `NewsView` page:

```js
    /* /newsapp/newsapp-strapi/src/pages/NewsView/index.js */
    
    import "./NewsView.css";
    import { useParams } from "react-router-dom";
    import axios from "axios";
    import { useEffect, useState } from "react";
    
    export default function NewsView() {
        let { id } = useParams();
        const [news, setNews] = useState();
    
        useEffect(() => {
            async function getNews() {
                const data = await axios.get("http://localhost:1337/api/newsposts/" + id);
                setNews(data?.data);
            }
            getNews();
        }, [id]);
    
        async function deleteNews() {
            if (window.confirm("Do you want to delete this news?")) {
                await axios.delete("http://localhost:1337/api/newsposts/" + id);
                window.history.pushState(null, "", "/news");
                window.location.reload();
            }
        }
        return (
            <div className="newsview">
                <div
                    className="newsviewimg"
                    style={{ backgroundImage: `url(${news?.imageUrl})` }}
                ></div>
                <div>
                    <div className="newsviewtitlesection">
                        <div className="newsviewtitle">
                            <h1>{news?.title}</h1>
                        </div>
                        <div className="newsviewdetails">
                            <span style={{ flex: "1", color: "rgb(99 98 98)" }}>
                                Written By: <span>{news?.writtenBy}</span>
                            </span>
                            <span style={{ flex: "1", color: "rgb(99 98 98)" }}>
                                Date: <span>{news?.created_at}</span>
                            </span>
                            <span>
                                <button className="btn-danger" onClick={deleteNews}>
                                    Delete
                                </button>
                            </span>
                        </div>
                    </div>
                    <div className="newsviewbody">{news?.body}</div>
                </div>
            </div>
        );
    }
```

We start by using the `useParams` hook to get the `:id` param and the value is stored in the `id` variable. We set a state to hold the current news being viewed. The `useEffect` loads the news details from the endpoint `http://localhost:1337/api/newsposts/ + id` and set the result to the `news` state.

The `deleteNews` function deletes this news by performing a **DELETE HTTP** request to the endpoint `http://localhost:1337/api/newsposts/" + id`. Then, navigate back to the `/news` page. This component’s UI displays the news in its entirety.

Add styling. Open `NewsView.css` and paste the below code:
```css
    /* /newsapp/newsapp-strapi/src/pages/NewsView/NewsView.css */
    
    .newsview {
      margin-top: 7px;
    }
    .newsviewimg {
      background-color: darkgray;
      background-repeat: no-repeat;
      background-size: cover;
      background-position: center;
      height: 200px;
    }
    .newsviewdetails {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .newsviewtitlesection {
      margin-bottom: 20px;
    }
    .newsviewtitle h1 {
      margin-bottom: 6px;
    }
    .newsviewbody {
      font-size: large;
    }
    .newsviewbody::first-letter {
      font-weight: 700;
      font-size: 4em;
      line-height: 0.83;
      float: left;
      margin-right: 7px;
      margin-bottom: 4px;
    }
    .newsviewbody {
      clear: left;
      font-size: 21px;
      line-height: 1.58;
      letter-spacing: -0.003em;
    }
```

We used the `::first-select` pseudoselector here `.newsviewbody::first-letter` to make the first letter in the news body appear larger 😁. That's a great UI appeal just like what [Medium.com](https://www.medium.com/) does.

**NEWSCARD COMPONENT**

This component is rendered by the `NewsList` to display a list of news posts in the app. Insert the following code to the `index.js` file for the `NewsCard` component:

```js
    /* /newsapp/newsapp-strapi/src/components/NewsCard/index.js */
    
    import { Link } from "react-router-dom";
    import "./NewsCard.css";
    
    export default function NewsCard({ newsItem }) {
      const { title, body, imageUrl, id } = newsItem;
      const synopsis = body.slice(0, 150);
      return (
        <Link to={"/newsview/" + id}>
          <div className="newscard">
            <div
              className="newscardimg"
              style={{ backgroundImage: `url(${imageUrl})` }}
            ></div>
            <div>
              <div className="newscardtitle">
                <h1>{title}</h1>
              </div>
              <div>
                <span>{synopsis}</span>
              </div>
              <div></div>
            </div>
          </div>
        </Link>
      );
    }
```

The `newsItem` is destructured from the component props, and then the news properties `body`, `imageUrl`, `title`, and `id` are destructured from it. Then, we generated a synopsis of the news from the `body`, this is just a snippet of the whole news. We did this by slicing off `150` characters from the whole news in the `body` and then displaying it.

See that we set a link to the full news by wrapping the `div#newscard` in the `Link` component with a link to `newsview/ + id`. This will load the `NewsView` component with the `id` param set to the param in the `id` variable. 

Add styling to the `NewsCard`. Open the `NewsCard.css` and paste the below code:

```css    
    /* /newsapp/newsapp-strapi/src/components/NewsCard/NewsCard.css */
    
    .newscard {
      /*background-color: white;*/
      padding: 8px;
      /*box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
      transition: 0.3s;*/
      border-radius: 4px;
      margin: 8px;
      cursor: pointer;
      display: flex;
    }
    .newscardimg {
      width: 146px;
      height: 146px;
      background-color: darkgray;
      background-repeat: no-repeat;
      background-size: cover;
      background-position: center;
      margin-right: 9px;
      flex: 1 100%;
    }
    .newscardtitle {
      flex: 1 100%;
    }
    .newscardtitle h1 {
      margin-top: 0;
      margin-bottom: 1px;
    }
```

**AddNewsDialog COMPONENT**

Add the below code to `index.js` for the `AddNewsDialog` component:
```js
    /* /newsapp/newsapp-strapi/src/components/AddNewsDialog/index.js */
    
    import { useState } from "react";
    import axios from "axios";
    
    export default function AddNewsDialog({ closeModal }) {
        const [disable, setDisable] = useState(false);
        async function saveNews() {
            const title = window.newsTitle.value;
            const imageUrl = window.newsImageUrl.value;
            const writtenBy = window.newsWrittenBy.value;
            const body = window.newsBody.value;
            const lang = window.addNewsLocales.value;
    
            setDisable(true);
            await axios.post("http://localhost:1337/api/newsposts", {
                title,
                imageUrl,
                writtenBy,
                body,
                locale: lang,
            });
            window.location.reload();
            setDisable(false);
        }
    
        return (
            <div className="modal">
                <div className="modal-backdrop" onClick={closeModal}></div>
                <div className="modal-content">
                    <div className="modal-header">
                        <h3>Add News</h3>
                        <span
                            style={{ padding: "10px", cursor: "pointer" }}
                            onClick={closeModal}
                        >
                            X
                        </span>
                    </div>
                    <div className="modal-body content">
                        <div style={{ display: "flex", flexWrap: "wrap" }}>
                            <div className="inputField">
                                <div className="label">
                                    <label>Title</label>
                                </div>
                                <div>
                                    <input id="newsTitle" type="text" />
                                </div>
                            </div>
                            <div className="inputField">
                                <div className="label">
                                    <label>Lang</label>
                                </div>
                                <div>
                                    <select name="addNewsLocales" id="addNewsLocales">
                                        <option value="en">English</option>
                                        <option value="fr-FR">French</option>
                                    </select>
                                </div>
                            </div>
                            <div className="inputField">
                                <div className="label">
                                    <label>ImageUrl</label>
                                </div>
                                <div>
                                    <input id="newsImageUrl" type="text" />
                                </div>
                            </div>
                            <div className="inputField">
                                <div className="label">
                                    <label>Written By</label>
                                </div>
                                <div>
                                    <input id="newsWrittenBy" type="text" />
                                </div>
                            </div>
                            <div className="inputField" style={{ flex: "2 1 100%" }}>
                                <div className="label">
                                    <label>Body</label>
                                </div>
                                <div>
                                    <textarea
                                        id="newsBody"
                                        style={{ width: "100%", height: "200px" }}
                                    ></textarea>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div className="modal-footer">
                        <button
                            disabled={disable}
                            className="btn-danger"
                            onClick={closeModal}
                        >
                            Cancel
                        </button>
                        <button disabled={disable} className="btn" onClick={saveNews}>
                            Save
                        </button>
                    </div>
                </div>
            </div>
        );
    }
```

We have a state that holds the disabling state of buttons. The UI displays input boxes and a text area where we enter our news info. Then, there is a `Lang select` element where we can select the language. Here, we hardcoded our two locales “English” and “French”, so we are bound with two languages here. It was done this way just to show you how you can toggle between languages from your frontend. A better solution will be to fetch the locales for this collection and dynamically list them in the select element.

Now, when adding your locales to the `option` element, the locale code is very important. Omitting any part of it will make it fetch the wrong language version or not work at all. For example, the French locale code is `fr-FR`, adding something like `fr` will not make it work. So we should be very careful when hardcoding locale code, or better still fetching the locales is the best option.

The “Save” button calls the `saveNews` function. The `saveNews` function picks the `title`, `imageUrl`, `writtenBy`, `body`, and `lang` values from the input boxes, text area, and select element, and disables the buttons. It then calls the `http://localhost:1337/api/newsposts` endpoint, passing the picked values as payload in the POST body. See that the language type for the news is set in the payload’s `locale` property. Finally, the page is reloaded and the newly added news post will be displayed on the `news` page.

### 3. Add Styling

Add global styles in `index.css`:

```css    
    /* /newsapp/newsapp-strapi/src/index.css */
    
    body {
      margin: 0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
        "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
        "Helvetica Neue", sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      background-color: rgba(234, 238, 243, 1);
    }
    code {
      font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
        monospace;
    }
    button {
      height: 30px;
      padding: 0px 15px 2px;
      font-weight: 400;
      font-size: 1rem;
      line-height: normal;
      border-radius: 2px;
      cursor: pointer;
      outline: 0px;
      background-color: rgb(0, 126, 255);
      border: 1px solid rgb(0, 126, 255);
      color: rgb(255, 255, 255);
      text-align: center;
    }
    .btn-danger {
      background-color: rgb(195 18 18);
      border: 1px solid rgb(195 18 18);
    }
    .container {
      min-height: 100vh;
      /*padding: 0 0.5rem; */
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background-color: rgba(234, 238, 243, 1);
    }
    .main {
      /*padding: 5rem 0;*/
      flex: 1;
      display: flex;
      flex-direction: column;
      width: 46%;
      /*justify-content: center;
      align-items: center;*/
    }
    .modal {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      z-index: 1000;
      font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
    }
    .modal-backdrop {
      opacity: 0.5;
      width: inherit;
      height: inherit;
      background-color: grey;
      position: fixed;
    }
    .modal-body {
      padding: 5px;
      padding-top: 15px;
      padding-bottom: 15px;
    }
    .modal-footer {
      padding: 15px 5px;
      display: flex;
      justify-content: space-between;
    }
    .modal-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .modal-header h3 {
      margin: 0;
    }
    .modal-content {
      background-color: white;
      z-index: 1;
      padding: 10px;
      margin-top: 10px;
      width: 520px;
      box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14),
        0px 9px 46px 8px rgba(0, 0, 0, 0.12);
      border-radius: 4px;
    }
    input[type="text"] {
      width: 100%;
      /*height: 3.4rem;*/
      padding: 9px;
      font-weight: 400;
      /*font-size: 1.3rem;*/
      cursor: text;
      outline: 0px;
      border: 1px solid rgb(227, 233, 243);
      border-radius: 2px;
      color: rgb(51, 55, 64);
      background-color: transparent;
      box-sizing: border-box;
    }
    .label {
      padding: 4px 0;
      font-size: small;
      color: rgb(51, 55, 64);
    }
    .content {
      display: flex;
      flex-wrap: wrap;
      flex-direction: column;
    }
    .inputField {
      margin: 3px 7px;
      flex: 1 40%;
    }
    .disable {
      opacity: 0.5;
      cursor: not-allowed;
    }
    a[href] {
      text-decoration: none;
      color: black;
    }
    a:visited {
      color: black;
    }
```

## Test the App
We begin testing the app by making sure the backend server and the frontend are running.
```bash
    /newsapp/newsapp-strapi $ cd /newsapp/newsapp-api && yarn develop
    /newsapp/newsapp-api $ cd /newsapp/newsapp-strapi && npm run start
```
You will start by adding a news item. In your browser go to http://localhost:3000/news page and click on the **Add News** button. On the modal that shows up type in the data:

```
    title -> Minnesota National Guard deployed after protests over the police killing of a man during a traffic stop
    imageUrl -> https://logos-world.net/wp-content/uploads/2020/11/CNN-Logo-700x394.png
    Body -> (CNN) -- Hundreds of people __TRUNC__t," Walz tweeted.
    writtenBy -> CNN
```

![Add News post in test mode](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620170399520_image.png)

Click on **Save.**

![List of News posts](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620170414692_image.png)

The news post is added. Click on it to confirm if it is readable.

![Newly added News post](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620170441982_image.png)

Add the **French (fr-FR)** version. Go back to http://localhost:3000/news page. Click on **Add News.** This time change the language to **French**.

Add the French version of the news post:

```
    title -> La Garde nationale du Minnesota déployée après des manifestations contre le meurtre par la police d'un homme lors d'un arrêt de la circulation
    imageUrl -> https://logos-world.net/wp-content/uploads/2020/11/CNN-Logo-700x394.png
    Body -> (CNN) Des __TRUNC__es forces de l'ordre", a tweeté Walz.
    writtenBy -> CNN
```
Click on **Save**. Select the **French** language:

![Sample](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620171239080_image.png)

See the news in French, click on it.
![Sample](https://paper-attachments.dropbox.com/s_E57271DB6CDE5AEABA9AFD6951DDA6DD7D6879FD82E4DC6D207B6D9EF42949EB_1620171293847_image.png)

Finally, test deleting the news posts.

Click on the **Delete** button and on the **OK** button on the confirm dialog that shows up. See the news is gone. 

## Conclusion

We learned how to use the Strapi i18n plugin to add internationalization to our Strapi API endpoints, and it is quite simple to use.

We started by learning what Strapi does and how it makes building APIs highly efficient. Next, we introduced the i18n plugin, and from there, we created a Strapi project to demonstrate how to use the i18n plugin in a Strapi project.

Next, we built a news app in React.js and showed how we could support multiple languages in a Reactjs app using Strapi as a backend with the help of the i18n plugin.

[View the source code here.](https://github.com/Marktawa/newsapp)