1

I am developing a multi-language App using i18n.js and expo-localization. The default language is the system default and users can select their preferred language. But after the language is selected, only those screens under StackActions.replace('MyDrawer') are refreseed and the language changed. Other screens still use the default langauge. I wonder if there are any ways to refresh all screens in React Navigation?

import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import i18n from 'i18n-js';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { StackActions } from '@react-navigation/native';


const LanguagePicker = ({navigation, route}) => {

  const [languageSelected, setLanguageSelected] = useState(null)
   
  const handleSelectLanguage = (n) => {
    setLanguageSelected(n)
    AsyncStorage.setItem('storedLanguage', n)
    navigation.dispatch(
      StackActions.replace('MyDrawer'))
  }

  i18n.locale = languageSelected
  console.log("i18n.locale in language picker", i18n.locale)

return(

  <View style={styles.container}>
 
  <TouchableOpacity onPress={() => {handleSelectLanguage('pt')}}>
      <Text style={styles.text}>Português</Text>
  </TouchableOpacity>
  <TouchableOpacity onPress={() => {handleSelectLanguage('en')}}>
      <Text style={styles.text}>English</Text>
  </TouchableOpacity>
</View>
)}

Rachel
  • 51
  • 7
  • 1
    I recommend using `react-i18next`, the library optimize for React/React Native and trigger screen rerender when subscribing to any translation - https://react.i18next.com/ – Fiston Emmanuel Feb 12 '22 at 11:02
  • If you are changing the app language, best user experience is to restart the app. You can refer to https://stackoverflow.com/questions/43090884/resetting-the-navigation-stack-for-the-home-screen-react-navigation-and-react-n to restart the application. Most app handle this by restarting app – Anurag Chutani Feb 13 '22 at 11:43
  • @AnuragChutani Thanks a lot for the tips! But I can only reset it up to MyDrawer: 'navigation.dispatch( CommonActions.reset({ index:1, routes:[ {name: 'MyDrawer'} ] }))' ... I nest MyDrawer inside Stack Navigator. And I cannot reset the whole Stack Navigator (I call it MyStack). Is there anyway just to reset the whole navigation? – Rachel Feb 14 '22 at 04:18

1 Answers1

2

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
Rachel
  • 51
  • 7
  • 2
    I would like to thank you guys who gave me suggestions and answers. Please don't beat me up for answering my own questions. I would much rather to ask a question and get answers from somebody else. But since I have gone through the pains in figuring out how to make use of Context, I would like to share it here where it could help some other people. I got loads of answers from searching old Q&A in Stack Overflow myself and am very grateful. Let's accumulate the knowledge and experience! – Rachel Mar 03 '22 at 10:22