简体   繁体   中英

Updating React Native language by user choice

I'm using I18n to create multiple language application.

I have created "on first launch" screen that offers the user option to choose his preferred language, it works fine but there is problem.

Once I choose language, App() component updates, it showing login component ( initialRouteName="Login" ). But the language is still by default English, only when I proceed to another screen it works or either FastRefresh login screen.

const Stack = createStackNavigator();
const HAS_LAUNCHED = "hasLaunched";
const ENGLISH = "en";
const HEBREW = "he";


//Save the language as AsyncStorage for other times the user will open the app
async function setAppLaunched(en) {
  AsyncStorage.clear()
  AsyncStorage.setItem(HAS_LAUNCHED, "true");
  AsyncStorage.setItem(en ? ENGLISH : HEBREW, "true");
  if(await AsyncStorage.getItem(HEBREW)){
    i18n.locale = "he";
    I18nManager.forceRTL(true);
  }
  else{
    i18n.locale = "en";
    I18nManager.forceRTL(false);
  }
}


//If first launch show this screen
function CheckIfFirstLaunch({ onSelect }) {

  const selectLaunched = (value) => {
    setAppLaunched(value);
    onSelect();
  };


  return (
    <View>
        <Text>Choose Language</Text>
        <Button onPress={() => selectLaunched(false)} title="Hebrew"/>
        <Button onPress={() => selectLaunched(true)} title="English"/>
    </View>
  );
}

export default function App() {
  const [selected, setSelected] = useState(false);

  const verifyHasLaunched = async () => {
    try {
      const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
      setSelected(hasLaunched != null);
    } catch (err) {
      setSelected(false);
    }
  };

  useEffect(() => verifyHasLaunched, []);

  if (!selected){
    return <CheckIfFirstLaunch onSelect={() => setSelected(true)} />;
  }
  else{
    const verifyLang = async () => {
      const lang = await AsyncStorage.getItem('he');
      if(lang != null){
        i18n.locale = "he";
        I18nManager.forceRTL(true);
      }
      else{
        i18n.locale = "en";
        I18nManager.forceRTL(false);
      }
   };
   () => verifyLang;
  }

  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
        <Stack.Screen name="Login" component={Login} />
        <Stack.Screen name="Register" component={Register} />
        <Stack.Screen name="Dashboard" component={Dashboard} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

I wonder since I have updated my component, the language should update as well, isn't?

Here are some screenshots that will visually explain what my problem is.

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

How can I update React Native app by user choice using I18n plugin?

EDIT

Debugging results:

selectedLaunched(value) - value returns boolean value correctly.

Checking setAppLaunched(en) if statement to see if responding correctly, it does.

selected state is also working fine and rendering the NavigationContainer component right after it set to true.

CheckIfFirstLaunch Launch Screen should also be inside NavigationContainer, It's outside navigation container might be cause of problem, All your screen be should inside navigationContainer.

Use an state for inital route,

const [initialRoute, setRouteState] = useState('CheckIfFirstLaunch');

then inside your condition update state

  if (!selected){
     setRouteState('CheckIfFirstLaunch);
    return <CheckIfFirstLaunch onSelect={() => setSelected(true)} />;
  }
  else{
     setRouteState('Login');
   }

then conditionally set your initial route,

  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: false}} initialRouteName{initialRoute}">
      <Stack.Screen name="CheckIfFirstLaunch" component={CheckIfFirstLaunch} />
        <Stack.Screen name="Login" component={Login} />
        <Stack.Screen name="Register" component={Register} />
        <Stack.Screen name="Dashboard" component={Dashboard} />
      </Stack.Navigator>
    </NavigationContainer>
  );

you can keep other app screens in a separate stack

const AppStack = createStackNavigator();

function MyAppStack() {
   <APPStack.Screen name="Login" component={Login} />
   <APPStack.Screen name="Register" component={Register} />
     <APPStack.Screen name="Dashboard" component={Dashboard} />
}

and then main navigationContainer as

<NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
      <Stack.Screen name="CheckIfFirstLaunch" component={CheckIfFirstLaunch} />
        <Stack.Screen name="Login" component={MyAppStack} />
      </Stack.Navigator>
    </NavigationContainer>

