DEV Community

Rense Bakker
Rense Bakker

Posted on

8 2 2 2 2

App translation with React-i18next

In this article I will show you how to translate your React app with React-i18next and give a few examples that you may find useful!

I am using joy ui in this example, but I won't show any configuration for it because it is out of scope. You can see the full code in this code sandbox.

I am also assuming that you already have a react project up and running.

Setup

First of we need to add the react-i18next package and its dependencies:

npm  install react-i18next i18next i18next-http-backend i18next-browser-languagedetector
Enter fullscreen mode Exit fullscreen mode

You do not strictly need i18next-http-backend and i18next-browser-languagedetector, however in my experience, almost every React app with translations will want to use these tools sooner or later.

Create config and add i18next provider

Now we need to create a i18n.ts file where we will configure the i18n instance:

// /src/i18n.ts
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import Backend from 'i18next-http-backend'
import LanguageDetector from 'i18next-browser-languagedetector'

i18n
  .use(Backend) // default import from /public/locales/[lang]/[namespace].json
  .use(LanguageDetector) // Detect browser language
  .use(initReactI18next)
  .init({
    fallbackLng: 'en', // Our default language
    debug: true, // Only use this in dev mode
    interpolation: {
      escapeValue: false // We don't need this for React
    }
  })

export  default i18n
Enter fullscreen mode Exit fullscreen mode

Next we need to provide this config to our entire React app using the I18nextProvider:

// /src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { I18nextProvider } from 'react-i18next'
import i18n from './i18n' // import i18n config

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <I18nextProvider i18n={i18n} defaultNS={'translation'}>
      <App />
    </I18nextProvider>
  </React.StrictMode>
)
Enter fullscreen mode Exit fullscreen mode

Add the translations files

English translation:

// /public/locales/en/translation.json
{
  "hello": "Hello World!"
}
Enter fullscreen mode Exit fullscreen mode

Dutch translation:

// /public/locales/nl/translation.json
{
  "hello": "Hallo wereld!"
}
Enter fullscreen mode Exit fullscreen mode

Typescript

For extra convenience, we can add a react-i18next.d.ts file so the compiler will know which translation keys are available in our app. This way we get autocomplete for translation keys and it will let us know when we are using a translation key that doesn't exist:

// /src/react-i18next.d.ts
import  'react-i18next'
import translation from  'locales/en/translation.json'

declare module 'react-i18next' {
  interface Resources {
    translation: typeof translation
  }
}

declare module 'react-i18next' {
  interface CustomTypeOptions {
    defaultNS: 'translation'
    resources: {
      translation: typeof translation
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Using translations

To translate a part of our app, we just import that useTranslation hook and let it know which translation key to use:

// /src/App.tsx
import { useTranslation } from 'react-i18next'
import { Typography } from '@mui/joy'

function App(){
  const { t } =  useTranslation()

  return <Typography>{t('hello')}</Typography>
}
Enter fullscreen mode Exit fullscreen mode

More complex scenario's

Variables and pluralization

You might be thinking, what if some part of my text is a variable? Do I have to split the text out into chunks before and after the variable and translate them seperately? The answer is: nope, I18next has you covered!

// translation.json
{
  "welcome_one":  "Welcome {{name}}, you have {{count}} message.",
  "welcome_other":  "Welcome {{name}}, you have {{count}} messages.",
}
Enter fullscreen mode Exit fullscreen mode
// /src/App.tsx
import { useTranslation } from 'react-i18next'
import { Typography } from '@mui/joy'

function App(){
  const { t } =  useTranslation()
  const [name, setName] =  useState('')
  const [count, setCount] =  useState(1)

  // ...

  return <Typography>{t('welcome',  { count, name })}</Typography>
}
Enter fullscreen mode Exit fullscreen mode

You can do a lot of other fancy stuff with i18next as well like context aware translations.

Custom React components

Sometimes you have some text with a link for example, or you may even want to use a custom React component inside a piece of translated text. For this you have to use the Trans component.

// translations.json
{
  "custom_link": "Translate some text with a custom <1>link</1>."
}
Enter fullscreen mode Exit fullscreen mode

Note the <1> placeholder that tells i18next where to expect a custom element.

// /src/App.tsx
import { Link } from '@mui/joy'
import { Trans } from 'react-i18next'

function App(){
  return <Trans  i18nKey="custom_link">
    Translate some text with a custom <Link  href="#link">link</Link>.
  </Trans>
}
Enter fullscreen mode Exit fullscreen mode

Adding a language switcher

Building a language switcher with i18next is very simple, all we have to do is make some buttons and call the i18n.changeLanguage method with the language that we want to switch to:

// /src/App.tsx
import { Typography, IconButton } from '@mui/joy'
import { useTranslation, Trans } from 'react-i18next'

function App(){
  const { t, i18n } = useTranslation()

  return <>
    <Typography>{t('hello')}</Typography>
    <IconButton onClick={() => i18n.changeLanguage('en')} size="sm" variant={i18n.language ==='en' ? 'solid' : 'soft'}>EN</IconButton>
    <IconButton onClick={() => i18n.changeLanguage('nl')} size="sm" variant={i18n.language ==='nl' ? 'solid' : 'soft'}>NL</IconButton>
  </>
}
Enter fullscreen mode Exit fullscreen mode

Links

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (4)

Collapse
 
joodi profile image
Joodi

Thank you for this amazing post on i18n! 🙌 I’m currently working on a project where I ran into a challenge that I couldn't quite solve and decided to leave it for now until I find a better approach.

The issue I’m facing is with a dashboard where users can dynamically add menus, and these menus are stored in a database and retrieved via an API for display. The problem arises because, in this library, you build a dictionary and translate words into different languages. However, the dynamic data, like the menu items that users add, doesn’t exist in the dictionary, so it doesn't get translated.

Do you have any suggestions for handling this scenario? 🤔

Collapse
 
brense profile image
Rense Bakker

If skipping translations for dynamically added menus is not possible, you'd have to let your users add translations for the menu items to your database. Once you have the translations in the database, you could build a custom plugin to retrieve the translations from the database.
Something along these lines:

import resourcesToBackend from 'i18next-resources-to-backend'

i18n.use(resourcesToBackend((language, namespace) => {
  /*
   ... Make a request to an endpoint that gets the translations from your database,
  return it as a json object that contains all the translations keys,
  similar to how you'd make a translations.json file...
  */
}))
Enter fullscreen mode Exit fullscreen mode
Collapse
 
brense profile image
Rense Bakker

Alternatively you could use something like locize and let your users add translations there and then use locize as your backend resource: github.com/locize/i18next-locize-b...

Collapse
 
julianv321 profile image
Julian

Hi, I am having issues with getting the autocomplete to work, even when running your sandbox. When I run it in VS Code and start typing a key in the t() function no suggestions are given. Do you have any idea what could cause this?

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more