Localize your React app with i18next and Gridly
Tags: react, i18next, tutorial, localization
Published: 2023-03-16 04:52:34

Are you tired of manually managing translations for your React application? Well, look no further! 

In this tutorial, we'll be using Typescript React with Vite to create a multilingual app and setting up a backend with i18next and [Gridly](https://www.gridly.com/) to achieve continuous localization. 

With `i18next–gridly-backend`, new translation keys will be sent to Gridly as soon as you save them in your code. It’s easy to translate your strings once they’re inside Gridly.

We'll show you how to create a new React project, set up your Gridly Grid, get the Grid API key and view ID, install the required dependencies, and get a read-only API key for production environments. 

Let's get started!

## Create your project
In this step, you can use a current React project or create a new one. Here, we’re using Typescript React with [Vite](https://vitejs.dev). Run the following commands to generate a new project:

```
npm create vite@latest my-react-app -- --template react-ts
cd my-react-app
npm install
```

Run `npm run dev` to start the app. Navigate to http://localhost:5173 in your browser:

![Default vite react homepage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l4nsgt5le7mq7egeboji.png)

We are going to localize this app in English (en), Swedish (se), German (de) and French (fr).

## Setup Gridly Grid

Create a [Gridly](https://app.gridly.com/) account
Create your company, project, and database.

To add a new Grid, click `+ Add Grid`.
  1. Choose source language `en`.
  2. Choose target languages `sv`, `de`, and `fr`.
  3. Other columns: remove all.

![Create grid from scratch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50ba4ttkfvvbtj7rc4su.png)

Click `Create` to create and navigate to your new Grid.
![Grid after sucessfully created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/834g5wbehxhems6kcpgq.png)

Change the column ID of each column to match the language code.
![Change column ID](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/042ka9uzy3ki6ub7hu2e.png)

Delete all placeholder records. Make the Record ID column viewable so it’s easy to see what’s happening.
![Show Record ID column](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phqeg7v6u42f3nr5h9lt.png)

## Get Grid API key and view ID

Click the `API` symbol on the right panel.
![API key and view ID on right panel](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kyo6j45u8azw68qsoh7m.png)

In your React source code, create a _.env.local_ file. This file is for local usage only and should not be public in your Git repo.

Add _.env.local_ in root:
```
VITE_GRIDLY_API_KEY=your_api_key
VITE_GRIDLY_VIEW_ID=your_view_id
```
**Warning**: Never expose the Grid API key in your production environment because someone can use it to modify your data. Use the read-only API key from the company settings. At the end of the tutorial, we’ll show you how to find this read-only API key.

## Install dependencies
Let's install some i18next dependencies:

[i18next](https://www.i18next.com)
[react-i18next](https://react.i18next.com)
[i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languageDetector)
[i18next-gridly-backend](https://github.com/truongtrongtin/i18next-gridly-backend)

```
npm install i18next react-i18next i18next-browser-languagedetector i18next-gridly-backend
```

Add the _src/i18n.ts_ file:
```ts
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import GridlyBackend, { GridlyBackendOptions } from 'i18next-gridly-backend';
import { initReactI18next } from 'react-i18next';

const isProduction = import.meta.env.PROD;
const gridlyOptions: GridlyBackendOptions = {
  apiKey: import.meta.env.VITE_GRIDLY_API_KEY,
  viewId: import.meta.env.VITE_GRIDLY_VIEW_ID,
};

i18n
  .use(LanguageDetector)
  .use(GridlyBackend)
  .use(initReactI18next)
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    backend: gridlyOptions,
    saveMissing: !isProduction,
  });

export default i18n;
```
Import in _src/main.tsx_ file:
```diff
  import React from 'react';
  import ReactDOM from 'react-dom/client';
  import App from './App';
  import './index.css';
+ import './i18n';

  ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
  );
```

## First translation

The original _src/App.tsx_:
```tsx
import { useState } from 'react';
import './App.css';
import reactLogo from './assets/react.svg';

function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" className="logo" alt="Vite logo" />
        </a>
        <a href="https://reactjs.org" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </div>
  );
}

export default App;
```

Next, we will localize all text, add a language toggle, and update _src/App.tsx_.
```diff
  import { useState } from 'react';
+ import { Trans, useTranslation } from 'react-i18next';
  import './App.css';
  import reactLogo from './assets/react.svg';

+ const languages = [
+   { value: 'en', name: 'English' },
+   { value: 'sv', name: 'Swedish' },
+   { value: 'de', name: 'German' },
+   { value: 'fr', name: 'French' },
+ ];
+
  function App() {
    const [count, setCount] = useState(0);
    const { t, i18n } = useTranslation();

    return (
      <div className="App">
        <div>
          <a href="https://vitejs.dev" target="_blank">
            <img src="/vite.svg" className="logo" alt="Vite logo" />
          </a>
          <a href="https://reactjs.org" target="_blank">
            <img src={reactLogo} className="logo react" alt="React logo" />
          </a>
        </div>
        <h1>Vite + React</h1>
        <div className="card">
          <button onClick={() => setCount((count) => count + 1)}>
-           count is {count}
+           {t('count_is', 'count is {{number}}', { number: count })}
          </button>
          <p>
-           Edit <code>src/App.tsx</code> and save to test HMR
+           <Trans i18nKey="description.part1">
+             Edit <code>src/App.tsx</code> and save to test HMR
+           </Trans>
          </p>
        </div>
        <p className="read-the-docs">
-         Click on the Vite and React logos to learn more
+         {t(
+           'description.part2',
+           'Click on the Vite and React logos to learn more',
+         )}
        </p>
+       <div>
+         {languages.map((lng) => (
+           <button
+             key={lng.value}
+             style={{
+               fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal',
+             }}
+             onClick={() => i18n.changeLanguage(lng.value)}
+           >
+             {lng.name}
+           </button>
+         ))}
+       </div>
      </div>
    );
  }

  export default App;
```
Save the file and watch as all the new translation keys are sent to Gridly. This works thanks to the i18next option _saveMissing: true_ and the _i18next-gridly-backend_ plugin.
![Add new translation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q3mhp1xu25b4jy6nsn10.png)

This allows the translator to translate content as soon as the developer saves a new key into the code. This process is called **continuous localization**!

Next, add the translations for all languages.
![Translate the content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxh1u05c68c0pcbs8c0b.png)

Try switching to another language. The translated content for the selected language will be downloaded from Gridly.
![Switch language](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35uuufubegidg3njiblo.gif)

## Read-only API key for production env
To get a read-only API key, go to `Company settings` on the dashboard, then select `API keys`.

![Create read-only api key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/doo5uhv1h7192azl6h08.png)

Create a new API key with read-only permission. Use this key in your production environment.

![Copy api key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihrha5ewiaohfc6n92yd.png)

The complete code can be found [here](https://github.com/truongtrongtin/i18next-gridly-backend/tree/main/examples/react-ts).
Watch a live demo [here](https://lambent-dango-1832c7.netlify.app).