Skip to content

Instantly share code, notes, and snippets.

@deadcoder0904
Last active November 29, 2019 03:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deadcoder0904/43606deb9625015981d6ca6f7f33f3e3 to your computer and use it in GitHub Desktop.
Save deadcoder0904/43606deb9625015981d6ca6f7f33f3e3 to your computer and use it in GitHub Desktop.
Translate React Native application to support multiple languages
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {NavigationNativeContainer} from '@react-navigation/native';
import React from 'react';
import {Platform, StatusBar} from 'react-native';
import {ThemeProvider} from 'react-native-elements';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import Ionicons from 'react-native-vector-icons/Ionicons';
import {Home} from './screens/Home';
import {Settings} from './screens/Settings';
const Tab = createBottomTabNavigator();
const isIOS = Platform.OS === 'ios';
const App = () => (
<>
<NavigationNativeContainer>
<StatusBar barStyle="dark-content" />
<SafeAreaProvider>
<ThemeProvider>
<Tab.Navigator
screenOptions={({route}) => ({
tabBarIcon: ({focused, color, size}) => {
let iconName;
if (route.name === 'Home') {
iconName = `${isIOS ? 'ios' : 'md'}-information-circle${
focused ? '' : '-outline'
}`;
} else if (route.name === 'Settings') {
iconName = `${isIOS ? 'ios' : 'md'}-options`;
}
// You can return any component that you like here!
return <Ionicons name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
}}>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
</ThemeProvider>
</SafeAreaProvider>
</NavigationNativeContainer>
</>
);
export default App;
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {NavigationNativeContainer} from '@react-navigation/native';
import React from 'react';
import {Platform, StatusBar} from 'react-native';
import {ThemeProvider} from 'react-native-elements';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import Ionicons from 'react-native-vector-icons/Ionicons';
import {LocalizationProvider} from './components/Translations';
import {Home} from './screens/Home';
import {Settings} from './screens/Settings';
const Tab = createBottomTabNavigator();
const isIOS = Platform.OS === 'ios';
const App = () => (
<>
<NavigationNativeContainer>
<StatusBar barStyle="dark-content" />
<SafeAreaProvider>
<LocalizationProvider>
<ThemeProvider>
<Tab.Navigator
screenOptions={({route}) => ({
tabBarIcon: ({focused, color, size}) => {
let iconName;
if (route.name === 'Home') {
iconName = `${isIOS ? 'ios' : 'md'}-information-circle${
focused ? '' : '-outline'
}`;
} else if (route.name === 'Settings') {
iconName = `${isIOS ? 'ios' : 'md'}-options`;
}
// You can return any component that you like here!
return <Ionicons name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
}}>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
</ThemeProvider>
</LocalizationProvider>
</SafeAreaProvider>
</NavigationNativeContainer>
</>
);
export default App;
{
"shop.title": "Grocery Shop",
"date.title": "Today's Date",
"date.format": "21/11/2019",
"app.currency": "$",
"fruit.apple": "Apple",
"fruit.apple.price.value": 3,
"fruit.banana": "Banana",
"fruit.banana.price.value": 2,
"fruit.watermelon": "Watermelon",
"fruit.watermelon.price.value": 5,
"added.items.one": "Added {no} item",
"added.items.endingWithZero": "Added {no} items",
"added.items.endingWithOne": "Added {no} item",
"added.items.endingWithTwoToFour": "Added {no} items",
"added.items.endingWithOther": "Added {no} items",
"cart.total.title": "Total Sum",
"cart.total.value.currencyStart": "{currencyStart}{value}",
"cart.total.value.currencyEnd": "{value}{currencyEnd}",
"settings.change_language": "Change Language"
}
import React, {useState} from 'react';
import {ScrollView, StyleSheet, View} from 'react-native';
import {Text} from 'react-native-elements';
import {useSafeArea} from 'react-native-safe-area-context';
import {Tile} from '../components/Tile';
const fruits = [
{
name: 'Apple',
price: '$3',
pic: require('../assets/apple.png'),
},
{
name: 'Banana',
price: '$2',
pic: require('../assets/banana.png'),
},
{
name: 'Watermelon',
price: '$5',
pic: require('../assets/watermelon.png'),
},
];
export const Home = () => {
const [total, changeTotal] = useState(0);
const insets = useSafeArea();
const addToTotal = price => {
changeTotal(total + price);
};
const removeFromTotal = price => {
changeTotal(total - price);
};
return (
<ScrollView>
<View
style={[
styles.container,
{paddingTop: insets.top, paddingBottom: insets.bottom},
]}>
<Text h1 h1Style={styles.grocery}>
Grocery Shop
</Text>
<Text style={styles.date}>Today's date: 21/11/2019</Text>
{fruits.map(fruit => {
return (
<React.Fragment key={fruit.name}>
<Tile
fruit={fruit}
addToTotal={addToTotal}
removeFromTotal={removeFromTotal}
/>
</React.Fragment>
);
})}
<Text h3 h3Style={styles.total}>
Total Sum: ${total}
</Text>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 10,
},
grocery: {
textAlign: 'center',
marginBottom: 10,
},
date: {
textAlign: 'center',
marginBottom: 20,
fontSize: 20,
},
total: {
marginTop: 30,
textAlign: 'center',
color: 'red',
},
});
import React, {useContext, useState} from 'react';
import {ScrollView, StyleSheet, View} from 'react-native';
import {Text} from 'react-native-elements';
import {useSafeArea} from 'react-native-safe-area-context';
import {Tile} from '../components/Tile';
import {LocalizationContext} from '../components/Translations';
export const Home = () => {
const {translations, initializeAppLanguage} = useContext(LocalizationContext);
const [total, changeTotal] = useState(0);
const insets = useSafeArea();
initializeAppLanguage(); // 1
// 2
const fruits = [
{
// 3
name: translations['fruit.apple'],
price:
translations['app.currency'] + translations['fruit.apple.price.value'],
pic: require('../assets/apple.png'),
},
{
name: translations['fruit.banana'],
price:
translations['app.currency'] + translations['fruit.banana.price.value'],
pic: require('../assets/banana.png'),
},
{
name: translations['fruit.watermelon'],
price:
translations['app.currency'] +
translations['fruit.watermelon.price.value'],
pic: require('../assets/watermelon.png'),
},
];
// 4
const addToTotal = price => {
changeTotal(total + price);
};
const removeFromTotal = price => {
changeTotal(total - price);
};
return (
<ScrollView>
<View
style={[
styles.container,
{paddingTop: insets.top, paddingBottom: insets.bottom},
]}>
{/* 5 */}
<Text h1 h1Style={styles.grocery}>
{translations['shop.title']}
</Text>
<Text style={styles.date}>
{translations['date.title']}: {translations['date.format']}
</Text>
{fruits.map(fruit => {
return (
<React.Fragment key={fruit.name}>
<Tile
fruit={fruit}
addToTotal={addToTotal}
removeFromTotal={removeFromTotal}
/>
</React.Fragment>
);
})}
<Text h3 h3Style={styles.total}>
{/* 6 */}
{translations['cart.total.title']}:
{translations.formatString(
translations['cart.total.value.currencyStart'],
{
currencyStart: translations['app.currency'],
value: total,
},
)}
</Text>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 10,
},
grocery: {
textAlign: 'center',
marginBottom: 10,
},
date: {
textAlign: 'center',
marginBottom: 20,
fontSize: 20,
},
total: {
marginTop: 30,
textAlign: 'center',
color: 'red',
},
});
/**
* @format
*/
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
{
"shop.title": "Продуктовый магазин",
"date.title": "Сегодняшняя дата",
"date.format": "21.11.19",
"app.currency": "₽",
"fruit.apple": "яблоко",
"fruit.apple.price.value": 10,
"fruit.banana": "Банан",
"fruit.banana.price.value": 7,
"fruit.watermelon": "Арбуз",
"fruit.watermelon.price.value": 25,
"cart.total": "Итого",
"added.items.one": "добавлено {no} товар",
"added.items.endingWithZero": "добавлено {no} товаров",
"added.items.endingWithOne": "добавлено {no} товар",
"added.items.endingWithTwoToFour": "добавлено {no} товара",
"added.items.endingWithOther": "добавлено {no} товара",
"cart.total.title": "Итого",
"cart.total.value.currencyStart": "{currencyStart}{value}",
"cart.total.value.currencyEnd": "{value}{currencyEnd}",
"settings.change_language": "Изменить язык"
}
import React, {useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {ListItem, Text} from 'react-native-elements';
import {useSafeArea} from 'react-native-safe-area-context';
const langs = ['en', 'ru'];
export const Settings = () => {
const [lang, changeLang] = useState('en');
const insets = useSafeArea();
return (
<View style={[styles.container, {paddingTop: insets.top}]}>
<Text h4 h4Style={styles.language}>
Change Language
</Text>
{langs.map((currentLang, i) => (
<ListItem
key={i}
title={currentLang}
bottomDivider
checkmark={currentLang === lang}
onPress={() => changeLang(currentLang)}
/>
))}
</View>
);
};
const styles = StyleSheet.create({
language: {
paddingTop: 10,
textAlign: 'center',
},
});
import React, {useContext} from 'react';
import {StyleSheet, View} from 'react-native';
import {ListItem, Text} from 'react-native-elements';
import {useSafeArea} from 'react-native-safe-area-context';
import {LocalizationContext} from '../components/Translations';
export const Settings = () => {
const insets = useSafeArea();
const {
translations,
appLanguage,
setAppLanguage,
initializeAppLanguage,
} = useContext(LocalizationContext); // 1
initializeAppLanguage(); // 2
return (
<View style={[styles.container, {paddingTop: insets.top}]}>
<Text h4 h4Style={styles.language}>
{translations['settings.change_language']} {/* 3 */}
</Text>
{translations.getAvailableLanguages().map((currentLang, i) => ( {/* 4 */}
<ListItem
key={i}
title={currentLang}
bottomDivider
checkmark={appLanguage === currentLang}
onPress={() => {
setAppLanguage(currentLang);
}}
/>
))}
</View>
);
};
const styles = StyleSheet.create({
language: {
paddingTop: 10,
textAlign: 'center',
},
});
import React, {useState} from 'react';
import {Dimensions, StyleSheet, View} from 'react-native';
import {Icon, Image, Text} from 'react-native-elements';
const {width} = Dimensions.get('window');
export const Tile = ({fruit, addToTotal, removeFromTotal}) => {
const [cart, changeCart] = useState(0);
const {name, pic, price} = fruit;
const fruitPrice = +fruit.price.substring(1);
const addToCart = () => {
const noOfItems = cart + 1;
changeCart(noOfItems);
addToTotal(fruitPrice);
};
const removeFromCart = () => {
if (cart > 0) {
const noOfItems = cart - 1;
changeCart(noOfItems);
removeFromTotal(fruitPrice);
}
};
return (
<>
<Image
resizeMode="contain"
source={pic}
style={{width, height: width * 0.8}}
/>
<View style={styles.flex}>
<Icon name="pluscircleo" type="antdesign" onPress={addToCart} />
<Text h4 h4Style={styles.name}>
{name} ({price})
</Text>
<Icon name="minuscircleo" type="antdesign" onPress={removeFromCart} />
</View>
<Text style={styles.cart}>Added {cart} items</Text>
</>
);
};
const styles = StyleSheet.create({
flex: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
name: {
margin: 20,
},
cart: {
textAlign: 'center',
},
});
import React, {useContext, useState} from 'react';
import {Dimensions, StyleSheet, View} from 'react-native';
import {Icon, Image, Text} from 'react-native-elements';
import {LocalizationContext} from './Translations';
const {width} = Dimensions.get('window');
const translateNumber = (translations, cart) => {
const key = 'added.items.';
if (cart === 1) {
return translations.formatString(translations[key + 'one'], {
no: cart,
});
}
if (
(cart % 10 === 0 ||
cart % 10 === 5 ||
cart % 10 === 6 ||
cart % 10 === 7 ||
cart % 10 === 8 ||
cart % 10 === 9) &&
(cart % 100 === 11 ||
cart % 100 === 12 ||
cart % 100 === 13 ||
cart % 100 === 14)
) {
return translations.formatString(translations[key + 'endingWithZero'], {
no: cart,
});
}
if (cart % 10 === 1 && cart % 100 !== 11) {
return translations.formatString(translations[key + 'endingWithOne'], {
no: cart,
});
}
if (
(cart % 10 === 2 || cart % 10 === 3 || cart % 10 === 4) &&
(cart % 100 !== 12 || cart % 100 !== 13 || cart % 100 !== 14)
) {
return translations.formatString(
translations[key + 'endingWithTwoToFour'],
{
no: cart,
},
);
}
return translations.formatString(translations[key + 'endingWithOther'], {
no: cart,
});
};
export const Tile = ({fruit, addToTotal, removeFromTotal}) => {
const {translations} = useContext(LocalizationContext);
const [cart, changeCart] = useState(0); // 1
const {name, pic, price} = fruit;
const fruitPrice = +fruit.price.substring(1);
// 2
const addToCart = () => {
changeCart(cart + 1);
addToTotal(fruitPrice);
};
const removeFromCart = () => {
if (cart > 0) {
changeCart(cart - 1);
removeFromTotal(fruitPrice);
}
};
return (
<>
<Image
resizeMode="contain"
source={pic}
style={{width, height: width * 0.8}}
/>
<View style={styles.flex}>
{/* 3 */}
<Icon name="pluscircleo" type="antdesign" onPress={addToCart} />
<Text h4 h4Style={styles.name}>
{name} ({price})
</Text>
<Icon name="minuscircleo" type="antdesign" onPress={removeFromCart} />
</View>
{/* 4 */}
<Text style={styles.cart}>{translateNumber(translations, cart)}</Text>
</>
);
};
const styles = StyleSheet.create({
flex: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
name: {
margin: 20,
},
cart: {
textAlign: 'center',
},
});
import AsyncStorage from '@react-native-community/async-storage'; // 1
import React, {createContext, useState} from 'react';
import LocalizedStrings from 'react-native-localization'; // 2
import * as RNLocalize from 'react-native-localize'; // 3
import en from '../localization/en.json';
import ru from '../localization/ru.json';
const DEFAULT_LANGUAGE = 'en';
const APP_LANGUAGE = 'appLanguage';
const languages = {en, ru};
const translations = new LocalizedStrings(languages); // 4
export const LocalizationContext = createContext({ // 5
translations,
setAppLanguage: () => {}, // 6
appLanguage: DEFAULT_LANGUAGE, // 7
initializeAppLanguage: () => {}, // 8
});
export const LocalizationProvider = ({children}) => { // 9
const [appLanguage, setAppLanguage] = useState(DEFAULT_LANGUAGE);
// 11
const setLanguage = language => {
translations.setLanguage(language);
setAppLanguage(language);
AsyncStorage.setItem(APP_LANGUAGE, language);
};
// 12
const initializeAppLanguage = async () => {
const currentLanguage = await AsyncStorage.getItem(APP_LANGUAGE);
if (currentLanguage) {
setLanguage(currentLanguage);
} else {
let localeCode = DEFAULT_LANGUAGE;
const supportedLocaleCodes = translations.getAvailableLanguages();
const phoneLocaleCodes = RNLocalize.getLocales().map(
locale => locale.languageCode,
);
phoneLocaleCodes.some(code => {
if (supportedLocaleCodes.includes(code)) {
localeCode = code;
return true;
}
});
setLanguage(localeCode);
}
};
return (
<LocalizationContext.Provider
value={{
translations,
setAppLanguage: setLanguage, // 10
appLanguage,
initializeAppLanguage,
}}>
{children}
</LocalizationContext.Provider>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment