【问题标题】:Updating React Native language by user choice根据用户选择更新 React Native 语言
【发布时间】:2021-02-01 20:07:52
【问题描述】:

我正在使用I18n 创建多语言应用程序。

我创建了“首次启动”屏幕,让用户可以选择自己喜欢的语言,它运行良好,但存在问题。

一旦我选择语言,App() 组件更新,它会显示登录组件 (initialRouteName="Login")。但是默认情况下语言仍然是英语,只有当我进入另一个屏幕时它才能工作或FastRefresh登录屏幕。

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>
  );
}

我想知道,既然我已经更新了我的组件,那么语言也应该更新,不是吗?

这里有一些屏幕截图可以直观地解释我的问题。

如何使用 I18n 插件根据用户选择更新 React Native 应用程序?

编辑

调试结果:

selectedLaunched(value) - value 正确返回布尔值。

检查setAppLaunched(en) if 语句以查看是否正确响应。

selected 状态也可以正常工作,并在 NavigationContainer 组件设置为 true 后立即渲染它。

【问题讨论】:

    标签: javascript react-native


    【解决方案1】:

    CheckIfFirstLaunch 启动屏幕也应该在 NavigationContainer 内,它在导航容器之外可能会导致问题,您的所有屏幕都应该在 NavigationContainer 内。

    对初始路由使用状态,

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

    然后在你的条件更新状态中

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

    然后有条件地设置你的初始路线,

      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>
      );
    

    您可以将其他应用屏幕保存在单独的堆栈中

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

    然后主navigationContainer为

    <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 i.e. "en " and the wether you have selected a language or not

    
    AsyncStorage.setItem("languageSelected",true)
    

    然后导航到登录屏幕。

    除了在 App Lauch 上,如果语言已被选择,您可以使用 Asyncstorage 而不是 isSelected 状态进行检查

    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')
    }
    

    完整的虚拟代码

    
    // 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
        }
      );
    }
    

    【讨论】:

    • 您能否缩小到一个包含所有内容的代码?当我尝试你的方式时,我得到了太多的重新渲染错误,似乎每次我设置 initialRoute 状态时应用程序组件都会在某种循环中重新渲染组件,也许我弄错了。
    • 我在更新答案的末尾放置了一个完整的代码,请检查。因为我没有办法测试它,它可能有一些问题,但大多数情况下它应该能够工作并且逻辑是相同的
    • 不幸的是,这给了我与我的问题代码相同的结果。
    • 我对 dummycode 做了一些小改动,清除 AsyncStorage 并重试。
    • 我已经清除了 AsyncStorage,现在这根本不起作用。
    【解决方案2】:

    我可以看到初始路由是登录,因此默认情况下它会以英文呈现。首次启动时选择语言的弹出窗口会呈现在登录屏幕本身上。因此,一旦您选择了不同的语言,它就会更新该语言,但组件不会重新渲染,因为没有状态更新。这就是语言在导航到其他屏幕或快速重新加载时更新的原因。 有两种解决方案可以实现:-

    1. 将语言选择创建为不同的屏幕,并在首次启动时导航到该屏幕。
    2. 您可以探索的另一个选项是尝试更新登录屏幕的本地状态中的某些内容,这将重新呈现组件。

    【讨论】:

    • 嘿,我已经尝试了您提供的第一个解决方案,但现在我无法让 onSelect 事件工作。这是我尝试过的:codepen.io/yotamdahan/pen/vYyYJJa 我无法执行第二个解决方案,因为我在登录屏幕中没有使用状态。
    • @ExCluSiv3 是没有导航到登录屏幕还是您收到错误消息。因为如果它没有导航,那么在选择时您可以尝试用登录屏幕替换当前屏幕,因为更改初始路线不会导航屏幕。
    • 它没有导航,没有错误。我已经根据选定的状态切换了 intialRouteName,但有些东西在那里不起作用。
    • 我已经成功导航了,但结果还是一样..
    【解决方案3】:

    问题是尝试使用 async 函数和使用 await 更改语言,这导致语言仅在第二个屏幕后更改。 所以在那种情况下,我只是将语言切换向后移动了一步。

    我将它移到const selectLaunched = (value) =&gt; 函数中,而不是将它放在async function setAppLaunched(en) 函数中:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-09
      • 2019-07-08
      • 1970-01-01
      • 2021-08-25
      • 1970-01-01
      相关资源
      最近更新 更多