I eventually use Context to manage the global state of language.
In Context.js
import React, {useState, useEffect} from 'react'
import AsyncStorage from '@react-native-async-storage/async-storage';
import i18n from '../config/i18n';
const Context = React.createContext()
export const Provider =({children}) => {
const [language, setLanguage] = useState(i18n.locale)
// In the beginning of App, check if user has selected language before.
// If not, use system default language
useEffect(()=> {
AsyncStorage.getItem('storedLanguage').then(data => {
if (data === null) {
setLanguage(Localization.locale)
}
else {setLanguage(data)}
console.log('language inside useEffect', language)
}).catch((error) => console.log(error))
}, [])
const userChangeLanguage = async (language) => {
setLanguage(language)
await AsyncStorage.setItem('storedLanguage', language)
}
return (
<Context.Provider value={{data: language, userChangeLanguage}}>
{children}
</Context.Provider>)
}
export default Context
And then wrap the App.js inside the provider like so:
import React from 'react'
import Navigation from './scr/navigation/Navigation'
import { Provider } from './scr/context/Context'
const App = () => {
return (
<Navigation/>
)
}
export default () => {
return <Provider>
<App/>
</Provider>
}
In the LanguagePicker.js screen:
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import Context from '../context/Context';
import React, { useContext } from 'react';
import { StackActions } from '@react-navigation/native';
const LanguagePicker = ({navigation}) => {
const {userChangeLanguage} = useContext(Context)
const handleChangeLanguage = (language) => {
userChangeLanguage(language);
navigation.dispatch(StackActions.replace('MyDrawer'))
}
return(
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={()=>handleChangeLanguage('zh')}>
<Text style={styles.text}>繁體中文</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => handleChangeLanguage('zh-Hans')}>
<Text style={styles.text}>简体中文</Text>
</TouchableOpacity>
etc...
Lastly, in Navigation.js, get data (language) from Context and set i18n.locale = data
const Navigation = () => {
const {data} = useContext(Context)
i18n.locale= data;
Alternatively, could use expo updates. This is definitely an overkill to force the whole app to check for latest update just to refresh language. Once press a language, the app will restart. Not the smoothest UX, but not too bad. Considering it only takes one line of code so I will still count it as a quick and dirty solution.
const [languageSelected, setLanguageSelected] = useState(i18n.locale)
const handleSelectLanguage = (n) => {
setLanguageSelected(n)
AsyncStorage.setItem('storedLanguage', n)
Updates.reloadAsync()
}
i18n.locale = languageSelected