DEV Community

Adriano Raiano
Adriano Raiano

Posted on

How to Easily Add Internationalization (i18n) to Your New Software Project

title image

Starting a new software project? One crucial step that many developers overlook is internationalization (i18n). If you plan to support multiple languages, it's best to integrate i18n from the beginning—otherwise, refactoring your code later can be time-consuming and complex.

In this guide, we’ll walk you through the step-by-step process of setting up internationalization in a new project using i18next (one of the most popular i18n frameworks). By the end, you’ll have a fully functional, scalable, and real-time translation setup for your app.

0. Why should you start internationalization early?

Many developers delay i18n, thinking they’ll add it “later.” However, starting early has key benefits:

  • No need for major refactoring – Your code is ready for localization from day one.
  • Easier testing – You can check how your app behaves in different languages early on.
  • Better user experience – Serve global users with localized content right from launch.
  • Powerful i18n features – Use automatic language detection, missing key handling, and more.

Now, let’s dive into how to set up i18next to build a continuous localization workflow for your project.

1. What is the best way to start with internationalization?

When adding internationalization to your project, the first decision is choosing the right i18n framework. A good i18n solution should be:

  • Flexible – Works across different platforms and frameworks.
  • Feature-rich – Supports pluralization, interpolation, and context-based translations.
  • Scalable – Easy to manage as your app grows.

Why choose i18next?

For this guide, we’ll use i18next, one of the most widely adopted i18n frameworks. It’s a battle-tested solution with:

  • Support for multiple environments – Works in browsers, Node.js, React, React Native, and more.
  • Powerful features – Provides automatic missing key handling, language detection, and async loading.
  • Large ecosystem – Integrates seamlessly with localization tools like locize for continuous translation updates.

📌 For a full list of supported platforms, check out the i18next documentation.

📋 Since i18next is the most popular and flexible solution, we'll use it for our example setup.

2. For which environment are you looking for an i18n solution?

Before setting up i18next, consider where you’re building your app. The setup may vary slightly depending on the platform:

  • Web Apps (React, Vue, Angular, plain JavaScript)
  • Mobile Apps (React Native, iOS, Android)
  • Server-Side (Node.js, Deno, serverless functions… In this article you can find some examples)

👉 To see i18next’s supported frameworks, visit this page.

Interested in server side web apps, like Next.js or Gatsby, etc? Then have a look at this article.

📋 For this guide, we’ll focus on a React application using TypeScript.

3. Do you need a language detector for your environment?

When implementing internationalization, your application needs a way to determine which language to use for each user. This can be done automatically based on various factors, such as browser settings, URL parameters, or stored user preferences.

Using a language detector can help provide a better user experience by ensuring that users see the correct language without having to manually select it.

How to Detect a User's Language?

Different environments require different methods for detecting the user’s preferred language. i18next provides several plugins that handle language detection automatically:

🔹 For Web Browsers: Use i18next-browser-languageDetector
Detects the language from the browser settings, URL query parameters, cookies, or localStorage.
Ideal for React, Vue, Angular, and plain JavaScript web apps.

🔹 For HTTP Servers (Node.js, Express, Fastify, etc.): Use i18next-http-middleware

Detects the language from request headers, cookies, or query parameters.
Ideal for backend applications that serve localized content dynamically.

🔹 For Other Environments:
React Native: Uses @os-team/i18next-react-native-language-detector.
Electron Apps: The browser detector generally works well, but you can also use i18next-electron-language-detector.
Command-line tools or scripts: In some cases, you might need to set the language based on system environment variables, like: i18next-cli-language-detector
for others have a look at: https://www.i18next.com/overview/plugins-and-utils#language-detector

When Is a Language Detector Not Necessary?

While language detection is useful, there are cases where you might not need it:

  • ❌ If your app only supports one language (no internationalization needed).
  • ❌ If the language is always determined by user settings (e.g., a user selects their preferred language in an account settings page).
  • ❌ If you're building a static site where translations are pre-rendered based on different language versions.

📋 Since we are building a React application that runs in the browser, we will use i18next-browser-languageDetector to automatically detect and store the user’s language preferences.

4. How do you want to load your translations?

After determining how to detect the user’s language, the next step is deciding how to provide translations to your app. There are multiple ways to do this, depending on your project’s requirements.

Do you want to bundle translations with your app?

Bundling translations means including all language files inside your application, making them available immediately at runtime. There are different ways to handle this:

This approach works well if your translations don’t change often or if you need an offline-capable application. However, it requires redeploying the app whenever translations are updated.

Do you want to load translations separately via HTTP?

Instead of bundling translations, you can load them dynamically from an external source using an HTTP backend. This allows for real-time translation updates without redeploying your app.

There are two main ways to serve translations over HTTP:

1️⃣ Load translations from your own server: Use i18next-http-backend to serve translation files from your backend or API.

2️⃣ Load translations from a professional CDN & TMS: Use i18next-locize-backend to manage translations in locize, a powerful Translation Management System (TMS) that supports real-time updates.

