Internationalization (i18n)
Learn how to internationalize your native and web apps
Introduction
Internationalization, or i18n for short, is the process of making your app suitable for people around the world. In practice, this usually means translations, so people can use your app in their native language.
AppKickstarter has everything setup using react-i18next, which is based on the i18next framework. It's also completely type-safe, so you'll know exactly which translations to use. We also have i18next-scanner setup for automatic translation extraction.
If you've used i18next before, you'll feel right at home.
File Structure
packages/i18n/
├── locales/
│ ├── en.json # English translations
│ └── fr.json # French translations
├── i18next-scanner.config.js # Scanner configuration
├── preserve-translations.js # Translation preservation utility
├── i18next.d.ts # TypeScript definitions for IDE autocomplete
├── index.ts # Main i18n setup
└── package.jsonDefining Translations
Everything related to i18n is in the packages/i18n/ directory.
Translations are defined in packages/i18n/locales. By default, AppKickstarter ships with English and French support, so we have en.json and fr.json here.
We'll go through an example here:
en.json:
{
"Profile updated successfully": "Profile updated successfully",
}fr.json:
{
"Profile updated successfully": "Profil mis à jour avec succès",
}More documentation for the JSON file schema here: https://www.i18next.com/misc/json-format
Using Translations
In your components, use the t() function from useTranslations() to access your translation keys:
import { useTranslation } from "react-i18next";
export function MyComponent() {
const { t } = useTranslation();
return (
<View>
<Text>{t("Profile updated successfully")}</Text>
</View>
);
}Thanks to the custom i18n.d.ts type definitions file, the t() function is completely type-safe, meaning your IDE will tell you what translations are available. Thus, preventing invalid translations.
This would render the translated value from the JSON into the component.
You can also pass in different namespaces, interpolate values (for example, "{price} dollars"). Please refer to the i18next documentation: https://www.i18next.com/translation-function/essentials, or the Advanced Usage section below.
There are many translation key strategies you could use. Above, we translate the strings directly (e.g. "Profile updated successfully"). You can also choose to categorize them by screen/feature:
{
"home": {
"welcome": "Welcome to the app!",
"description": "This app supports multiple languages."
}
}And then use in your components:
<Text>{t("home.welcome")}</Text>It's mostly a matter of personal preference!
Adding a New Language
To support a new language, all you have to do is create a new locale under packages/i18n/locales, and then import it in packages/i18n/index.ts:
import translationEn from "./locales/en.json";
import translationFr from "./locales/fr.json";
const resources = {
en: {
translation: translationEn,
},
fr: {
translation: translationFr,
},
};
..
supportedLngs: ["en", "fr"],You should also add it to the lngs array in i18next-scanner.config.js for automatic translation extraction of this language. You should be able to run pnpm run locale to generate the new language file. More details below.
Automatic Translation Extraction
By default, i18next requires you to manually go into the en.json/fr.json/etc. and extract the keys there.
For example, if you use a string that you want to translate in a component, you'll have to define that string in en.json, then replace your usage in the component with t("YourString"), and then repeat for each language file.
There is nothing wrong with this, per se. However, it is inefficient and there is room for improvement.
We use i18next-scanner to handle automatic translation extraction.
When you write your component code, just use:
t('key')function callsi18n.t('key')callsi18next.t('key')calls<Trans i18nKey="key">components
For example:
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { t } = useTranslation();
return (
<div>
<h1>{i18n.t('Welcome')}</h1>
<p>{t('Select a theme')}</p>
</div>
);
}Then, you can run:
pnpm run localeThis will scan through your app and web-app projects and look for any instances of the above translation functions, and extract them into en.json:
{
"Welcome": "Welcome",
"Select a theme": "Select a theme"
}You can also run the following command to check if any of your language files (e.g. fr.json) are missing translations from en.json:
pnpm run locale:checkThis will copy any missing translations into your other language files. From there, you can add the translations.
Configuration
The scanner is configured to extract translation keys from the inputs in i18next-scanner.config.js.
VS Code Snippets
Speed up development with these built-in snippets:
tt→<Text>{t("$1")}</Text>ttt→t("$1")ttd→t("$1", { defaultValue: "$2" })
Advanced Usage
With Variables
const userName = "Alice";
const messageCount = 3;
<Text>
{t("Hello {{name}}, you have {{count}} new messages", {
name: userName,
count: messageCount
})}
</Text>Translation files:
{
"Hello {{name}}, you have {{count}} new messages": "Hello {{name}}, you have {{count}} new messages"
}JSX Interpolation
The use case here is to write translation‑aware JSX without having to:
- Concatenate strings and variables manually.
- Pass React elements as interpolation arguments (which would normally become plain text).
- Write complex string‑building logic that mixes translation keys with component markup
This is done by using the <Trans> component:
<Trans i18nKey="greeting">Hello, <strong>{{name}}</strong></Trans><Trans i18nKey="loginPrompt"><a href="/login">Log in</a> first.</Trans>With Default Values
<Text>{t("New feature title", { defaultValue: "Amazing New Feature" })}</Text>Pluralization
<Text>
{t("You have {{count}} item", {
count: items.length,
defaultValue_one: "You have {{count}} item",
defaultValue_other: "You have {{count}} items"
})}
</Text>Changing Language Programmatically
import { i18n } from "@repo/design/lib/locale";
// Switch to French
await i18n.changeLanguage("fr");
// Switch to English
await i18n.changeLanguage("en");
// Get current language
const currentLang = i18n.language;