Want your React Native app to connect with users worldwide? Localization should be effortless, not exhausting. Humanicer’s API automates translations whilst maintaining your app’s perfect structure—no messy files or manual updates.
The Power of Localization
Expanding globally means speaking your users’ language—literally. Skip the headache of managing translations manually. Humanicer does the heavy lifting, delivering accurate translations whilst keeping your project files clean and organized.
Localising in React Native
React Native doesn’t have built-in localisation support like iOS. You’ll need to use popular libraries like i18next
or react-native-localize
.
Setting Up i18next in React Native
First, install the necessary packages:
npm install i18next react-i18next react-native-localize
Create a basic localisation setup:
// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from 'react-native-localize';
// Language resources
const resources = {
en: {
translation: {
HELLO_WORLD: "Hello, World!",
WELCOME_MESSAGE: "Welcome to our app!"
}
}
// Other languages will be added dynamically
};
// Get device language
const locales = RNLocalize.getLocales();
const languageDetector = {
type: 'languageDetector',
detect: () => {
// Use the device's language as fallback if available
return locales[0]?.languageCode || 'en';
},
init: () => {},
cacheUserLanguage: () => {}
};
// Initialize i18next
i18n
.use(languageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
interpolation: {
escapeValue: false // React already escapes values
}
});
export default i18n;
Using Translations in Components
// App.js
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
import './i18n'; // Import the i18n configuration
const App = () => {
const { t } = useTranslation();
return (
<View style={styles.container}>
<Text style={styles.text}>{t('HELLO_WORLD')}</Text>
<Text style={styles.text}>{t('WELCOME_MESSAGE')}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
text: {
fontSize: 18,
margin: 10,
},
});
export default App;
Integrating Humanicer’s API
Now let’s create a script to translate our translation files using Humanicer’s API:
// translate.js
const fs = require('fs');
const path = require('path');
const axios = require('axios');
class TranslationManager {
constructor(apiKey) {
/**
* Initialize the TranslationManager with API key
* @param {string} apiKey - Humanicer API key
*/
this.apiKey = apiKey;
this.apiUrl = 'https://humanicer.com/v1/localize';
}
async translateFile(sourcePath, targetLang) {
/**
* Translate a JSON translation file to target language
* @param {string} sourcePath - Path to source JSON file
* @param {string} targetLang - Target language code (e.g., 'fr')
* @returns {Object} - Translated JSON object
*/
try {
// Read source file
const sourceContent = fs.readFileSync(sourcePath, 'utf8');
const sourceJson = JSON.parse(sourceContent);
// Prepare text for translation (JSON stringified)
const textToTranslate = JSON.stringify(sourceJson, null, 2);
// Call Humanicer API
const response = await axios.post(
this.apiUrl,
{
text: textToTranslate,
target_platform: 'mobile',
target_language: targetLang
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey
}
}
);
// Parse the response
const translatedText = response.data.result.localized_text;
// Return parsed JSON
return JSON.parse(translatedText);
} catch (error) {
console.error(`Translation error: ${error.message}`);
throw error;
}
}
async batchTranslate(sourcePath, targetLanguages, outputDir) {
/**
* Translate to multiple languages
* @param {string} sourcePath - Path to source JSON file
* @param {Array<string>} targetLanguages - List of language codes
* @param {string} outputDir - Directory to save translated files
*/
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Source file name
const sourceFileName = path.basename(sourcePath);
for (const lang of targetLanguages) {
console.log(`Translating to ${lang}...`);
try {
// Translate content
const translatedJson = await this.translateFile(sourcePath, lang);
// Write to file
const outputPath = path.join(outputDir, `${lang}.json`);
fs.writeFileSync(outputPath, JSON.stringify(translatedJson, null, 2));
console.log(`Translation to ${lang} complete. Saved to ${outputPath}`);
} catch (error) {
console.error(`Failed to translate to ${lang}: ${error.message}`);
}
}
console.log('Batch translation complete.');
}
}
// Now use it
const translateResources = async () => {
const manager = new TranslationManager('your_api_key');
// Translate English resources to French and Spanish
await manager.batchTranslate(
path.join(__dirname, 'translations', 'en.json'),
['fr', 'es', 'de', 'ja'],
path.join(__dirname, 'translations')
);
};
// Run if called directly
if (require.main === module) {
translateResources().catch(console.error);
}
Updating i18n Configuration to Load All Translations
// i18n.js - Updated to dynamically load translations
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from 'react-native-localize';
// Import all translation files
const translationFiles = {
en: require('./translations/en.json'),
fr: require('./translations/fr.json'),
es: require('./translations/es.json'),
de: require('./translations/de.json'),
ja: require('./translations/ja.json'),
// Add more languages as needed
};
// Prepare resources object for i18next
const resources = Object.keys(translationFiles).reduce((acc, lang) => {
acc[lang] = {
translation: translationFiles[lang]
};
return acc;
}, {});
// Get device language
const locales = RNLocalize.getLocales();
const deviceLanguage = locales[0]?.languageCode || 'en';
// Check if we support the device language
const languageDetector = {
type: 'languageDetector',
detect: () => {
// Use device language if available in our translations, otherwise use English
return resources[deviceLanguage] ? deviceLanguage : 'en';
},
init: () => {},
cacheUserLanguage: () => {}
};
// Initialize i18next
i18n
.use(languageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
interpolation: {
escapeValue: false
},
react: {
useSuspense: false
}
});
export default i18n;
Language Switcher Component
// LanguageSwitcher.js
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
// Languages available in the app
const languages = [
{ code: 'en', name: 'English' },
{ code: 'fr', name: 'Français' },
{ code: 'es', name: 'Español' },
{ code: 'de', name: 'Deutsch' },
{ code: 'ja', name: '日本語' }
];
/**
* Changes the app language
* @param {string} langCode - Language code to switch to
*/
const changeLanguage = (langCode) => {
i18n.changeLanguage(langCode);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Select Language:</Text>
<View style={styles.buttonsContainer}>
{languages.map((lang) => (
<TouchableOpacity
key={lang.code}
style={[
styles.button,
i18n.language === lang.code && styles.activeButton
]}
onPress={() => changeLanguage(lang.code)}
>
<Text
style={[
styles.buttonText,
i18n.language === lang.code && styles.activeButtonText
]}
>
{lang.name}
</Text>
</TouchableOpacity>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
backgroundColor: '#f8f8f8',
borderRadius: 8,
margin: 10,
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 10,
},
buttonsContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
},
button: {
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: '#fff',
borderRadius: 4,
borderWidth: 1,
borderColor: '#ddd',
margin: 4,
},
activeButton: {
backgroundColor: '#007bff',
borderColor: '#007bff',
},
buttonText: {
color: '#333',
},
activeButtonText: {
color: '#fff',
},
});
export default LanguageSwitcher;
Updated App Component with Language Switcher
// App.js
import React, { useEffect } from 'react';
import { Text, View, StyleSheet, SafeAreaView } from 'react-native';
import { useTranslation } from 'react-i18next';
import './i18n'; // Import the i18n configuration
import LanguageSwitcher from './LanguageSwitcher';
import AsyncStorage from '@react-native-async-storage/async-storage';
const App = () => {
const { t, i18n } = useTranslation();
// Load saved language preference on startup
useEffect(() => {
const loadSavedLanguage = async () => {
try {
const savedLang = await AsyncStorage.getItem('userLanguage');
if (savedLang && i18n.language !== savedLang) {
i18n.changeLanguage(savedLang);
}
} catch (error) {
console.error('Failed to load language preference:', error);
}
};
loadSavedLanguage();
}, []);
// Save language preference when it changes
useEffect(() => {
const saveLanguagePreference = async () => {
try {
await AsyncStorage.setItem('userLanguage', i18n.language);
} catch (error) {
console.error('Failed to save language preference:', error);
}
};
saveLanguagePreference();
}, [i18n.language]);
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<Text style={styles.header}>{t('APP_NAME', 'My App')}</Text>
<View style={styles.contentContainer}>
<Text style={styles.greeting}>{t('HELLO_WORLD')}</Text>
<Text style={styles.welcome}>{t('WELCOME_MESSAGE')}</Text>
</View>
<LanguageSwitcher />
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#fff',
},
container: {
flex: 1,
padding: 16,
},
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 20,
},
contentContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
greeting: {
fontSize: 28,
marginBottom: 16,
},
welcome: {
fontSize: 18,
textAlign: 'center',
color: '#555',
}
});
export default App;
Integrating Humanicer API with React Native
Let’s create a utility class that can be used directly within a React Native app to fetch translations on demand:
// HumanicerService.js
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
import i18n from './i18n';
class HumanicerService {
/**
* Create a new HumanicerService instance
* @param {string} apiKey - Your Humanicer API key
*/
constructor(apiKey) {
this.apiKey = apiKey;
this.apiUrl = 'https://humanicer.com/v1/localize';
this.translationCache = {};
// Load cached translations from storage
this.loadCachedTranslations();
}
/**
* Load previously cached translations from AsyncStorage
*/
async loadCachedTranslations() {
try {
const cachedData = await AsyncStorage.getItem('humanicer_translations');
if (cachedData) {
this.translationCache = JSON.parse(cachedData);
}
} catch (error) {
console.error('Failed to load cached translations:', error);
}
}
/**
* Save translations to AsyncStorage cache
*/
async saveTranslationsToCache() {
try {
await AsyncStorage.setItem(
'humanicer_translations',
JSON.stringify(this.translationCache)
);
} catch (error) {
console.error('Failed to save translations to cache:', error);
}
}
/**
* Translate a single text string
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code
* @returns {Promise<string>} - Translated text
*/
async translateText(text, targetLang) {
// Check cache first
const cacheKey = `${text}_${targetLang}`;
if (this.translationCache[cacheKey]) {
return this.translationCache[cacheKey];
}
try {
const response = await axios.post(
this.apiUrl,
{
text,
target_platform: 'mobile',
target_language: targetLang
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey
}
}
);
const translatedText = response.data.result.localized_text;
// Add to cache
this.translationCache[cacheKey] = translatedText;
this.saveTranslationsToCache();
return translatedText;
} catch (error) {
console.error('Translation error:', error);
throw error;
}
}
/**
* Translate an object containing translations
* @param {Object} translationObj - Object with translation keys/values
* @param {string} targetLang - Target language code
* @returns {Promise<Object>} - Translated object
*/
async translateObject(translationObj, targetLang) {
try {
// Convert object to JSON string for translation
const textToTranslate = JSON.stringify(translationObj);
const translatedText = await this.translateText(textToTranslate, targetLang);
// Parse the translated JSON back to object
return JSON.parse(translatedText);
} catch (error) {
console.error('Failed to translate object:', error);
throw error;
}
}
/**
* Dynamically load translations for a new language
* @param {string} langCode - Language code to load
* @returns {Promise<boolean>} - Success status
*/
async loadLanguage(langCode) {
try {
// Check if we already have this language loaded
if (i18n.hasResourceBundle(langCode, 'translation')) {
return true;
}
// Get the English translations as source
const sourceTranslations = i18n.getResourceBundle('en', 'translation');
// Translate to target language
const translatedBundle = await this.translateObject(sourceTranslations, langCode);
// Add translations to i18next
i18n.addResourceBundle(langCode, 'translation', translatedBundle);
return true;
} catch (error) {
console.error(`Failed to load language ${langCode}:`, error);
return false;
}
}
}
// Export as singleton
export default new HumanicerService('your_api_key');
Automated Build Step to Generate Translations
To integrate translations into your build process, create a script that runs during your build:
// generate-translations.js
const fs = require('fs');
const path = require('path');
const axios = require('axios');
class TranslationBuilder {
/**
* Create a new translation builder
* @param {string} apiKey - Humanicer API key
*/
constructor(apiKey) {
this.apiKey = apiKey;
this.apiUrl = 'https://humanicer.com/v1/localize';
this.baseDir = path.resolve(__dirname);
this.sourceLanguage = 'en';
this.sourceFile = path.join(this.baseDir, 'src/translations/en.json');
this.outputDir = path.join(this.baseDir, 'src/translations');
}
/**
* Read the source translation file
* @returns {Object} - Source translations
*/
readSourceFile() {
try {
const fileContent = fs.readFileSync(this.sourceFile, 'utf8');
return JSON.parse(fileContent);
} catch (error) {
console.error(`Error reading source file: ${error.message}`);
throw error;
}
}
/**
* Translate content to target language
* @param {string} content - Content to translate
* @param {string} targetLang - Target language code
* @returns {Promise<string>} - Translated content
*/
async translate(content, targetLang) {
try {
const response = await axios.post(
this.apiUrl,
{
text: content,
target_platform: 'mobile',
target_language: targetLang
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey
}
}
);
return response.data.result.localized_text;
} catch (error) {
console.error(`Translation error: ${error.message}`);
if (error.response) {
console.error(`API response: ${JSON.stringify(error.response.data)}`);
}
throw error;
}
}
/**
* Generate translations for all target languages
* @param {Array<string>} targetLanguages - List of language codes to generate
*/
async generateAll(targetLanguages) {
console.log('Starting translation generation...');
// Ensure output directory exists
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
// Read source translations
const sourceTranslations = this.readSourceFile();
const sourceContent = JSON.stringify(sourceTranslations, null, 2);
// Translate to each target language
for (const lang of targetLanguages) {
console.log(`Translating to ${lang}...`);
try {
// Skip source language
if (lang === this.sourceLanguage) {
console.log(`Skipping source language ${lang}`);
continue;
}
// Check if file already exists and needs update
const outputFile = path.join(this.outputDir, `${lang}.json`);
const shouldUpdate = this.shouldUpdateFile(outputFile);
if (!shouldUpdate) {
console.log(`Skipping ${lang} - already up to date`);
continue;
}
// Translate content
const translatedContent = await this.translate(sourceContent, lang);
// Write to file
fs.writeFileSync(outputFile, translatedContent);
console.log(`Successfully generated ${lang} translations`);
} catch (error) {
console.error(`Failed to generate ${lang} translations: ${error.message}`);
}
}
console.log('Translation generation complete!');
}
/**
* Check if a translation file needs update
* @param {string} filePath - Path to check
* @returns {boolean} - True if update needed
*/
shouldUpdateFile(filePath) {
// If file doesn't exist, it needs to be created
if (!fs.existsSync(filePath)) {
return true;
}
// Get modification times
const sourceStats = fs.statSync(this.sourceFile);
const targetStats = fs.statSync(filePath);
// If source is newer than target, update needed
return sourceStats.mtime > targetStats.mtime;
}
}
// Run the translation generation
const run = async () => {
// Get API key from environment or config
const apiKey = process.env.HUMANICER_API_KEY || 'your_api_key';
// Target languages to generate
const targetLanguages = ['fr', 'es', 'de', 'ja', 'zh', 'ru', 'pt', 'it'];
const builder = new TranslationBuilder(apiKey);
await builder.generateAll(targetLanguages);
};
// Run if called directly
if (require.main === module) {
run().catch(error => {
console.error('Translation generation failed:', error);
process.exit(1);
});
}
Adding to Your Build Process
{
"name": "multilingual-react-native-app",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"generate-translations": "node scripts/generate-translations.js",
"prebuild": "npm run generate-translations",
"build": "expo build",
"eject": "expo eject"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.15.0",
"axios": "^0.24.0",
"expo": "~44.0.0",
"expo-status-bar": "~1.2.0",
"i18next": "^21.6.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-i18next": "^11.15.0",
"react-native": "0.64.3",
"react-native-localize": "^2.1.5",
"react-native-web": "0.17.1"
},
"devDependencies": {
"@babel/core": "^7.12.9"
},
"private": true
}
Best Practices for React Native Localisation
-
Separate translation files: Keep all translations in separate JSON files organised by language code
-
Use variables for dynamic content:
// Instead of concatenating strings
t('GREETING') + userName // BAD
// Use variables
t('GREETING_WITH_NAME', { name: userName }) // GOOD
- Handle plurals properly:
// In your JSON file
{
"ITEMS_COUNT": "8 item",
"ITEMS_COUNT_plural": "8 items"
}
// In your code
t('ITEMS_COUNT', { count: itemCount })
- Format dates and numbers according to locale:
import { format } from 'date-fns';
import { enGB, fr } from 'date-fns/locale';
// Map language codes to date-fns locales
const locales = {
en: enGB,
fr: fr
};
// In component
const dateStr = format(
new Date(),
'PPP', // Format pattern
{ locale: locales[i18n.language] || enGB }
);
-
Test with pseudo-localisation: Create a test translation file with extended characters or longer text to identify UI issues.
-
Cache translations to reduce API calls and work offline
-
Add RTL (Right-to-Left) support for languages like Arabic and Hebrew:
import { I18nManager } from 'react-native';
// In your app setup
const isRTL = I18nManager.isRTL;
// In your styles
const styles = StyleSheet.create({
container: {
flexDirection: isRTL ? 'row-reverse' : 'row'
}
});
Complete Project Structure
my-multilingual-app/
├── src/
│ ├── translations/
│ │ ├── en.json
│ │ ├── fr.json
│ │ ├── es.json
│ │ ├── de.json
│ │ └── ja.json
│ ├── components/
│ │ ├── LanguageSwitcher.js
│ │ └── [other components]
│ ├── services/
│ │ └── HumanicerService.js
│ ├── i18n.js
│ └── App.js
├── scripts/
│ └── generate-translations.js
├── package.json
└── babel.config.js
Example Translation File (en.json)
{
"APP_NAME": "My Multilingual App",
"HELLO_WORLD": "Hello, World!",
"WELCOME_MESSAGE": "Welcome to our app!",
"ONBOARDING": {
"SCREEN_1_TITLE": "Welcome",
"SCREEN_1_TEXT": "Get started with our amazing features",
"SCREEN_2_TITLE": "Discover",
"SCREEN_2_TEXT": "Find what you need with our intuitive interface",
"SCREEN_3_TITLE": "Connect",
"SCREEN_3_TEXT": "Stay connected with friends and family",
"NEXT_BUTTON": "Next",
"SKIP_BUTTON": "Skip",
"GET_STARTED": "Get Started"
},
"AUTH": {
"LOGIN": "Log In",
"SIGNUP": "Sign Up",
"EMAIL": "Email",
"PASSWORD": "Password",
"FORGOT_PASSWORD": "Forgot Password?",
"OR_CONTINUE_WITH": "Or continue with",
"GOOGLE": "Google",
"APPLE": "Apple",
"FACEBOOK": "Facebook"
},
"SETTINGS": {
"TITLE": "Settings",
"ACCOUNT": "Account",
"NOTIFICATIONS": "Notifications",
"LANGUAGE": "Language",
"THEME": "Theme",
"HELP": "Help & Support",
"LOGOUT": "Log Out"
},
"ERRORS": {
"GENERIC": "Something went wrong. Please try again.",
"NETWORK": "Network error. Please check your connection.",
"NOT_FOUND": "The requested resource was not found."
},
"COMMON": {
"SAVE": "Save",
"CANCEL": "Cancel",
"DONE": "Done",
"EDIT": "Edit",
"DELETE": "Delete",
"LOADING": "Loading...",
"SEARCH": "Search",
"NO_RESULTS": "No results found",
"RETRY": "Retry",
"CONFIRM": "Confirm",
"YES": "Yes",
"NO": "No",
"BACK": "Back",
"NEXT": "Next",
"ITEMS_COUNT": "8 item",
"ITEMS_COUNT_plural": "8 items"
},
"DATES": {
"TODAY": "Today",
"YESTERDAY": "Yesterday",
"TOMORROW": "Tomorrow",
"DAYS_AGO": "8 day ago",
"DAYS_AGO_plural": "8 days ago"
}
}
CI/CD Integration with GitHub Actions
Add translation generation to your CI/CD pipeline:
name: Generate Translations
on:
push:
branches: [main]
paths:
- 'src/translations/en.json'
workflow_dispatch:
jobs:
generate-translations:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Generate translations
run: npm run generate-translations
env:
HUMANICER_API_KEY: $
- name: Commit and push if changed
run: |
git config --local user.email "github-actions@github.com"
git config --local user.name "GitHub Actions"
git add src/translations/*.json
git diff --staged --quiet || (git commit -m "feat: update translations" && git push)
Step-by-Step Implementation Guide
- Set up the basic React Native project:
npx create-expo-app my-multilingual-app
cd my-multilingual-app
- Install the required packages:
npm install i18next react-i18next react-native-localize @react-native-async-storage/async-storage axios
- Create the project structure:
mkdir -p src/translations src/components src/services scripts
- Create the translation files:
- Create
src/translations/en.json
with your base translations - Set up Humanicer API key in your environment
- Create
- Set up i18n configuration:
- Implement
src/i18n.js
using the code from our example
- Implement
- Create the translation generation script:
- Add
scripts/generate-translations.js
using our example - Run it to generate initial translations:
node scripts/generate-translations.js
- Add
- Update package.json:
- Add the script commands as shown in our example
- Implement the language switcher component:
- Create
src/components/LanguageSwitcher.js
- Create
- Update your App.js to use translations:
- Import i18n and the language switcher
- Use the
t
function for all text content
- Optional: Set up CI/CD:
- Add GitHub Actions workflow if using GitHub
Getting Started with Humanicer
- Sign up at Humanicer for your API key
- Set up your API key in your environment variables or config
- Run your translation script to generate all language files
- Build and test your app to ensure translations work correctly
Localisation is now straightforward. Focus on building great features whilst Humanicer handles the translations. Your global users will thank you.