Reputation: 184
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.
Upvotes: 4
Views: 1019
Reputation: 184
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();
};
Upvotes: 0
Reputation: 326
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 :-
Upvotes: 1
Reputation: 813
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 i.e. "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
}
);
}
Upvotes: 3