then in the end when language is selected store it to asyncStorage your language ie "en " and the wether you have selected a language or not


AsyncStorage.setItem("languageSelected",true)

and navigate to the Login screen.

and other than that on App Lauch, you check with Asyncstorage rather than isSelected state if the language has already been selected

const langSelected = await AsyncStorage.getItem('languageSelected');

if(langSelected) {
   setInitialRoute('Login')
   // check which language has been Selected
   const lang = await AsyncStorage.getItem('he');
      if(lang != null){
        i18n.locale = "he";
        I18nManager.forceRTL(true);
      }
      else{
        i18n.locale = "en";
        I18nManager.forceRTL(false);
      }
} else {
    setInitialRoute('CheckIfFirstScreen')
}

An full Dummy Code


// CheckIfFirstLaunch screen component
export function CheckIfFirstLaunch(props) {
  
 //set default language on button selection
  setDefaultLanguage = (lang) => {
     //set default language language
      i18n.locale = lang;
      //If Hebrew switch to RTL
      if(lang === "he") {
              I18nManager.forceRTL(true);
       }
       AsyncStorage.setItem("language",lang)
           props.navigation.navigate("Login")
   }

  return (
    <View>
        <Text>Choose Language</Text>
        <Button onPress={() => setDefaultLanguage("he")} title="Hebrew"/>
        <Button onPress={() => setDefaultLanguage("en")} title="English"/>
    </View>
  );
}

const Stack = createStackNavigator();

export default function App() {
  //intial default route set FirstLaunch Screen
  const [initialAppRoute, setInitialRoute] = useState("CheckIfFirstLaunch");
  
  // a loading state to check if react has checked for data from asyncStorage
  const [dataLoaded, setDataLoaded] = useState("false");
  
  //verify if language has been selected
  const verifyHasLaunched = async () => {
    try {
      //get language from asyncStorage
      const lang = await AsyncStorage.getItem("language");
      
      // if language value stored in asyncStorage    
      if(hasLaunched) {
        
         // if language is hebrew do this  else do that
        if(lang === 'he'){
          i18n.locale = "he";
          I18nManager.forceRTL(true);
          }
        else{
           i18n.locale = "en";
           I18nManager.forceRTL(false);
          }
        // set initial route to Login
        setInitialRoute(initialAppRoute:'Login')           
      }else {
         // else initial route should language screen
         setInitialRoute(initialAppRoute:'CheckIfFirstLaunch')
      }
    } catch (err) {
          // if error do something here
    }
  };

  useEffect(() => verifyHasLaunched, []);
  return (
   {dataLoaded  ? 
    <NavigationContainer>
     <Stack.Navigator screenOptions={{headerShown: false}} initialRouteName={initialAppRoute}">
      <Stack.Screen name="CheckIfFirstLaunch" component={CheckIfFirstLaunch} />
        <Stack.Screen name="Login" component={Login} />
        <Stack.Screen name="Register" component={Register} />
        <Stack.Screen name="Dashboard" component={Dashboard} />
      </Stack.Navigator>
      </Stack.Navigator>
    </NavigationContainer>
    :
     // or keep loading loader until react has checked for data from asyncStorage
      null
    }
  );
}

As I can see the initial route is login so it get's rendered in english by default. And the popup for selecting the language on first launch is rendered on the login screen itself. So once you select a different language it updates that language but the component doesn't re-render as there are no state updates. That's why the language updates on navigating to other screens or on fast reload. There are two solutions that can be implemented:-

  1. Create the language selection as a different screen and navigate to that screen initially on first launch.
  2. And the other option that you can explore is try updating something in the local state of the login screen that will re-render the component.

The problem was trying to change the language using async functions and using await which caused the language to change only after the second screen. So in that case I just moved one step backwards the language switch.

Instead having it in async function setAppLaunched(en) function, I moved it to const selectLaunched = (value) => function like that:

const selectLaunched = (value) => {
      if(!value){
        i18n.locale = "he";
        I18nManager.forceRTL(true);
      }
      else{
        i18n.locale = "en";
        I18nManager.forceRTL(false);
      }
    setAppLaunched(value);
    onSelect();
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM