Published on

Internationalize React + Vite App with I18Next

Authors
  • Name
    Yujia Sun
    Twitter

react-i18next is a powerful internationalization framework for React / React Native. This blog provides a step-by-step guide to internationalizing your React + Vite app with i18next.

From this link get code of this guide: wave-ys/react-vite-i18next-demo

Install needed dependencies

Install both react-i18next and i18next packages:

pnpm i i18next react-i18next i18next-http-backend i18next-browser-languagedetector

Configure i18next

Create a new file i18n.ts under the directory src with the following content:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';

void i18n
  // load translation from `/public/locales` using http
  .use(Backend)
  // auto detect user language
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en-US',

    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    }
  });


export default i18n;

And then import this file in main.tsx to make it bundled:

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'

import './i18n.ts'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

Create translation files

Create a file public/locales/en-US/common.json:

{
  "home": {
    "hello": "Hello!"
  },
  "button": "Change Language"
}

And another file public/locales/zh-CN/common.json with translation:

{
  "home": {
    "hello": "你好!"
  },
  "button": "更改语言"
}

The filename common will be the namespace for this translation file.

Translate your content

The easiest way is to use useTranslation hood:

import {useTranslation} from "react-i18next";

export default function App() {
  const {t} = useTranslation("common");
  return <>{t("home.hello")}</>;
}

Change language

You can use i18n.changeLanguage method to change current language.

import { useTranslation } from "react-i18next";

export default function App() {
  const { t, i18n } = useTranslation("common");
  const changeLanguage = () => {
    void i18n.changeLanguage(i18n.language === "en-US" ? "zh-CN" : "en-US");
  }

  return <>{t("home.hello")} <button onClick={changeLanguage}>{t("button")}</button></>;
}

You can also import i18n if you cannot use react hook to get it:

import i18n from "i18next";
Effect: Effect image

Extensions

Enable translation files hot reload

Vite does not monitor the update in public folder. To enable hot reload for translation files, edit i18n.ts and add these lines:

if (import.meta.hot) {
  import.meta.hot.on("locales-update", () => {
    void i18n.reloadResources().then(() => {
      void i18n.changeLanguage(i18n.language);
    });
  });
}

Then edit vite.config.js to add a plugin:

import {defineConfig, PluginOption} from 'vite'
import react from '@vitejs/plugin-react-swc'

function i18nHotReload(): PluginOption {
  return {
    name: "i18n-hot-reload",
    handleHotUpdate({ file, server }) {
      if (file.includes("locales") && file.endsWith(".json")) {
        server.ws.send({
          type: "custom",
          event: "locales-update",
        });
      }
    },
  };
}

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), i18nHotReload()],
})

Add i18n for Zod

If you are using Zod, you can use zod-i18n-map library to translate Zod error message.

Firstly, add dependency:

pnpm i zod-i18n-map

Then we need to modify i18n.ts:

// ...

import enZod from "zod-i18n-map/locales/en/zod.json";
import zhCnZod from "zod-i18n-map/locales/zh-CN/zod.json";

void i18n
// ...
  .init({
    // ...

    // Add next five lines
    partialBundledLanguages: true,
    resources: {
      'en-US': { zod: enZod },
      'zh-CN': { zod: zhCnZod }
    }
  });

// Add this line
z.setErrorMap(zodI18nMap);

// ...

To test, we add these lines to App.tsx:

try {
  const mySchema = z.string();
  mySchema.parse(123);
} catch (e: any) {
  console.log(e.message);
}
Then after we click the button we can see this output from the browser console: Console