💡 There are many other backend options available depending on your needs. You can find a full list of i18next backends (also non-HTTP) here.

📋 For this tutorial, we will not bundle translations within the app. Instead, we will load them dynamically using i18next-locize-backend. This enables real-time translation updates, making localization much more efficient.

In the next section, we’ll explore why using locize is beneficial and how it simplifies the translation management process.

5. Do you want to manage your translations with a Translation Management System?

Once your app is set up to load translations dynamically, the next step is deciding how to manage and update those translations efficiently. Instead of manually handling JSON files, a Translation Management System (TMS) can streamline the localization process.

Why use a Translation Management System?

Using a TMS like locize provides several key benefits:

  • No more redeployments – Update translations in real time without modifying your app’s code.
  • Collaborate efficiently – Multiple translators can work on translations without needing access to the codebase.
  • Automated translation workflows – Use machine translation or AI translation to speed up the process and manually refine translations where needed.
  • Version control & rollback – Keep track of translation changes and restore previous versions if needed.
  • Seamless integration with i18next – Since locize was created by the same team as i18next, it offers native support for the framework.

How does locize work?

locize acts as a translation storage & delivery service. Instead of keeping translations inside your app, they are stored in locize and fetched dynamically at runtime, if you want to.

  • 🔹 Missing translations? locize can automatically detect and store them, so you never forget to translate anything.
  • 🔹 Need instant updates? Translations are fetched in real time without requiring a new deployment.
  • 🔹 Multiple environments? Manage staging and production translations separately to avoid breaking changes.

📋 For this tutorial, we will:
1️⃣ Use locize as our translation management system
2️⃣ Enable automatic detection of missing translations (so new keys are instantly added to locize)
3️⃣ Use machine translation to generate initial translations automatically
4️⃣ Fetch translations dynamically using i18next-locize-backend

6. Hands-On: How to prepare the code?

Now that we’ve decided on our internationalization strategy, it’s time to start coding! In this section, we’ll set up a React project, configure i18next with locize, and ensure that translations are loaded dynamically.

Step 0: Create a new React project

For this tutorial, we’ll use Vite with TypeScript, as it provides a fast and modern development experience.

Initialize the Project

Run the following command to create a new React project using Vite:

npm create vite@latest my-i18n-app -- --template react-ts
cd my-i18n-app
npm install
Enter fullscreen mode Exit fullscreen mode

This sets up a React project with TypeScript.

Step 1: Install i18next and required packages

To integrate internationalization, we need to install the following packages:

  • 🔹 i18next – The core internationalization framework.
  • 🔹 react-i18next – React bindings for i18next.
  • 🔹 i18next-browser-languagedetector – Automatically detects the user’s preferred language.
  • 🔹 i18next-locize-backend – Fetches translations from locize in real time.
  • 🔹 locize-lastused – Updates last used timestamps on reference keys in locize. (optional)
  • 🔹 locize – Handles integration with the In-Context editor. (optional)
  • 🔹 i18next-resources-for-ts – Helps to transform resources to be used in a type safe i18next project. (optional)
  • 🔹 locize-cli – Help to download the translations during dev for type safe translations. (optional)

Install these dependencies:

npm install i18next react-i18next i18next-browser-languagedetector i18next-locize-backend locize-lastused locize
npm install --save-dev i18next-resources-for-ts locize-cli
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up i18next Configuration
Now, let’s configure i18next to:

  • ✅ Use locize as the translation backend
  • ✅ Automatically detect the user’s language
  • ✅ Fallback to English if a translation is missing

Create a new file: src/i18n.ts and add the following setup:

import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languageDetector'
import LocizeBackend from 'i18next-locize-backend'
import LastUsed from 'locize-lastused'
import { locizePlugin } from 'locize'

const isDev = import.meta.env.DEV

const locizeOptions = {
  projectId: import.meta.env.VITE_LOCIZE_PROJECTID,
  apiKey: import.meta.env.VITE_LOCIZE_APIKEY // // YOU should not expose your apps API key to production!!!
}

if (isDev) {
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  i18n.use(LastUsed)
}

i18n
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(LocizeBackend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // Bind i18next to React
  .use(initReactI18next)
  // InContext Editor of locize
  .use(locizePlugin)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: isDev, // Enable logging for development
    fallbackLng: 'en', // Default language
    backend: locizeOptions,
    locizeLastUsed: locizeOptions,
    saveMissing: isDev // you should not use saveMissing in production
  })

export default i18n
In src/main.tsx, import the i18n.ts configuration before rendering the app:
Enter fullscreen mode Exit fullscreen mode

import './i18n' // Import i18next configuration

 Step 3: Create a new locize project

First you need to signup at locize and login. Then create a new project in locize and copy your project id and api key to your env files in your project. The api key is only used for the save missing and the last used functionality.

new project

api keys

Optionally, enable the auto-machine translation workflow, and if you like, you can also configure a generative AI service.

Image description

Step 4: Start to internationalize with i18next

Import the useTranslation hook and try to use the resulting t function to write some simple text:

// ...
import { useTranslation, Trans } from 'react-i18next'
// ...
const { t, i18n } = useTranslation()
// ...
<p className="read-the-docs">
    {t('clickLogos', 'Click on the logos to learn more!')}
</p>
// ...
Enter fullscreen mode Exit fullscreen mode

The first argument of the t function is the i18n key, and the optional second argument can be some options or the default value.

And use the Trans component for sections having some nested elements:

<Trans i18nKey="editCode">
   Edit <code>src/App.tsx</code> and save to test HMR
</Trans>
Enter fullscreen mode Exit fullscreen mode

By default i18next uses the translation namespace, but you can decide yourself how you want to structure the namespaces.
On this page you can find more information about this topic.

Now the translations will be loaded asynchronously, so make sure you wrap your app with a Suspense component to prevent this error: Uncaught Error: App suspended while rendering, but no fallback UI was specified.

import { Suspense } from 'react'

function App() {
  // your app's code...
}

// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  )
}
Enter fullscreen mode Exit fullscreen mode

By visiting your app’s local host (npm run dev), i18next will detect your new keys and send them to locize:

react app

missing keys

If the automatic machine translation workflow is enabled, the keys gets automatically translated. But you can always manually translate the content.

Step 5: Language switcher

Currently, the i18next-browser-languageDetector plugin detects the browser language, but there is currently no nice way to manually switch the language.

So create something like this:

// ...
type LngRet = { [lng: string]: { nativeName: string } }
const lngs: LngRet = {
  en: { nativeName: 'English' },
  de: { nativeName: 'Deutsch' },
  it: { nativeName: 'Italiano' }
}
// ...
{Object.keys(lngs).map((lng) => {
  const isSelected = i18n.resolvedLanguage === lng
  return (
    <button
      key={lng}
      type="submit"
      disabled={isSelected}
      onClick={() => {
        i18n.changeLanguage(lng)
      }}
    >
      {lngs[lng].nativeName}
    </button>
  )
})}
// ...
Enter fullscreen mode Exit fullscreen mode

Or even better, fetch the available languages directly from locize:

// ...
const [lngs, setLngs] = useState<LngRet>({ en: { nativeName: 'English' }})
// ...
useEffect(() => {
  i18n.services.backendConnector.backend.getLanguages().then((ret: LngRet) => setLngs(ret))
}, [i18n])
// ...
Enter fullscreen mode Exit fullscreen mode

Clicking one of these language buttons, now calls the changeLanguage function of i18next and the new language code is stored in the localStorage. So when you refresh the page it remembers the last used language:

language switcher

Step 6: Type safe translations

Mastering i18next for type-safe translations empowers TypeScript developers to unlock the full potential of their applications. By ensuring accurate localization, eliminating runtime errors, and leveraging the seamless integration between i18next and TypeScript, developers can create robust, localized applications that cater to diverse language preferences.

At runtime we load the translation directly from the locize CDN. So how do we get type-safe translations during development?

We create some npm scripts to help us:

1️⃣ Download the published translations (in reference language) to a temporary directory, i.e.:
downloadEn: locize download --project-id=8f6dc428-303c-463f-9995-f4957cdcc145 --language=en --ver=latest --clean=true --path=./src/@types/locales

2️⃣ Create the appropriate interface definition file, i.e.:
interface: i18next-resources-for-ts interface -i ./src/@types/locales -o ./src/@types/resources.d.ts

3️⃣ Final script: download, create interface and delete the temporary files, i.e.:
update-interface: npm run downloadEn && npm run interface && rm -rf ./src/@types/locales

We now can just import that interface in our ./src/@types/i18next.d.ts file:

import Resources from './resources'

declare module 'i18next' {
  interface CustomTypeOptions {
    resources: Resources
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it!

typescript check

The translations are separated from our code repository and at the same time we maintain type safety with the help of an interface.

Step 7: Add more languages

Since we’re fetching automatically all available languages directly from locize, we can add new languages without having to adapt the code or without do redeploy our app.

Just navigate to your locize project and add a new language:

add language

Open the cat and translate everything with the bulk actions and click save:

machine translate

Now refresh your app’s page, and voilà:

react app with new language

Step 8: More fancy stuff

Thanks to the locize-last-used plugin, you'll be able to find and filter in locize which keys are used or not used anymore.

unused filter

With the help of the locize plugin, you'll be able to use your app within the locize InContext Editor.
Just add the ?incontext=true query parameter in your app’s url, then a popup will show up, login to locize and then:

in-context

And there’s a lot more you can do with locize…

For a sneak peek have a look at this video:

🧑‍💻 The complete code for this React example can be found here. And feel free to have a look at this video that shows the whole Hans-On section.

🎉🥳 Congratulations 🎊🎁

I hope you’ve learned a few new things.

So if you want to take your i18n topic to the next level, it's worth trying the localization management platform - locize.

By using locize, you directly support the future of i18next, as the founders of locize are also the founders of i18next.

Top comments (0)