Implementing Internationalization (i18n)
1. Installing i18n Library
For internationalization in Expo projects, the i18next library is commonly used. It provides a robust framework for handling translations and locale management.
| Package | Description | Version |
|---|---|---|
| i18next | Main i18n library for managing translations and resources | 23.7.16 |
| react-i18next | React integration for i18next, providing hooks and components | 15.0.0 |
Note: Ensure that you are using compatible versions of
i18next and react-i18next to avoid runtime errors.2. Configuring Languages
| Property | Values | Description |
|---|---|---|
| supportedLocales | ['en', 'es', 'fr'] |
List of locales supported by the app |
| defaultLocale | 'en' |
Default locale when no language is detected |
| localeConfig | {} |
Configuration object for locale-specific settings |
Example: Configuring supported locales in app.json
{
"expo": {
"locales": {
"en": "./locales/en.json",
"es": "./locales/es.json",
"fr": "./locales/fr.json"
},
"defaultLocale": "en"
}
}
Note: Ensure that all locale files are properly formatted and located in the specified paths.
3. Creating Translation Files
| File Type | Location | Description |
|---|---|---|
| JSON | locales/en.json |
English translation file with key-value pairs |
| JSON | locales/es.json |
Spanish translation file with key-value pairs |
| JSON | locales/fr.json |
French translation file with key-value pairs |
Example: en.json
{
"welcome": "Welcome to our app",
"greeting": "Hello, {name}!",
"settings": "Settings",
"logout": "Logout"
}
Example: es.json
{
"welcome": "Bienvenido a nuestra aplicación",
"greeting": "Hola, {name}!",
"settings": "Configuración",
"logout": "Cerrar sesión"
}
Note: Translation files should be structured consistently across languages to ensure easy maintenance and scalability.
4. Using Translation Hook
| Property | Description | Usage |
|---|---|---|
| useTranslation | Custom React hook to access translation functions and language context | const { t, i18n } = useTranslation(); |
Example: Basic usage of useTranslation hook
import { useTranslation } from 'expo-i18n';
export default function MyComponent() {
const { t } = useTranslation();
return (
<View>
<Text>{t('welcome_message')}</Text>
</View>
);
}
Example: Accessing i18n instance
import { useTranslation } from 'expo-i18n';
export default function LanguageSwitcher() {
const { t, i18n } = useTranslation();
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
};
return (
<View>
<Text>{t('current_language', { lng: i18n.language })}</Text>
<Button title="Switch to Spanish" onPress={() => changeLanguage('es')} />
</View>
);
}
Note: The
useTranslation hook must be used inside a component that is wrapped in the <I18nProvider> context.
5. Switching Languages
| Method | Description | Notes |
|---|---|---|
| I18n.switchLanguage() | Changes the current language of the app | Must be called with a valid locale code from your supported languages list |
| I18n.getCurrentLanguage() | Returns the currently active language code | Useful for debugging or displaying current language in UI |
Note: Ensure that the language code passed to
I18n.switchLanguage() is one of your supported locales defined in the configuration.
Warning: Switching languages does not automatically reload the app. You may need to manually refresh or navigate to see changes.
6. Formatting Dates
| Property | Description | Example |
|---|---|---|
| formatDate | Formats a date according to the locale | formatDate(new Date(), { locale: 'fr-FR' }) |
| formatTime | Formats a time according to the locale | formatTime(new Date(), { locale: 'en-US' }) |
| formatDateTime | Formats a date and time according to the locale | formatDateTime(new Date(), { locale: 'de-DE' }) |
Example: Formatting Dates in Different Locales
import { formatDate } from 'expo-localization';
const date = new Date('2025-04-15');
console.log(formatDate(date, { locale: 'en-US' })); // "4/15/2025"
console.log(formatDate(date, { locale: 'fr-FR' })); // "15/04/2025"
console.log(formatDate(date, { locale: 'ja-JP' })); // "2025/04/15"
Example: Custom Date Formatting
import { formatDate } from 'expo-localization';
const date = new Date('2025-04-15');
console.log(formatDate(date, {
locale: 'en-US',
year: 'numeric',
month: 'long',
day: 'numeric'
})); // "April 15, 2025"
Note: The
formatDate function uses the device's locale settings by default, but can be overridden with a custom locale option.
7. Formatting Numbers
| Property | Description | Example |
|---|---|---|
| Intl.NumberFormat | Formats numbers according to locale | new Intl.NumberFormat('en-US').format(1234567.89) |
| style | Formatting style: 'decimal', 'currency', 'percent' | { style: 'currency', currency: 'USD' } |
| currency | Currency code (ISO 4217) | { style: 'currency', currency: 'EUR' } |
| minimumFractionDigits | Minimum number of digits after decimal point | { minimumFractionDigits: 2 } |
| maximumFractionDigits | Maximum number of digits after decimal point | { maximumFractionDigits: 2 } |
Example: Formatting Numbers in Different Locales
import { Intl } from 'intl';
const number = 1234567.89;
// US locale
console.log(new Intl.NumberFormat('en-US').format(number));
// Output: 1,234,567.89
// German locale
console.log(new Intl.NumberFormat('de-DE').format(number));
// Output: 1.234.567,89
// Currency formatting
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(number));
// Output: $1,234,567.89
Example: Custom Number Formatting
const formatter = new Intl.NumberFormat('en-US', {
style: 'decimal',
minimumFractionDigits: 2,
maximumFractionDigits: 4
});
console.log(formatter.format(1234.56789));
// Output: 1,234.5679
8. Using Device Locale
| Property | Description | Default |
|---|---|---|
| locale | Current device locale code (e.g., en-US, fr-FR) |
System default |
Example: Detecting Device Locale
import * as Localization from 'expo-localization';
const locale = Localization.locale;
console.log("Device locale:", locale); // e.g. "en-US"
Note: The
expo-localization package provides access to the device's current locale settings, which can be used to determine language and region preferences for internationalization.
9. Handling RTL Languages
| Property | Description | Values |
|---|---|---|
| direction | Defines text direction for layout and content | ltr, rtl |
| locale | Current locale used for formatting and localization | String (e.g., ar-SA, he-IL) |
Example: Setting RTL layout in Expo
import { I18nManager } from 'react-native';
I18nManager.forceRTL(true);
Note: Enabling RTL requires careful handling of layout and component positioning to ensure proper rendering.
RTL Support in Expo
- Uses
I18nManagerto control text direction - Supports right-to-left languages like Arabic and Hebrew
- Requires manual adjustment of layout components for RTL alignment
10. Using Pluralization
| Feature | Description | Usage |
|---|---|---|
| pluralization | Supports plural forms in different languages | {count, plural, one{# item} other{# items}} |
| plural rules | Rules for determining plural forms in various languages | Based on CLDR (Common Locale Data Repository) |
Note: The
count variable is used to determine the correct plural form based on the language's rules.
11. Creating Translation Namespaces
| Namespace | Description | Usage |
|---|---|---|
| common | Shared translations used across the app | { common: { welcome: 'Welcome' } } |
| auth | Authentication-related strings | { auth: { login: 'Login', register: 'Register' } } |
| settings | Settings and preferences strings | { settings: { theme: 'Theme', language: 'Language' } } |
Example: Defining Translation Namespaces
// locales/en.json
{
"common": {
"welcome": "Welcome",
"loading": "Loading..."
},
"auth": {
"login": "Login",
"logout": "Logout"
}
}
Note: Namespaces help organize translations and improve maintainability.
12. Interpolating Values
| Property | Description | Example |
|---|---|---|
| format | Used to format values within translation strings | {name}, you have {count} messages |
Example: Basic Interpolation
<Trans i18nKey="greeting" values={{ name: "John", count: 5 }}>
Hello {{name}}, you have {{count}} messages
</Trans>
Example: Nested Values
<Trans i18nKey="userProfile" values={{
firstName: "Alex",
lastName: "Johnson",
age: 30
}}>
{{firstName}} {{lastName}} is {{age}} years old
</Trans>
Note: Values must be passed as an object with keys matching the placeholders in the translation string.