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:
- 🔹 Adding translations on initialization – Load all translations inside your i18next configuration when initializing the library. (https://www.i18next.com/how-to/add-or-load-translations#add-on-init)
- 🔹 Adding translations dynamically after initialization – Fetch translations when needed and add them at runtime. (https://www.i18next.com/how-to/add-or-load-translations#add-after-init)
- 🔹 Loading translations from the filesystem (server-side apps) – Use local JSON files when running on a backend like Node.js or Deno. (https://github.com/i18next/i18next-fs-backend)
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
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
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:
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.
Optionally, enable the auto-machine translation workflow, and if you like, you can also configure a generative AI service.
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>
// ...
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>
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>
)
}
By visiting your app’s local host (npm run dev), i18next will detect your new keys and send them to locize:
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>
)
})}
// ...
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])
// ...
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:
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
}
}
That's it!
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:
Open the cat and translate everything with the bulk actions and click save:
Now refresh your app’s page, and voilà:
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.
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:
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)