-
-
Save deadcoder0904/43606deb9625015981d6ca6f7f33f3e3 to your computer and use it in GitHub Desktop.
Translate React Native application to support multiple languages
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @format | |
*/ | |
import {AppRegistry} from 'react-native'; | |
import App from './App'; | |
import {name as appName} from './app.json'; | |
AppRegistry.registerComponent(appName, () => App); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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": "Изменить язык" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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